Принципы освещения
Что влияет
Начну с самого начала а подробности расскажу чуть ниже.
"Излучателями" света служат источники света (в .mdl это Light):
Light "Light1" {
ObjectId 1,
Omnidirectional, // Тип источника
static Visibility 1, // Видимость источника
static AttenuationStart 80.0, // Дистанция начала затухания (не работает)
static AttenuationEnd 200.0, // Дистанция конца затухания (не работает)
static Intensity 15.0, // Интенсивность направленного света
static Color { 1.0, 0.5, 0.0 }, // Цвет направленного света
static AmbIntensity 1.5, // Интенсивность окружающего света
static AmbColor { 1.0, 0.0, 0.0 }, // Цвет окружающего света
}
"Поглотителями" света служат единичные вектора именуемые нормалями (в .mdl это Normals):
Geoset {
Vertices 4 { // Вершины
{ 64.0, 64.0, 0.0 },
{ -64.0, 64.0, 0.0 },
{ 64.0, -64.0, 0.0 },
{ -64.0, -64.0, 0.0 }
},
Normals 4 { // Каждая из нормалей ниже соответствует вершине Vertices выше
{ 0.0, 0.0, 1.0 }, // X Y Z , длина вектора равна 1
{ 0.0, 0.0, 1.0 },
{ 0.0, 0.0, 1.0 },
{ 0.0, 0.0, 1.0 }
}
// ...
}
Что такое грань, вершина и ребро. Для справки.
Взаимодействие источников света и нормалей в итоге дает то самое освещение.
Источники света
Выше были вкратце описаны поля источников света, а сейчас подробнее.
Тип источника - то как свет будет распространятся в пространстве, в варике их всего 3:
- Omnidirectional - Всенаправленный источник. Лучи света исходят из одной конкретной точки сразу во всех направлениях. Имитирует всякие фонари, лампочки, костры и т.д.
- Directional - Направленный источник. Лучи света исходят в одном направлении под определенным углом, не имеет определенной точки излучения и работает на всю карту. Чтобы указать направление света нужно повернуть сам источник с помощью Rotation, изначально он обращен вниз (0, 0, -1) . Имитирует свет от солнца, луны, звезд. Предполагается использовать только в моделях глобального освещения.
- Ambient - Окружающий источник. "Лучи" тут вообще как концепт не используются, одинаково освещает объекты вне зависимости от того как они ориентированы в пространстве и работает на всю карту. Предполагается использовать только в моделях глобального освещения.
Visibility - Видимость источника. Принимает только значения 1 или 0, т.е. видим или не видим.
AttenuationStart и AttenuationEnd - СРАЗУ СКАЖУ ЧТО ЭТИ ПОЛЯ НЕ РАБОТАЮТ. По идее должны были означать дистанцию начала и конца затухания света соответственно для типа Omnidirectional (для остальных типов не имело бы смысла т.к. они не имеют местоположения; отсюда например следует что у этих типов интенсивность обычно стоит ~ от 0,3 до 1,2 - не нужно использовать большие значения т.к. они не затухают), т.е. потерю интенсивности с расстоянием, но по какой-то причине не были реализованы (?). Как предполагалось:
Удаленные объекты становятся тусклее.
По дефолту у всех моделей освещения вара стоят значения 80 и 200, это потому что
Это унаследованные из 3дмакса параметры Attenuation. Значения 80 и 200 взялись не из ниоткуда, это значения по умолчанию, которые вписывает макс в эти поля при добавлении нового источника света в сцену.
(комментарий). Предполагаю что остались по дефолту а дальше на них просто хер забили.
Пытался понять как же работает затухание света для всенаправленного источника...
Как вы можете проверить сами, если увеличивать интенсивность света то и расстояние на которое он освещает тоже увеличивается, допустим указав 1000000 можно всю карту осветить (т.е. как понятно вот эти вот 80 и 200 вообще ничего не решают).
Пришел к выводу что либо там используется Квадратичное затухание (Quadratic Attenuation) либо его производные, для 2002 года это были самые распространенные методы. Использует три коэффициента, которые управляют затуханием света на разных расстояниях от источника. Эти коэффициенты позволяют настроить интенсивность света таким образом, чтобы она уменьшалась более реалистично по мере увеличения расстояния.
Как вы можете проверить сами, если увеличивать интенсивность света то и расстояние на которое он освещает тоже увеличивается, допустим указав 1000000 можно всю карту осветить (т.е. как понятно вот эти вот 80 и 200 вообще ничего не решают).
Пришел к выводу что либо там используется Квадратичное затухание (Quadratic Attenuation) либо его производные, для 2002 года это были самые распространенные методы. Использует три коэффициента, которые управляют затуханием света на разных расстояниях от источника. Эти коэффициенты позволяют настроить интенсивность света таким образом, чтобы она уменьшалась более реалистично по мере увеличения расстояния.
I' = I/(a + b*d + c*d^2)
I' — интенсивность света в вершине,
I — интенсивность источника света,
d — расстояние от источника света до вершины,
a b c — константный, линейный и квадратичный коэффициенты соответственно.
Вот эти вот a b c не известны, кажется какие-то глобальные константы. Ясно одно - чем больше интенсивность света тем большую область он будет освещать.
I — интенсивность источника света,
d — расстояние от источника света до вершины,
a b c — константный, линейный и квадратичный коэффициенты соответственно.
Вот эти вот a b c не известны, кажется какие-то глобальные константы. Ясно одно - чем больше интенсивность света тем большую область он будет освещать.
Intensity и Color - Начальная интенсивность и цвет всенаправленного/направленного света. Цвет содержит 3 RGB компоненты диапазоном от 0 до 1.
AmbIntensity и AmbColor - Начальная интенсивность и цвет окружающего света. Цвет содержит 3 RGB компоненты диапазоном от 0 до 1.
AmbIntensity и AmbColor - Начальная интенсивность и цвет окружающего света. Цвет содержит 3 RGB компоненты диапазоном от 0 до 1.
Вне зависимости от того какой был выбран Тип источника, значения Intensity и Color будут влиять на всенаправленный/направленный свет а значения AmbIntensity и AmbColor на окружающий свет. При этом у типа Omnidirectional обе интенсивности будут затухать с расстоянием.
На счет анимирования источника света - те поля где указано static перед их именем, могут быть анимированы. Можно сделать так чтобы источник света был видим в одних анимациях и небыл виден в других, плавно/резко менять цвет или интенсивность. Подробнее про способы перехода можно прочесть тут, а про анимацию света тут.
Нормали
Как известно модели состоят из граней-треугольников, каждый из которых в свою очередь состоит из трех вершин соединенных между собой. Каждая такая вершина имеет собственную нормаль (X, Y, Z), эти вектора еще называют нормалями вершин (vertex normals); вообще есть еще нормали граней (face normal) и нормали рёбер (edge normal) но хрен с ними.
Так вот именно нормаль определяет как свет будет ложится на вершину.
Так вот именно нормаль определяет как свет будет ложится на вершину.
Нормаль - это единичный вектор (квадратный корень из суммы квадратов его координат равен 1) определяющий ориентацию лицевой грани поверхности. Зачастую они перпендикулярны поверхности, но если нам нужно показать что модель имеет гладкие края а не ребристые то используют усреднение нормалей у смежных вершин чтобы свет ложился так будто углов там нет.
Пример затенения граней по Фонгу (Phong shading):
Вот эти вот желтые линии это нормали.
Пример затенения граней по Фонгу (Phong shading):
Вот эти вот желтые линии это нормали.
У смежных вершин нормали перпендикулярны своим граням, поэтому идет такой резкий переход затенения. Такие нормали еще называют жесткими нормалями.
Конкретно в варике используется метод затенения граней по Гуро (Gouraud shading) - самый распространенный для 2002 года. В то время так же использовалось Плоское затенение (Flat shading) но это прям вообще древняя тема.
Узнаете красавца по центру?
Освещение рассчитывается для каждой вершины, а затем цвет интерполируется по всей поверхности грани. Если по порядку то суть такова:
Есть вершина грани с координатами P.x P.y P.z и нормалью N.x N.y N.z
Есть источник света с координатами S.x S.y S.z и интенсивностью I
Есть источник света с координатами S.x S.y S.z и интенсивностью I
- Вычисляем вектор света L (L.x L.y L.z) так чтобы он был направлен от вершины к источнику света
L = (S.x - P.x, S.y - P.y, S.z - P.z)
- Нормализуем вектор света - делим его составляющие на его длину
|L| = √(L.x^2 + L.y^2 + L.z^2)
Lnorm = L/|L|
Lnorm = L/|L|
- Находим косинус угла между нормалью N и вектором света Lnorm учитывая тот факт что их длины равны 1
cosθ = N⋅Lnorm = N.x*Lnorm.x + N.y*Lnorm.y + N.z*Lnorm.z
cosθ может получится и отрицательным, это означает что угол между векторами больше 90° - в этом случае грань обращена к свету тыльной стороной, тогда cosθ просто зануляем.
- Получаем освещенность E умножая cosθ на формулу которую я писал разделом выше
E = I*cosθ/(a + b*|L| + c*|L|^2)
Цвет
Поскольку у света есть цвет излучения, для каждой компоненты {L.r, L.g, L.b} света рассчитаем модификатор цвета умножив его на освещенность в вершине:
M.r = E*L.r
M.g = E*L.g
M.b = E*L.b
M.r = E*L.r
M.g = E*L.g
M.b = E*L.b
Такие M считаются для каждого из источников света воздействующих на вершину. Тогда итоговым модификатором цветового канала вершины является минимум между единицей и суммой каждой из освещенностей:
M.r = min(1, M1.r + M2.r + ... + Mn.r)
M.g = min(1, M1.g + M2.g + ... + Mn.g)
M.b = min(1, M1.b + M2.b + ... + Mn.b)
M.r = min(1, M1.r + M2.r + ... + Mn.r)
M.g = min(1, M1.g + M2.g + ... + Mn.g)
M.b = min(1, M1.b + M2.b + ... + Mn.b)
Это один из методов тонального отображения - в варике как и во многих играх того времени использовали просто метод отсечения (Clamping) как в формуле выше, а так их дохрена. Конкретно в варе сделано так чтобы итоговые значения цветовых каналов вершины после расчета освещения не превышали значений цветовых каналов пикселя текстуры, т.е. M будет содержать значения от 0 до 1.
После чего наконец-то получаем цвет вершины после воздействия света, умножив каждую из ее цветовых компонент {P.r, P.g, P.b} на модификатор M:
P.r = P.r*M.r
P.g = P.g*M.g
P.b = P.b*M.b
P.r = P.r*M.r
P.g = P.g*M.g
P.b = P.b*M.b
Итоговый цвет в вершинах билинейно интерполируется по всей поверхности грани.
Как результат - нереальное говно:
Из-за того что используется метод затенения Гуро а ландшафт состоит из больших треугольников получается вот такое уродство.
Заключение
Тут хотел подытожить и добавить пару замечаний:
- Используйте тип Omnidirectional для локальных источников света, а Directional и Ambient для глобальных.
- Для управления направления света Directional источников используйте Rotation.
- Не трогайте AttenuationStart и AttenuationEnd - оставьте их 80 и 200 (где-то читал что могут быть баги с пропаданием света если будут несколько источников с разными значениями затухания).
- Совмещайте в одном и том же источнике Intensity/Color и AmbIntensity/AmbColor когда необходимо.
- Меняйте радиус освещения путем изменения Intensity/AmbIntensity.
- Еще один способ изменить интенсивность - пропорционально изменить RGB значения цвета света Color/AmbColor.
- Если используете Omnidirectional источники с большой интенсивностью (т.е. светят далеко) то не забудьте увеличить Bounds Radius модели чтобы свет не пропадал когда модель вылазит за камеру.
- Слишком большие грани могут привести к неверному отображению затенения (особенно если в карте освещение это ключевая составляющая), поэтому рекомендуется разбивать большие грани на несколько мелких.
- Старайтесь не использовать много локальных источников в одном месте, чревато багами, замените их меньшим кол-вом источников с большей интенсивностью либо замените направленное освещение на окружающее.
- (!) При выделении модели с источником освещения, в центре карты создастся копия источника, осветив центр карты, это происходит из-за отрисовки портрета модели. Если модель с источником освещения выделяема в игре, лучше сделать ей отдельную модельку портрета без источников освещения.
- Возможно (но это не точно) что баги могут вылезать при использовании множества источников с разной интенсивностью.
Типо есть Omni с Color 1 0 0 и AmbColor 0 1 0
Тогда та часть модели которая не попадает под прямой свет будет зелёная (0 1 0) а та часть которая попадает будет жёлтой (1 0 0 + 0 1 0 = 1 1 0).
А можно вообще помаяться и замутить множество направленных источников с разных сторон, но с меньшей интенсивностью.
Можно и амбиент, но тогда в нижнем источнике лучей смысла нет, ведь снизу у нас второе солнце не светит)
Но свет отражается от поверхностей и падает на ту часть объекта которая не попадает под прямое излучение. Вот это вот и можно реализовать низко интенсивным направленным источником.
Ред. ScorpioT1000
Ред. EugeAl
у вторых у всех есть недостаток - они используют текстуры дыма или облаков, что смотрится не очень. Думаю, нужно будет с этим покончить и запилить текстуру из огня, тоже для партиклов additive, чтоб можно было наконец делать нормальное пламя