Войти
ПрограммированиеСтатьиГрафика

Световые эффекты в OpenGL.

Автор: terror

Введение
Блики
Попиксельное освещение
Вместо заключения

Введение

Если текстуры являются самой красивой частью трехмерной сцены, то освещение, бесспорно, стоит на втором месте. Даже в старые времена игры были с освещением, не таким как сегодня, но тогда и это было круто. На протяжении многих лет совершенствовалось железо, а вместе с ним и технологии создания света. Фактически, от самого начала до неопределенного конца, человечество стремится сделать мир на экране монитора фотореалистичным. Хотя иногда и стиль обязывает, например, в такой игре как Warcraft3 графика вовсе не фотореалистичная, больше мультяшная. Но это не значит, что разработчики Warcraft3 думают, как бы все похуже сделать. Просто, как я уже сказал, стиль обязывает. Ну и конечно дело каждого, что ему по душе.

Но все-таки 90% людей видят в слове графика именно фотореалистичную графику. Но в таком случае появляется целая куча проблем. Нужны качественные сфотографированные текстуры, и оцифрованные модели. Моделлеры, художники могут это все обеспечить (хоть и не просто это будет). Но современный графический движок должен не только уметь выводить текстурированные модели, но и сам уметь что-то делать на лету. Но вот тут и заключается вся проблема, в данном случае баланс сил между моделлером и программистом не сохраняется. В доказательство этому могут послужить старые игры, где все основано на статике, то есть на моделлерах, а программисты лишь советовали, где чего убрать, где что сместить, чтобы не сильно тормозило.

Да и в современное время на моделлеров ложится основная часть работы. Именно они делают картинку на экране монитора. Движок ее только оживляет, но сам практически ничего не может сделать. Хотя попытки есть.

Думаю флейма достаточно, перейдем непосредственно к делу. Я хотел бы поведать в двух словах о некоторых световых эффектах в OpenGL, которые создаются и отрисовываются в реальном времени. Думаю достаточно уже всяких лайтмапов и заранее просчитанных вертекслайтингов, есть вещи, которые вкупе смотрятся не хуже, а может даже и лучше.

Блики

Основой некоторых световых эффектов является геометрический способ представления освещения в пространстве. Зачастую такой способ представления световых эффектов можно охарактеризовать как fake, то есть фальшивка. В большинстве случаев под фальшивкой понимается то факт, что данную задачу нельзя решить в определенных условиях. Одним из самых главных условий является железо. И хотя современные компьютеры могут рисовать довольно быстро, они не смогут делать некоторые эффекты честно и при этом быстро.

Поэтому люди стали придумывать всякие fake'овые методы разнообразнейших эффектов, дабы улучшить общий графический уровень и при этом не особо потерять в быстродействии. К таким эффектам можно отнести и блики.

Вообще под словом блик не совсем понимается то о чем пойдет дальше речь. Более правильное название будет вспышка от источника света. Но как таковой вспышки у нас нет - она вечная, поэтому в пределах данной статьи будем называть это бликом от источника света.

Данная технология (если можно так сказать) используется довольно-таки давно. Я, конечно, не могу сказать, где впервые это было сделано, но в такой игре как Unreal уже были блики от источников света. Хотя чувствовалось, что они используются редко из-за того, что тогдашнее железо не позволяло использовать их на каждом шагу. Но с появлением Unreal Tournament все резко изменилось, в этой игре блики использовались на полную катушку. Может, железо круче стало, может из-за того, что на Direct3D перешли. Но ложка дегтя была в тамошних бликах. Они были расставлены чуть ли не на каждом шагу. В некоторых уровнях они просто пестрили своим количеством, что имхо было не очень хорошо. И, тем не менее, сабж уже давно используется, поэтому приведу пример как его можно сделать.

Сам по себе блик выглядит в виде обычного четырехугольника, который все время повернут к лицом к камере. Обычно их называют спрайтами. Многие думают, что спрайты это вчерашний день, при этом сами же их используют. Собственно это и есть тот fake о котором я говорил ранее, без него никуда не денешься. И его будут использовать еще очень долго.

В интернете описывается огромное количество методов создания спрайтов. В том числе и через новомодные шейдеры, но мы опустим этот зверский способ и сделаем что-нибудь более простое.

В примере к этой статье спрайт стоится по четырем точкам, а тип примитива задается как связанные треугольники. Толку, правда, не очень много от этого, но будем использовать их. Формула, по которой строится спрайт, выглядит так:

P + (( -right - up ) * size ))

P + ((  right - up ) * size ))
P + (( -right + up ) * size ))
P + ((  right + up ) * size ))

