Если вы посмотрите на общее определение модели отражения Фонга, обычно это суммирование по всем источникам света в виде:
Σ kd * (L . N) * i + ks * (R . V)^a * i
В этом случае kd
и ks
- диффузные и зеркальные константы, L
, N
и R
- соответствующие векторы, a
- это блеск, а i
- интенсивность текущий входящий свет. Ваша версия немного отличается, так как вы можете переписать ее так, как вы делали, разделив суммирование и переместив константы, но это не так распространенный способ сделать это:
kd * Σ ((L . N) * i) + ks * Σ ((R . V)^a * i)
Причина, по которой это делается не так часто, связана с тем, как работает общее уравнение рендеринга, которое обычно имеет форму интеграла по полушарию в точке:
Lo(wo) = Le(wo) + ∫ f(wi, wo) * (wi . n) * Li(wi) dwi
В этом случае Lo
- исходящее излучение в направлении wo
, Le
- излучающий вклад в направлении, f
- BRDF, n
- нормаль поверхности, Li
- это вклад входящего сияния во входящем направлении wi
(то, что интегрируется в). На практике это реализовано в виде суммирования, еще раз показывающего, что вклад освещения в точке в направлении является типом взвешенной суммы каждого отдельного расчета освещения в направлении. В случае простого рендера с точечными источниками света, такими как ваш, это просто сумма вклада каждого источника света, поскольку предполагается, что свет может исходить только от источника света, а не от самой среды. На самом деле это не проблема, но если вы когда-нибудь планируете реализовать более сложную модель освещения, вам придется немного переписать структуру вашей программы.
Основная проблема, однако, я подозреваю, заключается просто в том, что освещение в сцене с трассировкой лучей обычно выполняется в линейном пространстве без границ, что означает, что свет не всегда будет оставаться в диапазоне 0-1, как вы наблюдали. Огни могут быть представлены со значениями, намного превышающими 1, чтобы отличить, например, солнце от простого настольного освещения, или в вашем случае, вероятно, комбинация множества небольших огней приводит к тому, что значения на поверхности будут намного больше 1 при объединении. Хотя это не является проблемой во время рендеринга (и на самом деле должно быть так для получения правильных результатов), это проблема, когда вы решаете окончательно представить изображение, поскольку мониторы принимают только 8 бит или в случае более современного дисплея HDR устройства, 10-битный цвет для каждого канала, что означает, что вы должны представить весь диапазон яркости плавающей точки в вашей сцене как гораздо более ограниченный целочисленный диапазон.
Этот процесс перехода от HDR к LDR обычно выполняется с помощью тонального отображения, которое фактически представляет собой операцию по сжатию диапазона значений в нечто «презентабельное» «интеллектуальным» способом, каким бы он ни был. В картографирование тонов могут быть включены различные факторы, такие как экспозиция, которая может быть получена даже из физически смоделированных свойств камеры, таких как выдержка, диафрагма и ISO (как мы привыкли к тому, как камеры фиксируют мир, как это видно в фильмах и фотографиях), или может быть грубо аппроксимированным, как это делают многие видеоигры, или это можно полностью игнорировать. Кроме того, кривая и «стиль» операции тонального отображения полностью субъективны, обычно выбираются на основе того, что выглядит соответствующим для рассматриваемого контента, или выбираются специально художниками в случае чего-то, например, фильма или видеоигры, то есть вы можете по большей части просто выберите то, что вам больше нравится, так как нет единственно правильного ответа (опять же, он обычно основан на показе пленки в форме кривой S из-за широкого использования камер в медиа).
Даже после того, как диапазон значений был преобразован во что-то более приемлемое для вывода на дисплей, однако, цветовое пространство может все еще быть неправильным, и в зависимости от того, как вы записываете его на устройство отображения, может потребоваться коррекция гаммы путем установки значения через OETF (оптоэлектронная передаточная функция) для кодирования выходного электронного сигнала для монитора. Как правило, вам не нужно беспокоиться о цветовом пространстве, поскольку большинство людей все работают на мониторах в sRGB (небольшой вариант Рекомендации 709) и используют его непосредственно в своих приложениях, поэтому, если вы не сделаете все возможное, чтобы создать цветовое пространство в вашем трассировщике лучей что-то другое, тогда вам не о чем беспокоиться. С другой стороны, гамма-коррекция обычно должна выполняться как кадровый буфер по умолчанию в таких API, как OpenGL, Direct3D или Vulkan, обычно уже в гамма-пространстве (в то время как математика освещения, как упоминалось ранее, выполняется в линейном пространстве), хотя, если вы выводите к чему-то, например к изображению, тогда это может не понадобиться в зависимости от формата.
Тем не менее, в общем, вам просто нужно применить оператор отображения тонов и, возможно, гамма-коррекцию, чтобы получить окончательный вывод цвета, чтобы получить что-то, что выглядит достаточно корректно. Если вам нужен быстрый и грязный, вы можете попробовать x / (x + 1)
(также известный как тональное отображение Рейнхарда), где x будет выводом из трассировки лучей. Вы можете умножить вход для этой функции на некоторую произвольную константу, а также для простой регулировки «экспозиции», если выходной сигнал слишком темный. Наконец, если ваше устройство вывода ожидает чего-то в гамма-пространстве, вы можете взять тональный вывод и применить к нему функцию x^(1.0 / 2.2)
(обратите внимание, что это небольшое упрощение правильного sRGB OETF, но его можно использовать до тех пор, пока вы имейте это в виду), чтобы поместить его в гамма-пространство, хотя, опять же, если вы выводите изображение, это обычно не требуется, но все же следует помнить о чем-то. Следует также отметить, что отображение тонов обычно выводится в диапазоне 0-1, поэтому, если вам нужно преобразовать в 8-битные целые числа путем умножения на 255 или любого другого формата, например, выходного изображения, это следует делать после того, как все чем раньше, умножение его ранее ничего не изменит, кроме как сделает сцену намного ярче.
Я также хотел бы упомянуть, планируете ли вы когда-нибудь развивать этот трассировщик лучей в нечто более подробное, например трассировщик, использование модели освещения Фонга будет недостаточным из-за нарушения ожидаемых свойств энергосбережения по уравнению рендеринга. Существует много BRDF, даже относительно простых, основанных на Phong (с небольшими изменениями, чтобы заставить его работать должным образом), поэтому такое изменение не потребует слишком большого количества дополнительного кода, но улучшит визуальную точность рендерера и сделает его более будущим доказательство того, что когда-либо реализовано более сложное поведение.