Как добавить реализма в path tracing

в 15:47, , рубрики: path tracing, Анимация и 3D графика, игростроение, оптика, Песочница, Программирование, метки: , ,

Здравствуйте, уважаемыее. Казалось бы: куда уже реальнее, но все же, есть у меня идея. По порядку.

Введение

Path tracing — это метод создания сцен виртуальной реальности, основан на оптике.

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

Добавлю только, что в оптике время изотропно, это означает, что оба направления времени равноправны. Получается, можно следить за лучами не от источника к глазу, а от глаза к источнику. Это практичнее, path tracing и так очень затратный, нельзя тратить время на трассировку лучей, в которых нет шанса быть нарисованными.

Идея

А что если трассировать не лучи, а фотоны. Это же тоже самое! Я уточню. В одному направлении испускать сразу много монохроматических фотонов. Там где раньше был один луч, теперь 16, 32, 64… сколько не жалко фотонов с длиной волны 720, 700, ..., 450 нм. И у каждого из них может быть своя история. Смысл это будет иметь только тогда, когда материал по разному изменяет направление фотонов с разной частотой.

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

На псевдокоде это как-то так выглядит:

	i = 0..screen_h-1
	j = 0..screen_w-1
		double x = 2*j/screen_w-1;
		double y = 2*i/screen_h-1;
		table[i, j] = norm(center+x*left+y*up);

	n = 0..samples_num-1
	i = 0..screen_h-1
	j = 0..screen_w-1
	freq = 0..color_quality-1
		power = 0;
		Ray r = ray_from_vecs(eye, table[i, j]);
		trace(&r, &power, freq);
		screen[i*w+j] = screen[i*w+j]+power*rgb_from_freq(freq);

	i = 0..screen_h-1
	j = 0..screen_w-1
		screen[i*w+j] /= samples_num;

Здесь, думаю, все понятно. freq — частота волны света, screen_w, screen_h — ширина и высота экрана в пикселях, samples_num — количество выборок. Истинный цвет — среднее арифметическое по всех выборках, когда число выборок стремится к бесконечности. Векторы направлений фотонов лучше сохранять в таблице (table), что-бы не рассчитывать каждый раз. Рассчитываются они по несложной формуле, в которой участвуют еще и вектора указывающие на центр экрана, левую и верхнюю границу экрана. Например если соотношение сторон 4:3, то можно выбрать

center = (0, 0, 0)
left = (4, 0, 0)
up = (3, 0, 0)

Разумеется это все зависит от реализации.

Теперь о функции trace, в ней и происходит «магия». Она принимает положение и направление луча, а также его частоту, мощность (power) должна быть равна нулю. Возвращает она новое положение и направление и мощность. Положение и направление нас не интересует и фигурирует только для того, что-бы функция trace могла вызываться рекуррентно.

Color — массив, размер которого совпадает с количеством градаций частот.

Опять псевдокод

void trace()
{
	Vec n;	// вектор нормали в точке пересечения
	Vec p;	// точка пересечения
	Color dif;	// вероятности диффузного отражения от материала
	Color rfl;	// вероятности зеркального отражения от материала
	Color ems;	// интенсивность излучения материала
	Color koef;	// коэффициент преломления материала при конкретной частоте
	find_intersekt();

	double f = random();	// (fade) судьба луча
	double a = dif[freq];
	double b = a+rfl[freq];

	r->pos = p;
	double k = n*r->dir;	// косинус угла с нормалью при падении

	if (f < a)			// диффузное отражение
	{
		r->dir = случайное_направление_в_полупространстве_нормали;
	}
	if ((f >= a) && f < b)	// зеркальное отражение
	{				// эти формулы получил я сам
		Vec a_ = cross(n, r->d);
		Vec b_ = cross(a_, koef*n);
		double d = 1-b_*b_;
		if (d < 0)		// если возможно отражение (в полупространстве нормали), то отражаем
			r->dir = r->dir+2*k*n;
		else			// иначе преломляем
		{
			double l = sqrt(d);
			r->dir = b_-l*n;
		}
	}
	if (f < b)			// диффузное или зеркальное отражение, то идем в рекурсию
		trace(r, power, freq);
	power = power+ems[freq]*k;	// на выходе с рекурсии подсчитываем поверхности, которые излучают
}

Конечно этот псевдокод не претендует ни на что. Можно например сохранять разные вероятности для преломления и зеркального отражения. Или еще как то усложнить модель. Главное здесь — вероятности и коэффициент преломления зависят от частоты и у каждого фотона она своя.

Я полистал картинки по запросу path tracing в гугле и не увидел дисперсию света. Поиск по запросу monochromatic path tracing не показал ничего интересного. Пересмотрел всю первую страницу, везде эти слова разделены другими и в самой статье нету моей идеи.

Конечно скорее всего это уже реализовано и описано в книгах, до которых я еще не дотянулся.

Следствия

Не нужно быть гением, что-бы понять: трассировка монохроматических фотонов обойдется в 16, 32,… дороже чем стандартный алгоритм (зависит от качества цвета). Сейчас трассировка пути в реальном времени на железе обычного пользователя невозможна и непонятно когда это можно будет осуществить. А «монохроматическая» трассировка — еще дороже.

Автор: vlad9486

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js