P - координаты центра спрайта.
size - размер спрайта (квадрат соответственно).
right и up это два вектора, взятые из матрицы модели. Для их задания нужны следующие значения.
Для вектора right - ModelMatrix[0], ModelMatrix[4], ModelMatrix[8].
Для вектора up - ModelMatrix[1], ModelMatrix[5], ModelMatrix[9].

Приведенный способ будет работать при соответствующем классе вектора, в нем должно быть целая куча перегруженных операторов. В противном случае придется расписывать каждую строку (каждый оператор) по всем трем компонентам, то есть для каждого X, Y и Z.

Осталось вывести на экран спрайт в виде, как я уже говорил, двух связанных треугольников. Текстурные координаты при этом такие:
{0.0f,1.0f} - для первой вершины.
{1.0f,1.0f} - для второй вершины.
{1.0f,0.0f} - для третей вершины.
{0.0f,0.0f} - для четвертой вершины.

То, что выведется на экран всего лишь 50% от конечного результата. Теперь нужно задать прозрачность. В данном случае достаточно будет:
glBlendFunc ( GL_SRC_ALPHA, GL_ONE );
После чего можно указать в виде четвертого компонента функции glColor4* значение альфа, тем самым, регулируя прозрачность спрайта.

Но это еще не все, часто можно встретить вопрос типа "Почему спрайты режут друг друга". Ответ простой - нужно не записывать глубину спрайта на момент его отрисовки, делается это через glDepthMask со значением 0 (false). После отрисовки спрайта нужно включить запись в глубину вызвав, glDepthMask со значением 1(true). Хотя то что я сказал можно и не делать для одиночного спрайта, более это подходит для комплексных эффектов, типа систем частиц.

Теперь пора вспомнить, чего мы вообще делаем. А делаем мы блик от источника света. Тут можно опять пофлеймить. Посмотрите как в реале выглядит блик от источника света, например, от обычной лампочки на потолке. Выглядит он в виде яркого пятна с жутко навороченной прозрачностью и яркой сердцевиной. Попробуйте закрыть половину этого блика рукой. Частично лучи все еще проходят (только половина закрыта), так как возможно центр блика все еще не полностью закрыт. При этом, как я уже сказал, частично лучи все еще проходят от центра лампочки и они как бы находятся впереди ладошки (ну или чем вы там закрываете). При этом если не спеша закрыть центр блика полностью то блик плавно исчезнет (плавно настолько, насколько вы плавно его будете закрывать). То есть выясняется сразу две вещи в отношении бликов от источников света, а именно:
- Блик все еще может находится перед закрываемым объектом, если источник этого блика не был полностью закрыт.
- Блик плавно исчезает ЗА объектом, настолько плавно, насколько плавно объект перекроет источник.

Я все это к чему, а к тому, что некоторые люди утверждают, что данный способ построения бликов явно глюкавый. Но, если же посмотреть, как это сделано "в реале", то выясняется, что там все точно также.

А теперь реализация. С первым пунктом вроде все просто. Так как мы используем fake'овый метод он у нас получится на автомате. Спрайт сам будет производить такой эффект, когда перекрываемый объект будет очень близко до полного перекрытия. На картинке это видно:

Изображение

При этом использовать glDepthMask() нельзя, так как он лишь закрывает глубину для текущего объекта (объектов), но не для всей сцены. Поэтому чтобы спрайт ложился на объекты как в реале нужно полностью выключать тест на глубину.
В данном случае это можно сделать, поставив  glDepthFunc() в GL_NOTEQUAL, это позволит игнорировать все входящие звонки от абонентов Вася Пупкин Корпорэйшн. Вы еще не заснули? Это позволит игнорировать все входящие Z значения пикселей, тем самым спрайт будет рисоваться как какой-то псевдо-объемный световой шар, то есть тот самый fake о котором я говорил в самом начале. Сразу же могу сказать, что GL_ALWAYS и полное отключение zTest'а даст такой же результат. Остальные параметры, насколько я помню, не пропустят входящие Z значения, поэтому zBuffer будет работать, как обычно и спрайты будут протыкать все стены, как показано на рисунке.

Изображение

