[ Новые сообщения · Участники · Правила форума · Поиск · RSS ]
  • Страница 1 из 1
  • 1
Форум » Прочее » Общение на любые темы » Световые эффекты в OpenGL
Световые эффекты в OpenGL
MerGC_TeamДата: Пятница, 20.09.2013, 11:20 | Сообщение # 1
Веселый админ
Группа: Администраторы
Сообщений: 32
Статус: Оффлайн
Введение

Если текстуры являются самой красивой частью трехмерной сцены, то освещение, бесспорно, стоит на втором месте. Даже в старые
времена игры были с освещением, не таким как сегодня, но тогда и это
было круто. На протяжении многих лет совершенствовалось железо, а вместе
с ним и технологии создания света. Фактически, от самого начала до
неопределенного конца, человечество стремится сделать мир на экране
монитора фотореалистичным. Хотя иногда и стиль обязывает, например, в
такой игре как 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, то значит спрайт ничем не перекрывается.
Итого, есть как минимум три проверки спрайта на
отрисовку: проверка видимости спрайта, проверка пересечения спрайта с
другой геометрией и проверка расстояния спрайта от его центра до камеры.
Совет: если есть несколько способов проверить надобность отрисовки чего-либо, то в начале выбирайте самый быстрый
способ проверки.

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

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

Попиксельное освещение

С увеличением производительности компьютеров люди начали задумываться об алгоритмах построения освещения в реальном времени.
Помните, что я вначале говорил? Так вот это был шаг к созданию чего-то
на уровне движка, а не на уровне моделлеров. В принципе такой эффект
появился тоже довольно-таки давно. Уже в первом Unreal был этот эффект и
даже не плохо работал, но это все из-за того, что уровни в первом U
были из "пяти полигонов".
И все же большинство игр использовала заранее
рассчитанные карты освещенности (lightmaps) и накладывали их на
геометрию. Сейчас тоже так делают, и еще очень долго будут делать, так
как халявно получить хорошие тени и хорошую производительность еще никто
не отказывался. Есть, конечно, варианты с вершинным освещением, его
нужно использовать "когда как". То есть к повсеместному использованию он
не подойдет. Тени по-прежнему лучше делать lightmap'ами, но для
некоторых объектов можно делать и вершинное освещение.
Нынешние требования моделлеров к движку это - не меньше
20.000 тысяч полигонов на хату, не меньше 7.000 тысяч полигонов на
брата, да чтобы все это дело освещалось по полной программе и пусть
только попробует на GeForce2 меньше 24 FPS выдать. В принципе оно
реально, все зависит от того, что еще есть на сцене. Но если взять общий
случай, то тормозить будет в ass. В любом случае сделать хотя бы эффект
света от взрыва или fake'овые тени, еще никому не вредило.
Не повредит и нам. Попиксельное освещение, это значит,
что для каждого пикселя карты освещения будет идти расчет цвета исходя
из параметров самого источника света. В примере, прилагаемом к этой
статье, реализован простейший точечный источник света (point light) с
линейным законом затухания. Есть разные способы создания такого
источника света. Остановимся на одном из них. Вся основная работа будет
идти через Combiner'ы. Это NV расширение поддерживается всеми
GeForce'ами. Разборы полетов этого монстра делать бесполезно, так как
для этого лучше читать спецификацию и смотреть конкретные примеры.
Мы лишь ограничимся инициализацией комбайнера и смыслом его работы:

Код
glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 1);glCombinerParameterfvNV(GL_CONSTANT_COLOR0_NV, Color);glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB);
glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE1_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB);
glCombinerOutputNV(GL_COMBINER0_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE0_NV, GL_NONE, GL_NONE,
GL_FALSE, GL_FALSE, GL_FALSE);glFinalCombinerInputNV(GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
glFinalCombinerInputNV(GL_VARIABLE_B_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
glFinalCombinerInputNV(GL_VARIABLE_C_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
glFinalCombinerInputNV(GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
glFinalCombinerInputNV(GL_VARIABLE_G_NV, GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_ALPHA);glEnable(GL_REGISTER_COMBINERS_NV);

Мда, количество параметров внушает. Фишка использования
комбайнеров в данном случае заключается в том, что расчет затухания
будет делать видео карта (все на нее свалили). Расчет интенсивности
находится по формуле:

Intensity = 1 - (x2 + y2 + z2)/R2
При этом, комбайнер для расчета интенсивности будет брать значения X и Y из текстуры.


Для расчета интенсивности по Z значения берутся из одномерной текстуры:


Все остальное за нас сделает правильно настроенный комбайнер.
Теперь нужно рассчитать текстурные координаты для
источника света. Все текстурные координаты должны быть приведены к
интервалу от 0 до 1. Поэтому желательно ставить GL_CLAMP_TO_EDGE при
инициализации текстурных GL_TEXTURE_WRAP_S и GL_TEXTURE_WRAP_T.
Далее для каждой вершины каждого треугольника нужно посчитать текстурные координаты. Делается это так:

Код
s = (Vertex1 - LightPos ) * (1.0f / (LightRadius*2.0f))) + 0.5f;
t = (Vertex2 - LightPos ) * (1.0f / (LightRadius*2.0f))) + 0.5f;
r = (Vertex3 - LightPos ) * (1.0f / (LightRadius*2.0f))) + 0.5f;


Обратите внимание на то, что мы считаем еще и r
текстурную координату. Как уже было ранее сказано, для расчета
интенсивности точечного источника света нам нужны были две текстуры.
Одна текстура рассчитывает интенсивность по X, Y, то есть s и t
текстурные координаты. А вторая текстура будет рассчитывать значение
интенсивности по Z, то есть r текстурная координата.
При таком повороте событий нужно правильно настроить массивы данных. Делается это так:

Код
glClientActiveTextureARB(GL_TEXTURE0_ARB);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, sizeof(vector), LightTVerts );glClientActiveTextureARB(GL_TEXTURE1_ARB);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);glTexCoordPointer(1, GL_FLOAT, sizeof(vector), &LightTVerts[0].v[2] );


