Pull to refresh

Comments 10

В качестве спектра, я так понимаю, взят Zucconi6, но ведь реальная спектральная картина зависит от цвета источника света. Если на диск светить светодиодом, то у нас будет дырявый спектр, в котором, в зависимости от цвета и качества, будут сильные пики красного, синего и зеленоватого цвета — и полностью отсутствовать все промежуточные значения. И картинка на диске, соответственно, тоже будет дырявой.

Иными словами, следующий шаг реализма — получить значение света (хотя бы RGB) в точке отражения и использовать его как вес для спектральной функции. Тогда можно будет увидеть зелёные блики от зелёного фонаря и красно-синие — от розового.
В общем случае нужно считать отдельно для как можно большего количества длин волн в видимом диапазоне, и после рассчётов переводить в XYZ численным интегрированием с учётом спектра источника. XYZ потом переводится в линейный sRGB умножением на матрицу. Подынтегральное выражение — чувствительность глаза (таблица значений CIE1931, которые полстатьи пытались аппроксимировать, cvrl.ioo.ucl.ac.uk/cmfs.htm), умноженное на спектр, получившийся после вычислений, которые в свою очередь зависят от спектра источника света (нет цвета в спектре источника — вычисления умножаются на ноль). Второй множитель дорого считать в шейдере для большого количества длин волн, и обычно обходятся тремя длинами волн, которые потом нужно интерполировать при интегрировании. У меня в шейдере выглядит как-то так:
float3 xyz = 0;
for (float j = 0; j < 64; j += 1) {
	float intensity = InterpolateLambdaNm(5.0*j+390, /*массив значений после рассчётов для нескольких длин волн*/);
	xyz += intensity * cie_colour_match_2012_2deg_390_705_5[j];
}
finalColor = mul(xyz_to_linear_srgb, xyz*5.0);

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

Но я не об этом, я о том, что
color += gi.light.color * spectral_zucconi6(wavelength); 
// Стыдно, не знаю, нормирован ли вектор цвета, будем считать, что да

для получения таких картинок
image

Промахнулся, ответил отдельной веткой

Повторюсь, это верно для источников света со сложным спектром, а в юнити всё RGB. Так что даже если мы попытаемся биться головой об пол, в итоге получим то же самое RGB, либо с шумом, либо с мыльцом.
Хотя, если мы перейдём к одномерным текстурам спектра, то сможем и дёшево их пришивать к игровым объектам (как освещению, так и материалу), и достаточно дёшево их умножать, ведь для пары тип_освещения-тип_материала это нужно будет сделать единожды, и добавить перделок, как, например, эффект Доплера.
Но я этого, того, размечтался. Я, в конце концов, шейдеры не колупал и понятия не имею, как это делается.
Шейдеры можно писать как угодно, просчитывать процесс прохождения луча хоть с точностью в нанометр, главное в конце пиксельного шейдера выдать правильный цвет. Ну и данные о спектре источника хранить отдельно где-то конечно и передавать в шейдер, по умолчанию везде rgb.

дномерным текстурам спектра, то сможем и дёшево их пришивать к игровым объектам (как освещению, так и материалу), и достаточно дёшево их умножать

Дёшево не выйдет, так как вычисления придётся проводить отдельно для каждой моделируемой длины волны и в конце переводить в RGB. Хотя наверное можно предрассчитать всё для каждой длины волны и угла отражения и положить в 2D текстуру.

Я упоминал, но видимо слишком туманно :) Берем длины волн, для которых будем всё считать, и интенсивности излучения источника света на этих же длинах волн. Выйдет примерно так в цикле:
float[iLambda] result = getLightIntensity(iLambda)*calculateSomethingLikeCDreflectionAtWawelength(iLambda);
В статье всего три длины волны, и для сложного спектра понятно что этого слишком мало. result потом подставляется в InterpolateLambdaNm из моего предыдущего комментария. getLightIntensity() — возвращает значение спектрограммы источника света для данной длины волны. У автора там просто значения интенсивностей для rgb, что маловато для сложного спектра. tldr: нужно считать всё для большого количества длин волн, особенно если спектр источника сложный, но так как это дорого, то обычно считают для трёх длин волн примерно соответствующих rgb значениям, что "прокатывает" для равномерного спектра.

приблизительно 299 792 458 метров в секунду

точно 299 792 458 метров в секунду — см. определение метра.

Я недавно тоже реализовывал иридесценцию, но в оффлайн рендерере Blender'a — Cycles. В трейсере Юнити я не разбираюсь, но если цвет пикселя вычисляется по нескольким лучам, то можно попробовать следующий подход. Можно наложить текстуру шума с равномерным распределением от 0 до 1 (я брал ячейки Вороного, не обязательно плавный шум). И значения от 0 до 1 растягиваем до значений видимых длин волн. Дальше мы вычисляем не длину волны, которая удовлетворяет условию максимума, а для каждой длины волны вычисляем её интенсивность в результате интерференции (тоже для нескольких порядков; формулы простые, можно посмотреть на википедии). Потом генерируем цвета с помощью «функции радуги» и умножаем на полученную на предыдущем шаге интенсивность. Если текстура шума субпиксельная, то разные лучи, которые будут попадать в данный пиксель, будут рассчитаны как бы для разных длин волн. В итоге цвет пикселя будет складываться и лучей на разных длинах волн и получим подобие спектрального рендера. Такой подход должен давать более физически корректный результат, а вычислений кажется не сильно больше
Sign up to leave a comment.

Articles

Change theme settings