Теперь пора развести очередной флейм и поговорить о самих текстурах для блика. Универсальные блики - это блики белого цвета. Такие легче всего поддаются цветовому контролю, так как белый цвет идеально подходит для переделки в любой другой. Поэтому лучше делать именно белые текстуры. Иногда бывает необходимость сделать уникальную текстуру с бликом. Вот такие кадры имеют полное право носить собственный цвет отличный от белого. Хотя это не самое главное в блике. Есть две вещи, которые делают блик действительно бликом. Это цвет его фона и размер. Цвет фона по периметру блика должен быть равен нулю, если это будет не так, то на экране будут нарисованы не прозрачные световые пятна, а прозрачные квадраты, в которых нарисованы пятна. Почти тоже самое касается и размеров блика. Они не должны вылезать за пределы картинки. При создании блика это невозможно заметить, глядя на блик. Но если посмотреть на цвета пикселов по краям блика, то можно заметить числа типа 10, 40, 30, 17 (это сразу для всех трех составляющих RGB). Этого быть не должно, края блика должны быть полностью черными. В противном случае световое пятно будет как бы вылезать за невидимую линию отсечения спрайта.

Но вернемся к баранам. Теперь вы знаете все, что нужно для того, чтобы реализовать первый пункт о бликах. Теперь пора поговорить о втором пункте.
Кто его забыл, напомню: "Блик плавно исчезает ЗА объектом, настолько плавно, насколько плавно объект перекроет блик". И тут же хочу всех немного обломать, дело в том, что реализовать такую фишку довольно-таки сложно. Одними fake'ами уже не ограничишься. Поэтому мы сделаем так, как это сделано в большинстве компьютерных игр, а именно: "Блик плавно исчезает ЗА объектом, настолько плавно, насколько это указал программист". То есть после того как блик скроется за объектом, нужно его самому плавно затушить.

Чтобы узнать виден ли центр блика или нет, можно воспользоваться буфером глубины и проекцией трехмерной точки на плоскость.
Сразу же разберем функцию которая это делает.

void RenderFlare ( flare_effect_t &flare, uint tex )
{
  if ( !flare.visible ) return;
  double sx,sy,sz;
  float  depth  = 0.0f;
  bool   in_vis = true;  // объект виден

  if ( flare.color.v[3] > 0.0f )
  {
    if ( CamPosition.GetDistance ( flare.pos ) > flare.dist )
      in_vis = false;

    // остальные проверки проверяются, только если in_vis все еще виден
    if ( in_vis )
      in_vis = Frustum.PointInFrustum ( flare.pos );

    if ( in_vis )
    {
      gluProject ( flare.pos.v[0], flare.pos.v[1], flare.pos.v[2], 
              Model_Matrix, Project_Matrix, ViewPort_Matrix, &sx, &sy, &sz );
      glReadPixels ( (int)sx, (int)sy, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth );
      if ( depth > sz )
        in_vis = true;
      else
        in_vis = false;
    }
  }

  if ( in_vis )
    // рисуем спрайт
  else
    // глушим спрайт

  flare.in_visible = in_vis;
}

Вначале находим расстояние от камеры до центра спрайта, и если оно больше той дистанции, на которой спрайт виден, то спрайт тушится. Дальше проверяем, лежит ли центр спрайта в пределах области видимости. Если нет, то ставим переменную in_vis в false. Эта переменная отвечает за то, что спрайт по какой-то причине не виден на экране и значит что его можно "тушить". Дальше нужно проверить, не перекрывает ли спрайт наша геометрия. Проще всего это проверить через оконную Z координату, то есть глубина трехмерной сцены. Это конечно же гораздо быстрее чем полноценные RayTrace методы. Для этого мы проецируем точку спрайта на экран и читаем пиксель в этой точке. Читаем исключительно компонент из zBuffer'а. Если получившаяся глубина больше той, что вернула функция gluProject, то значит спрайт ничем не перекрывается.

Итого, есть как минимум три проверки спрайта на отрисовку: проверка видимости спрайта, проверка пересечения спрайта с другой геометрией и проверка расстояния спрайта от его центра до камеры.

Совет: если есть несколько способов проверить надобность отрисовки чего-либо, то в начале выбирайте самый быстрый способ проверки.

Если хоть одно из условий не выполнится, то спрайт тушится. То есть плавно затухает. Кстати, расстояние вы можете регулировать исходя из общих настроек качества изображения вашей программы.

Поэтому если пользователь выбрал:
- самое низкое качество, то не нужно рисовать блики.
- низкое качество - рисуем блики на маленькой дистанции.
- среднее качество - рисуем блики на средней дистанции.
- высокое качество - рисуем блики на большой дистанции при этом можно включить еще какой-нибудь эффект связанный с бликами.

Вот и все.

Страницы: 1 2 3 Следующая »

#эффекты, #OpenGL

21 сентября 2003 (Обновление: 12 июня 2009)

Комментарии [16]