Обратите внимание на запись второй текстуры. Мы говорим
OpenGL брать только одно значение из трех, то есть мы не делаем
отдельного массива, все идет в одном единственном. При этом указываем,
чтобы OpenGL брал только последнюю координату.
После всех приготовлений идет рендер освещенных вершин.
Пару слов об оптимизации. То, что используются массивы
вершин вместо glBegin/glEnd это уже хорошо. Более того, в массив вершин
записываются только те вершины, что попадают в радиус источника света.
Ну и тот факт, что затухание рассчитывается видео картой на комбайнерах.
Во всем остальном это жутко тормознутый эффект и использовать его
повсеместно в реальном времени будет слишком накладно, особенно если на
сцене много полигонов. Хотя если применять достойную оптимизацию, то
будет все нормально. Соответственно использовать кубы для разбивки
сцены, порталы, BSP, ну и прочие продвинутые вещи подходящие для
конкретно вашего случая. Данная сцена выдает порядка 30-45 FPS на моем
GeForce2MX, на ней присутствуют шесть динамических попиксельных
источников света. ИМХО не так уж и плохо…

Вместо заключения
Хотел бы сказать пару слов еще о некоторых эффектах, которые могут улучшить сцену.
В первую очередь, это, конечно же, системы частиц. Каждый
программист считает своим долгом написать "мотор" этих самых систем.
Может из-за того, что это просто интересно занятие. Так как системы
частиц это одна из немногих вещей, которую делает сам движок. Плюс это
какая никакая анимация. Плюс можно до посинения в глазах играться с
параметрами систем частиц. Поэтому фишку должен реализовать каждый сам и
по-своему.
Во вторую очередь можно отметить всяческие fake'овые
эффекты с анимационными текстурами или с генерирующимися текстурами,
что, конечно же, круче, но и накладывает некоторые тяжести на общую
производительность.
Так в примере к этой статье на сцене есть самая настоящая
fake'овая молния, построенная чисто на некотором количестве
анимационных текстур. По геометрической части, молния состоит всего из
двух вытянутых плоскостей расположенных крест на крест. Оригинальный
способ создания молний это молнии из систем частиц. Но такая вещь
требует длительной работы и будет жутко тормозить. И потом, если
анимационные картинки с молнией будут хорошо сделаны, при этом будут
хорошего качества и большого размера, то в определенных ситуациях они
могут быть круче, чем системы частиц, ну и, конечно же, гораздо быстрее.
Программа, прилагаемая к этой статье всего лишь демонстрация того, что
можно сделать на полную катушку. Поэтому размер картинок с молнией не
очень большой, да и сжатие высокое. Пример и так много занимает. Поэтому
конечный результат зависит от вас. Кстати, вы можете заменить картинки
на свои, добавить больше кадров и изменить пару цифр в программе. И
убедиться, что такой способ самый быстрый и не менее красивый чем любые
другие. Молнию можно нарисовать в Photoshop. А можно надыбать плагин для
Макса и нагенерировать хоть тысячу всяких разных молний.
Кстати, обратите внимание на зеркало реализованное в
демонстрационной программе. А именно на ту часть, на которую смотрит в
самом начале камера. Чем то смахивает на эффект в Дум3, кажется третий
уровень, точно не помню. Поэтому рекомендую взять эффект на вооружение:


Ну и напоследок хотелось бы не обидеть вершинное
освещение средствами OpenGL. Хоть и старая это тема, да и восемь
источников света уже не куда не катят. Но все-таки ее можно
использовать, для быстрых динамичных сцен вполне будет неплохо.
Если вы хотите сделать вашу сцену более красивой в плане
освещения, то добавьте к источникам света блики. К оружию можно добавить
клевую молнию - быструю и красивую. А от взрывов сделайте динамическое
освещение.

Одним словом - нужно жить красиво и халявно.

Если есть комментарии, пишите terror_ext@mail.ru
Всем спасибо за внимание.
P.s.

Кто в демке найдет Easter egg, тот клевый парень.
При создании статьи были использованы следующие ресурсы:

1. Звуковая библиотека fmod - http://www.fmod.org
2. Библиотека скелетной анимации - http://cal3d.sourceforge.net
3. Текстуры взяты из UT2003.
Для компиляции исходного кода программы вам понадобится заголовочные файлы Cal3d и cal3d.lib.
Исходный и исполняемый код примера к статье: 20030921.zip (815 кБ).

Источник -> gamedev.ru
 
KarasevEtДата: Четверг, 29.07.2021, 19:01 | Сообщение # 2
Группа: Пользователи
Сообщений: 2
Статус: Оффлайн
http://divecenter.in.ua/content/view/10/11/
https://www.alfoot.net/arhiv/season-2015.html
http://www.olimpshop.ru/price/
https://barcaman.ru/publ/1-1-0-148
http://2b3.ru/podvodnye-obitateli/akuly/kitovaya-akula/
 
Форум » Прочее » Общение на любые темы » Световые эффекты в OpenGL
  • Страница 1 из 1
  • 1
Поиск: