Всё освещение, которое мы до этого момента использовали, исходило из единственного источника света, являвшегося одинокой точкой в пространстве. Это дает хорошие результаты, но в реальном мире у нас есть несколько типов света, действующих на объект по-разному. Источник света, который отбрасывает свет на объекты, называется light caster. На этом уроке мы обсудим несколько различных типов light caster-ов. Обучение моделированию различных источников света — это еще одна деталь в вашем наборе инструментов для дальнейшего усовершенствования окружения сцены.
Сначала мы обсудим направленный свет, затем точечный свет, который, по сути, является продолжением того, что у нас было раньше, и, наконец, рассмотрим прожекторы. На следующем уроке мы объединим несколько различных типов света в одной сцене.
Направленный свет
Когда источник света находится далеко, лучи света, исходящие от него, идут почти параллельно друг другу. Можно считать, что все световые лучи исходят из одного и того же направления, независимо от того, где находится объект и/или зритель. Свет от источника света, расположенного бесконечно далеко от объекта, называют направленным светом (англ. «directional light»), поскольку все его световые лучи, падающие на объект, имеют одно и то же направление; оно не зависит от местоположения источника света.
Прекрасным примером направленного источника света является Солнце в том виде, в каком мы его знаем. Солнце не удалено бесконечно далеко от нас, но оно настолько далеко, что в наших расчетах освещения мы можем воспринимать его как бесконечно удаленную точку. Все световые лучи, исходящие от Солнца, моделируются как параллельные световые лучи, в чем вы можете убедиться, посмотрев на следующее изображение:
Поскольку все световые лучи параллельны друг другу, не имеет значения, как положение каждого объекта соотносится с положением источника света, поскольку направление света для каждого объекта в сцене остается одинаковым. Так как вектор направления света остается неизменным, то и расчеты освещения для каждого объекта в сцене будут одинаковыми.
Можно смоделировать направленный свет, определив вектор направления света вместо вектора положения источника света. Шейдерные вычисления остаются в основном теми же за исключением того, что на этот раз мы непосредственно используем вектор направления света direction
вместо вычисления вектора lightDir
через вектор положения источника света position
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
struct Light { // vec3 position; // больше не требуется, т.к. теперь используется направленный свет vec3 direction; vec3 ambient; vec3 diffuse; vec3 specular; }; ... void main() { vec3 lightDir = normalize(-light.direction); ... } |
Заметьте, что мы используем вектор направления -light.direction
(с отрицательным знаком). Расчеты освещения, которые мы делали до сих пор, предполагают, что в качестве направления света используется направление от фрагмента к источнику света, но люди обычно предпочитают указывать данное направление в глобальном смысле, т.е. от источника света к фрагменту. Поэтому мы должны взять вектор направления света с противоположным знаком, чтобы изменить его направление; теперь это — вектор направления, указывающий на источник света. Кроме того, обязательно нормализуйте его, так как неразумно предполагать, что входной вектор является единичным.
Затем полученный вектор lightDir
используется, как и прежде, в вычислениях рассеянной и отраженной составляющих.
Чтобы наглядно продемонстрировать, что направленный свет оказывает одинаковое воздействие на различные объекты, мы обратимся к сцене «контейнерной вечеринки», указанной в конце Урока №8. Системы координат в OpenGL. В случае если вы её пропустили, мы определили 10 различных позиций контейнера:
1 2 3 4 5 6 7 8 9 10 11 12 |
glm::vec3 cubePositions[] = { glm::vec3( 0.0f, 0.0f, 0.0f), glm::vec3( 2.0f, 5.0f, -15.0f), glm::vec3(-1.5f, -2.2f, -2.5f), glm::vec3(-3.8f, -2.0f, -12.3f), glm::vec3( 2.4f, -0.4f, -3.5f), glm::vec3(-1.7f, 3.0f, -7.5f), glm::vec3( 1.3f, -2.0f, -2.5f), glm::vec3( 1.5f, 2.0f, -2.5f), glm::vec3( 1.5f, 0.2f, -1.5f), glm::vec3(-1.3f, 1.0f, -1.5f) }; |
И сгенерировали для каждого контейнера свою матрицу модели, где каждая матрица содержит соответствующие преобразования из локального пространства в глобальное/мировое:
1 2 3 4 5 6 7 8 9 10 |
for(unsigned int i = 0; i < 10; i++) { glm::mat4 model = glm::mat4(1.0f); model = glm::translate(model, cubePositions[i]); float angle = 20.0f * i; model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f)); lightingShader.setMat4("model", model); glDrawArrays(GL_TRIANGLES, 0, 36); } |
Кроме того, не забудьте в явном виде указать направление источника света (обратите внимание, что мы определяем направление как направление от источника света):
1 |
lightingShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f); |
Отступление
Мы уже не первый раз передаем векторы положения и направления света как параметры типа vec3, но некоторые люди предпочитают, чтобы все векторы были определены как vec4. При определении векторов положения как vec4 важно задать значение 1.0
для их w-компоненты, чтобы потом к ним корректно применялись операции трансляции и проекции. Однако, при определении вектора направления как vec4 мы не хотим, чтобы применение операции трансляции подействовало и на него (поскольку все они по своей сути просто представляют направления, не более того), поэтому мы определяем их w-компоненту, равную 0.0
.
Тогда векторы направления можно представить в следующем виде: vec4(-0.2f, -1.0f, -0.3f, 0.0f)
. В данном случае подобное представление можно использовать для простой проверки типов света: если w-компонента равна 1.0
, то мы работаем с вектором положения света, а если w-компонента равна 0.0
, то мы работаем с вектором направления света; поэтому, исходя из вышесказанного, можно скорректировать свои вычисления следующим образом:
1 2 3 4 |
if(lightVector.w == 0.0) // примечание: будьте осторожны с ошибками типа с плавающей точкой // Выполняем расчеты направленного света else if(lightVector.w == 1.0) // Выполняем расчеты света, используя его положение (как на предыдущих уроках) |
Забавный факт: Именно так старый OpenGL (ограниченной функциональности) определял, является ли источник света направленным светом или позиционным источником света, и на основе этого регулировал свое освещение.
Направленный свет (продолжение)
Если бы вы сейчас скомпилировали приложение и пролетели сквозь сцену с помощью камеры, то это выглядело бы так, как будто есть солнцеподобный источник света, отбрасывающий свет на все объекты. Вы видите, как рассеянный и отраженный компоненты реагируют так, как если бы где-то в небе был источник света?
GitHub / Урок №14. Источники света в OpenGL — Исходный код №1
Точечные источники света
Направленный свет отлично подходит для использования в качестве глобального освещения, озаряющего всю сцену, но при этом, мы также хотим, чтобы по всей сцене были разбросаны несколько точечных источников освещения (англ. «point lights»). Точечный свет — это источник света с заданным где-то в глобальном пространстве положением, светящий во всех направлениях, при этом его лучи света исчезают с увеличением расстояния. К таким точечным источникам света можно отнести обычные лампочки или факелы.
На предыдущих уроках мы имели дело с упрощенным точечным светом. У нас был источник света в заданном положении, который распространял свет во всех направлениях от заданного положения. Однако источник света, который мы тогда определили, имитировал световые лучи, которые никогда не исчезали с увеличением расстояния, таким образом, делая его похожим на источник света чрезвычайно большой силы. В большинстве 3D-приложений мы хотели бы имитировать источник света, который освещал бы не всю сцену, а только область, близкую к источнику света.
Если бы мы добавили к сцене освещения штук 10 контейнеров из предыдущих уроков, то заметили бы, что контейнер, находящийся вдалеке, освещен с той же интенсивностью, что и контейнер, расположенный непосредственно перед источником света; у нас пока нет никакой логической модели, которая бы уменьшала освещенность объектов в зависимости от их расстояния до источника света. Мы хотим, чтобы контейнер, расположенный на заднем плане, имел лишь небольшое освещение по сравнению с контейнерами, находящимися рядом с источником света.
Затухание
Эффект уменьшения интенсивности света с увеличением расстояния, которое проходит световой луч, называется затуханием. Один из способов уменьшить интенсивность света на расстоянии — это просто использовать линейное уравнение. Такое уравнение будет линейно уменьшать интенсивность света, в зависимости от расстояния до объекта, тем самым гарантируя, что удаленные объекты будут менее яркими. Однако такая линейная функция имеет тенденцию выглядеть немного фальшивой. В реальном мире свет, когда вы стоите рядом, обычно выглядит довольно ярким, но его яркость с увеличением расстояния быстро уменьшается; затем оставшаяся интенсивность света будет убывать довольно медленно. Таким образом, мы нуждаемся в другом уравнении для более правдоподобного уменьшения интенсивности света.
К счастью, некоторые умные люди уже разобрались с этим до нас. Следующая формула вычисляет величину затухания на основе расстояния от фрагмента до источника света, которое позже мы умножаем на вектор интенсивности света:
где d
— это расстояние от фрагмента до источника света. Далее, для вычисления величины затухания, мы определяем 3 (настраиваемых) параметра: константа Kc
, линейный член Kl
и квадратичный член Kq
.
Константу обычно оставляют равной 1
для того, чтобы знаменатель никогда не был меньше 1
, поскольку в противном случае он бы увеличивал интенсивность света в зависимости от расстояния, а это не тот эффект, который нам нужен.
Линейный член умножается на значение расстояния, тем самым линейно уменьшая интенсивность.
Квадратичный член умножается на квадрат расстояния и задает для источника света квадратичное уменьшение интенсивности. Квадратичный член будет тем менее значимым по сравнению с линейным членом, чем меньше расстояние, и будет намного больше по мере роста расстояния.
Из-за квадратичного слагаемого свет будет уменьшаться линейным образом до тех пор, пока расстояние не станет достаточно большим, после чего квадратичный член превзойдет линейный, и тогда интенсивность света будет уменьшаться намного быстрее. В результате получается, что свет имеет довольно большую интенсивность на близком расстоянии, но с увеличением расстояния быстро теряет свою яркость, а под конец будет терять её более плавно. На следующем графике показан пример такого затухания:
Вы можете видеть, что свет имеет самую высокую интенсивность, когда расстояние довольно мало, но как только оно начинает расти, интенсивность света значительно уменьшается и медленно достигает 0 примерно на расстоянии 100. Это именно то, чего мы хотим.
Выбор правильных значений
Но какие значения мы должны указать для этих 3 слагаемых? Установка правильных значений зависит от многих факторов: окружающей среды, расстояния, которое вы хотите осветить, типа света и т.д. В большинстве случаев это просто вопрос опыта и небольшого количества настроек. В следующей таблице показаны некоторые значения, которые данные слагаемые могут принимать для моделирования реалистичного (своего рода) источника света, охватывающего определенный радиус (расстояние). В первом столбце указывается расстояние, на которое будет светить свет, а в следующих столбцах — параметры затухания этого света. Данные значения, любезно предоставленные Ogre3D wiki, являются хорошими отправными точками для большинства источников света:
Расстояние | Константа | Линейный член | Квадратичный член |
7 | 1.0 | 0.7 | 1.8 |
13 | 1.0 | 0.35 | 0.44 |
20 | 1.0 | 0.22 | 0.20 |
32 | 1.0 | 0.14 | 0.07 |
50 | 1.0 | 0.09 | 0.032 |
65 | 1.0 | 0.07 | 0.017 |
100 | 1.0 | 0.045 | 0.0075 |
160 | 1.0 | 0.027 | 0.0028 |
200 | 1.0 | 0.022 | 0.0019 |
325 | 1.0 | 0.014 | 0.0007 |
600 | 1.0 | 0.007 | 0.0002 |
3250 | 1.0 | 0.0014 | 0.000007 |
Как вы можете видеть, значение постоянного члена Kc
для всех вариантов таблицы установлено на уровне 1.0
. Линейный член Kl
для покрытия больших расстояний задается довольно маленьким значением, а величина квадратичного члена Kq
задается при этом еще меньшим значением. Попробуйте немного поэкспериментировать с этими значениями, чтобы увидеть их эффект в вашей реализации. В нашем окружении расстояния от 32 до 100 обычно достаточно для большинства источников света.
Реализация затухания
Для реализации затухания нам понадобятся 3 дополнительные переменные во фрагментном шейдере: константа, линейный и квадратичный члены уравнения. Лучше всего их сохранить в структуре Light
, которую мы определили ранее. Обратите внимание, что нам нужно снова вычислить lightDir
, используя переменную position
, поскольку это точечный свет (как мы делали на предыдущем уроке), а не направленный свет.
1 2 3 4 5 6 7 8 9 10 11 |
struct Light { vec3 position; vec3 ambient; vec3 diffuse; vec3 specular; float constant; float linear; float quadratic; }; |
Затем, в нашем приложении, мы устанавливаем значения вышеприведенных переменных. Мы хотим, чтобы свет покрывал расстояние 50, поэтому будем использовать значения из таблицы для соответствующих переменных: константы, линейного и квадратичного членов.
1 2 3 |
lightingShader.setFloat("light.constant", 1.0f); lightingShader.setFloat("light.linear", 0.09f); lightingShader.setFloat("light.quadratic", 0.032f); |
Реализация затухания во фрагментном шейдере относительно проста: мы просто на основе уравнения вычисляем значение затухания и умножаем его на фоновый, рассеянный и отраженный компоненты.
Однако, чтобы уравнение работало, нам нужно вычислить расстояние до источника света. Вспомните, каким образом вычисляется длина вектора? Мы можем получить значение переменной расстояния, вычисляя вектор разности между фрагментом и источником света, и взять длину данного результирующего вектора. Для этой цели воспользуемся встроенной в GLSL функцией вычисления длины length():
1 2 |
float distance = length(light.position - FragPos); float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); |
Затем мы используем вычисленное значение затухания в расчетах освещения, умножая его на фоновый, рассеянный и отраженный цвета:
1 2 3 |
ambient *= attenuation; diffuse *= attenuation; specular *= attenuation; |
Примечание: Мы могли бы и не трогать фоновый компонент, чтобы фоновое освещение не уменьшалось с увеличением расстояния, но если мы будем использовать более одного источника света, то все фоновые компоненты начнут складываться. В этом случае необходимо будет ослабить внешнее освещение. Просто поэкспериментируйте с данными значениями для того, чтобы лучше понять, что подходит для вашего окружения.
Если бы мы запустили приложение, то получили бы что-то вроде следующего:
GitHub / Урок №14. Источники света в OpenGL — Исходный код №2
Мы можем видеть, что прямо сейчас освещены только передние контейнеры, а ближайший из них — самый яркий. Ящики в задней части сцены не освещены вообще, так как они находятся слишком далеко от источника света.
Таким образом, точечный свет является светом с настраиваемыми параметрами местоположения и затухания, применяемыми к расчетам его освещения. Еще одним типом света в нашей копилке осветительного арсенала стало больше.
Прожектор
Прожектор — это источник света, расположенный где-то в окружающем нас пространстве, который вместо того, чтобы испускать световые лучи во всех направлениях, испускает их только в определенном направлении. В результате освещаются только те объекты, которые находятся в определенном радиусе от направления света прожектора, а всё остальное остается тёмным. Хорошим примером прожектора может служить уличный фонарь или карманный фонарик.
В OpenGL прожектор задается положением в мировом пространстве, направлением и углом отсечки, который задает радиус прожектора. Для каждого фрагмента мы вычисляем, находится ли фрагмент между направлениями отсечки прожектора (т.е. в его световом конусе). Если находится, то мы освещаем фрагмент соответствующим образом. Следующее изображение даст вам лучшее представление о том, как работает прожектор:
Рассмотрим этот рисунок детально:
LightDir
— это вектор, указывающий направление от фрагмента к источнику света.
SpotDir
— это направление, вдоль которого направлен прожектор.
ϕ
(Фи) — это угол отсечки, определяющий радиус прожектора. Всё, что находится за пределами этого угла, не освещается прожектором.
θ
(Тета) — это угол между вектором LightDir
и вектором SpotDir
. Значение угла θ
должно быть меньше значения угла ϕ
, чтобы находиться в границах прожектора.
Итак, нам нужно вычислить скалярное произведение (возвращающее косинус угла между двумя единичными векторами) между вектором LightDir
и вектором SpotDir
и сравнить его с углом отсечки ϕ
. Теперь, когда вы понимаете, что такое прожектор, мы создадим его в форме фонарика.
Фонарик
Фонарик — это прожектор, расположенный на позиции наблюдателя и обычно направленный в прямом направлении относительно точки наблюдения. По своей сути фонарик — это обычный прожектор, но его положение и направление постоянно обновляются, в зависимости от положения и ориентации наблюдателя.
Итак, для фрагментного шейдера нам понадобятся значения вектора положения прожектора (для вычисления вектора направления от фрагмента к свету), вектора направления прожектора и угла отсечки. Поместим данные переменные в структуру Light
:
1 2 3 4 5 6 |
struct Light { vec3 position; vec3 direction; float cutOff; ... }; |
Далее мы передаем соответствующие значения в шейдер:
1 2 3 |
lightingShader.setVec3("light.position", camera.Position); lightingShader.setVec3("light.direction", camera.Front); lightingShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f))); |
Как вы можете видеть, мы не устанавливаем значение для угла отсечки, а вычисляем значение косинуса угла и передаем результат во фрагментный шейдер. Причиной этому является то, что во фрагментном шейдере вычисляется скалярное произведение между вектором LightDir
и вектором SpotDir
, и скалярное произведение возвращает значение косинуса угла, а не сам угол; и мы не можем напрямую сравнить угол со значением косинуса угла. Чтобы затем в шейдере получить значение угла, необходимо будет вычислить арккосинус результата скалярного произведения, что является дорогостоящей операцией. Поэтому, чтобы сохранить некоторую производительность, мы заранее вычисляем значение косинуса заданного угла отсечки и передаем этот результат во фрагментный шейдер. Поскольку оба угла теперь представлены в виде значений соответствующих косинусов, мы можем непосредственно сравнивать их без использования дорогостоящих операций.
Теперь нам нужно рассчитать значение угла θ
и сравнить его со значением угла отсечки ϕ
, чтобы определить, находимся ли мы внутри светового конуса прожектора или снаружи его:
1 2 3 4 5 6 7 8 |
float theta = dot(lightDir, normalize(-light.direction)); if(theta > light.cutOff) { // Выполняем вычисления освещения } else // в противном случае, используем ambient-свет, чтобы вне светового конуса прожектора сцена не была полностью тёмной color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0); |
Сначала мы вычисляем скалярное произведение между вектором lightDir
и отрицательным вектором направления (отрицательным, потому что мы хотим, чтобы векторы указывали на источник света, а не от него). Обязательно нормализуйте все соответствующие векторы.
Возможно, вы задаетесь вопросом, почему в условии if(theta > light.cutOff)
используется знак сравнения >
вместо знака <
. Разве угол θ
не должен быть меньше значения угла отсечки света ϕ
, чтобы находиться внутри светового конуса прожектора? Это верно, но не забывайте, что значения углов представлены соответствующими значениями их косинусов: угол в θ
градусов представлен значением косинуса данного угла, равным 1.0
, а угол 90 градусов представлен значением косинуса соответствующего угла, равным 0.0
. Ниже на картинке вы можете это увидеть:
Обратите внимание, что чем ближе значение косинуса к 1.0
, тем меньше его угол. Теперь понятно, почему угол θ
(переменная theta
) должен быть больше, чем угол отсечки ϕ
(light.cutOff
). Значение отсечки в настоящее время установлено как cos(12.5 градусов) = 0.976
, поэтому значения cos(θ)
, лежащие между 0.976
и 1.0
, приведут к тому, что фрагмент будет освещен, находясь внутри светового конуса прожектора.
Запуск приложения приводит к появлению прожектора, который освещает только те фрагменты, которые находятся непосредственно внутри его светового конуса:
GitHub / Урок №14. Источники света в OpenGL — Исходный код №3
Однако он все еще выглядит немного фальшивым в основном потому, что у светового пятна прожектора присутствуют «жесткие» края. Везде, где фрагмент достигает края светового конуса прожектора, его освещенность мгновенно падает до нуля — нет приятного плавного затухания освещенности. Реалистичный же прожектор будет постепенно уменьшать свет вокруг краев своего светового конуса.
Сглаженные/Мягкие края освещения
Чтобы создать эффект плавно очерченного прожектора, необходимо смоделировать прожектор, имеющий внутренний и внешний конусы. Мы можем задать внутренний конус как конус, определенный в предыдущем разделе, но также нам потребуется определить еще и внешний конус, который будет постепенно гасить свет от внутреннего к краям внешнего конуса.
Чтобы создать внешний конус, мы просто определяем другое значение косинуса, которое представляет собой угол между вектором направления прожектора и вектором внешнего конуса. Затем, если фрагмент находится между внутренним и внешним конусом, косинус будет содержать значение интенсивности освещенности, лежащее между значениями 0.0
и 1.0
. Если фрагмент находится внутри внутреннего конуса, то интенсивность его освещенности равна 1.0
, а если фрагмент находится вне внешнего конуса, то интенсивность его освещенности равна 0.0
.
Мы можем вычислить такое значение, используя следующее уравнение:
где ϵ
(эпсилон) — это разность косинусов между углами внутреннего (ϕ
) и внешнего (γ
) конусов (ϵ=ϕ−γ
). Тогда итоговое значение I
будет являться интенсивностью света прожектора в текущем фрагменте.
Немного трудно представить себе, как на самом деле работает эта формула, поэтому давайте исследуем её с несколькими примерами значений:
θ | θ в градусах | φ (внутренняя обрезка) | φ в градусах | γ (внешняя обрезка) | γ в градусах | ε | Ι |
0.87 | 30 | 0.91 | 25 | 0.82 | 35 | 0.91 — 0.82 = 0.09 | (0.87 − 0.82) / 0.09 = 0.56 |
0.9 | 26 | 0.91 | 25 | 0.82 | 35 | 0.91 — 0.82 = 0.09 | (0.9 − 0.82) / 0.09 = 0.89 |
0.97 | 14 | 0.91 | 25 | 0.82 | 35 | 0.91 — 0.82 = 0.09 | (0.97 − 0.82) / 0.09 = 1.67 |
0.83 | 34 | 0.91 | 25 | 0.82 | 35 | 0.91 — 0.82 = 0.09 | (0.83 − 0.82) / 0.09 = 0.11 |
0.64 | 50 | 0.91 | 25 | 0.82 | 35 | 0.91 — 0.82 = 0.09 | (0.64 − 0.82) / 0.09 = -2.0 |
0.966 | 15 | 0.9978 | 12.5 | 0.953 | 17.5 | 0.9978 — 0.953 = 0.0448 | (0.966 − 0.953) / 0.0448 = 0.29 |
Как вы можете видеть, интерполирование значений освещенности выполняется с использованием «внешнего» и «внутреннего» косинусов, зависящих от угла θ
. Если вы все еще не понимаете, что происходит, не волнуйтесь, вы можете просто принять формулу как должное и вернуться сюда позже, когда станете более опытны.
Теперь у нас есть значение интенсивности освещенности, которое является отрицательным, если фрагмент находится вне светового конуса прожектора; больше 1.0
— если фрагмент находится внутри внутреннего конуса; и все оставшиеся значения для случаев, когда фрагменты находятся между кромками внутреннего и внешнего световых конусов. Если мы правильно зафиксируем значения, то нам больше не понадобятся условия if/else во фрагментном шейдере, и мы сможем просто умножить компоненты света на вычисленное значение интенсивности:
1 2 3 4 5 6 7 8 |
float theta = dot(lightDir, normalize(-light.direction)); float epsilon = light.cutOff - light.outerCutOff; float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); ... // Оставляем ambient-составляющую без изменений, тем самым оставляя немного света diffuse *= intensity; specular *= intensity; ... |
Обратите внимание, что мы используем функцию clamp(), которая фиксирует диапазон значений своего первого аргумента между значениями 0.0
и 1.0
. Это гарантирует, что значения интенсивности не окажутся за пределами диапазона [0, 1]
.
Убедитесь, что вы добавили переменную outerCutOff
в структуру Light
и задали в приложении её uniform-значение. Для следующего изображения был использован внутренний угол отсечки 12.5
и внешний угол отсечки 17.5
:
GitHub / Урок №14. Источники света в OpenGL — Исходный код №4
О-о-оо, это уже намного лучше. Такой тип фонарика/прожектора идеально подходит для хоррор-игр.
Поиграйте с внутренними и внешними углами отсечки и попробуйте создать прожектор, который лучше соответствует вашим потребностям.
Упражнения
Попробуйте поэкспериментировать со всеми различными типами света и их фрагментными шейдерами. Попробуйте инвертировать некоторые векторы и/или использовать <
вместо >
. Попытайтесь объяснить получившиеся визуальные эффекты.
Действительно интересный эффект при инвертировании интенсивности. В середине получется круглая тень, а края стандартно освещены.
Приветствую. У меня вопрос: я реализовал фонарик как описывается в уроке, но есть проблема, при передвижении (WASD) позиция фонарика так же меняется, но при попытке повернуться (с пом. мыши) световой пучок поворачивается быстрее чем камера (смещается от центра экрана), так и должно быть?
Добрый день.
Не замечал у себя такого. Какие ОС и IDE/компилятор у вас используются?
Если проблема все еще актуальна, то у тебя наверное свет рассчитывается не в мировых координатах, а в координатах камеры (по крайней мере у меня так было). В таком случае все сделай то же самое, только в мировых координатах, или можешь передать в light.position — vec3(0.0f, 0.0f, 0.0f) (так как позиция камеры есть центром координат), а в light.direction — vec3(0.0f, 0.0f, -1.0f) (все так же из-за того, что наша камера есть центром координат, и когда мы двигаем камеру, то направление в нас должно быть всегда одно и то же, а camera.Front изменяет её).
Если все считается в пространстве view, то обязательно надо домножить на view матрицу, и внимательно смотреть, чтоб cam.Front был c w = 0.0f или vec3.