Артефакты с затенением по фонгу и трассировкой лучей - PullRequest
3 голосов
/ 06 июня 2019

В настоящее время я экспериментирую с различными артефактами при реализации затенения фона на моем трассировщике лучей.

Первый случай возникает, когда я выполняю расчет зеркального освещения так, как я полагаю, прав: добавление вкладаисточники света в совокупности с:

specular_color += light_intensity * std::pow (std::max(0.f,reflected*camera_dir),mat.ns); 

Однако, если я не накапливаю вклад, с помощью

specular_color = light_intensity * std::pow (std::max(0.f,reflected*camera_dir),mat.ns);

я получаю это:

Кажется ближе, но все же с некоторыми артефактами.

Печать некоторых значений, предполагаемых переменной specular_color (то есть объект с плавающей точкой 3), я получаю значения вплоть до

specular_color (200) после: 1534.73 1534.73 1534.73

для пикселей x и y = 200 с добавленным знаком +

без него я получаю:

specular_color (200) после: 0 0 0

Все эти значения с плавающей запятой фиксируются

a [ctr] = min (final_color.blue*255.0f,255.0f);
     a [ctr+1] = min (final_color.green*255.0f,255.0f);
     a [ctr+2] = min (final_color.red*255.0f,255.0f); 

для записи в файл

И final_value равноничего больше, чем:

final_color = diffuse_color * mat.ks + specular_color * mat.kd;

Компоненты specular_color (light_intensity, reflected, camera_dir), кажется, правильны, поскольку используются в других местах без проблем.

Поэтому я был бы признателен за любые предложениягде может быть ошибка и как ее исправить.

Ответы [ 2 ]

3 голосов
/ 06 июня 2019

Если вы посмотрите на общее определение модели отражения Фонга, обычно это суммирование по всем источникам света в виде:

Σ 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 (с небольшими изменениями, чтобы заставить его работать должным образом), поэтому такое изменение не потребует слишком большого количества дополнительного кода, но улучшит визуальную точность рендерера и сделает его более будущим доказательство того, что когда-либо реализовано более сложное поведение.

2 голосов
/ 06 июня 2019

Первое предложение: не представляйте интенсивность источников света, используя 0,0 - 255,0.Используйте от 0,0 до 1,0.Масштабирование и накопление будут работать лучше.Когда пришло время отображать, вы отображаете окончательную интенсивность на значение в пикселях в диапазоне 0 - 255.

Если вы представляете свой «самый яркий» источник света как 255, и ваша сцена имеет один такой источник света, то вы можетеуйти с ним.Но если затем добавить второй источник света, любой данный пиксель может потенциально освещаться обоими источниками света и в итоге получится вдвое ярче, чем самая яркая вещь, которую вы можете изобразить, что в основном и происходит в вашем первом примере - большинство ваших пикселей слишкомЯркий для отображения.

Чтобы нормализовать свет, вам понадобятся дополнительные деления и умножения на number_of_lights * 255.Это становится грязным.Интенсивность масштабируется и накапливается линейно, а значения пикселей - нет.Так что работайте с интенсивностью и в конце преобразуйте в значения пикселей.

Чтобы выполнить сопоставление, у вас есть несколько вариантов.

  1. Найдите самые низкие и самые высокие интенсивности в вашемизображения и используйте линейное отображение для преобразования их в значения пикселей от 0 до 255.

    Например, если ваша самая низкая интенсивность равна 0,1, а ваша самая высокая - 12,6, то вы можете вычислить каждое значение пикселя следующим образом:

    pixel_value = (intensity - 0.1) / (12.6 - 0.1) * 255;
    

    Это не очень реалистично (физически), но достаточно для получения достойных результатов независимо от того, сколько (или мало) света у вас есть на сцене.Вы эффективно делаете грубую «автоэкспозицию».К сожалению, ваши «темные» сцены будут казаться слишком яркими, а ваши яркие сцены могут казаться темными.

  2. Фактическая кривая реакции глаз и пленки на интенсивность света не является линейной.Обычно это s-образная кривая, которая часто может быть аппроксимирована чем-то вроде:

    response = 1 - exp(intensity / exposure);
    pixel_value = 255 * response;  // clamp to 0 - 255
    

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

    Кривые отклика могут быть еще более сложными.У старой школы светочувствительный фильм обладает странными свойствами.Например, длинная экспозиция с пленкой может привести к тому, что изображение, полученное с помощью быстрого затвора, будет отличаться от «эквивалентной» экспозиции.

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

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

final_color = diffuse_color * mat.ks + specular_color * mat.kd;

Полагаю, вы намеревались использовать mat.ks для зеркального и mat.kd для диффузного.Это может привести к путанице при настройке этих значений.

...