Проблемы трассировки лучей — из будущего в реальное время.

Я знаю, это немного разочаровывает. Где отражения, тени и красивый внешний вид? Мы всё это получим, ведь мы пока только начали. Но это хорошее начало - сферы выглядят как круги, а это лучше, чем если бы они выглядели как кошки. Они не выглядят как сферы потому, что мы упустили важный компонент, позволяющий человеку определять форму объекта - то, как он взаимодействует со светом.

Освещение

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

Мы начнём с некоторых упрощающих допущений, которые облегчат нам жизнь.

Во-первых, мы объявим, что всё освещение имеет белый цвет. Это позволит нам охарактеризовать любой источник освещения единственным действительным числом i, называемым яркостью освещения. Симуляция цветного освещения не так сложна (необходимо только три значения яркости, по одному на канал, и вычисление всех цветов и освещения поканально), но чтобы сделать нашу работу проще, я не буду его делать.

Во-вторых, мы избавимся от атмосферы. Это значит, что освещение не становятся менее яркими, независимо от их дальности. Затухание яркости света в зависимости от расстояния реализовать тоже не слишком сложно, но для ясности мы пока его пропустим.

Источники освещения

Свет должен откуда-то поступать. В этом разделе мы зададим три различных типа источников освещения.

Точечные источники

Точечный источник испускает свет из фиксированной точки в пространстве, называемой его позицией . Свет испускается равномерно во всех направлениях; именно поэтому его также называют всенаправленным освещением . Следовательно, точечный источник полностью характеризуется его позицией и яркостью.

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

Давайте зададим вектор как направление из точки P в сцене к источнику освещения Q. Этот вектор, называемый световым вектором , просто равен . Заметьте, что поскольку Q фиксирована, а P может быть любой точкой сцены, то в общем случае будет разным для каждой точки сцены.

Направленные источники

Если точечный источник является хорошей аппроксимацией лампы накаливания, то что может служить аппроксимацией Солнца?

Это хитрый вопрос, и ответ зависит от того, что вы хотите отрендерить.

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

Однако если в вашей сцене действие происходит на Земле, то это не слишком хорошее приближение. Солнце находится так далеко, что каждый луч света будет на самом деле иметь одинаковое направление (Примечание: эта аппроксимация сохраняется в масштабе города, но не на более дальних расстояниях - на самом деле. древние греки смогли с удивительной точностью вычислить радиус Земли на основании разных направлений солнечного света в различных местах.). Хотя это можно аппроксимировать это с помощью точечного источника, сильно удалённого от сцены, это расстояние и расстояние между объектами в сцене настолько отличаются по величине, что могут появиться ошибки точности чисел.

Для таких случаев мы зададим направленные источники освещения . Как и точечные источники, направленный источник имеет яркость, но в отличие от них, у него нет позиции. Вместо неё у него есть направление . Можно воспринимать его как бесконечно удалённый точечный источник, светящий в определённом направлении.

В случае точечных источников нам нужно вычислять новый световой вектор для каждой точки P сцены, но в этом случае задан. В сцене с Солнцем и Землёй будет равен .

Окружающее освещение

Можно ли смоделировать любое освещение реального мира как точечный или направленный источник? Почти всегда да (Примечание: но это необязательно будет просто; зональное освещение (представьте источник за рассеивателем) можно аппроксимировать множеством точечных источников на его поверхности, но это сложно, более затратно по вычислениям, а результаты оказываются неидеальными.). Достаточно ли этих двух типов источников для наших целей? К сожалению, нет.

Представьте, что происходит на Луне. Единственным значимым источником освещения поблизости является Солнце. То есть «передняя половина» Луны относительно Солнца получает всё освещение, а «задняя половина» находится в полной темноте. Мы видим это с разных углов на Земле, и этот эффект создаёт то, что мы называем «фазами» Луны.

Однако ситуация на Земле немного отличается. Даже точки, не получающие освещения непосредственно от источника освещения, не находятся полностью в темноте (просто посмотрите на пол под столом). Как лучи света достигают этих точек, если «обзор» на источники освещения чем-то перекрыт?

Как я упомянул в разделе Цветовые модели , когда свет падает на объект, часть его поглощается, но остальная часть рассеивается в сцене. Это значит, что свет может поступать не только от источников освещения, но и от других объектов, получающих его от источников освещения и рассеивающих его обратно. Но зачем останавливаться на этом? Рассеянное освещение в свою очередь падает на какой-нибудь другой объект, часть его поглощается, а часть снова рассеивается в сцене. При каждом отражении свет теряет часть своей яркости, но теоретически можно продолжать ad infinitum (Примечание: на самом деле нет, потому что свет имеет квантовую природу, но достаточно близко к этому.).

Это значит, что нужно считать источником освещения каждый объект . Как можно представить, это сильно увеличивает сложность нашей модели, поэтому мы не пойдём таким путём (Примечание: но вы можете хотя бы загуглить Global Illumination и посмотреть на прекрасные изображения.).

Но мы всё равно не хотим, чтобы каждый объект был или освещён напрямую, или был полностью тёмным (если только мы не рендерим модель Солнечной системы). Чтобы преодолеть эту преграду, мы зададим третий тип источников освещения, называемый окружающим освещением , которое характеризуется только яркостью. Считается, что оно носит безусловный вклад освещения в каждую точку сцены. Это очень сильное упрощение чрезвычайно сложного взаимодействия между источниками освещения и поверхностями сцены, но оно работает.

Освещённость одной точки

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

Для вычисления освещённости точки нам просто нужно вычислить количество света, вносимое каждым источником и сложить их, чтобы получить одно число, представляющее общее количество полученного точкой освещения. Затем мы можем умножить цвет поверхности в этой точке на это число, чтобы получить правильно освещённый цвет.

Итак, что произойдёт, когда луч света с направлением из направленного или точечного источника падает на точку P какого-нибудь объекта в нашей сцене?

Интуитивно мы можем разбить объекты на два общих класса, в зависимости от того, как они ведут себя со светом: «матовые» и «блестящие». Поскольку большинство окружающих нас предметов можно считать «матовыми», то с них мы и начнём.

Диффузное рассеяние

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

Чтобы убедиться в этом, внимательно посмотрите на какой-нибудь матовый объект, например, на стену: если двигаться вдоль стены, её цвет не меняется. То есть, видимый вами свет, отражённый от объекта, одинаков вне зависимости от того, в какое место объекта вы смотрите.

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

Чтобы выразить это математически, давайте охарактеризуем ориентацию поверхности по её вектору нормали . Вектор нормали, или просто «нормаль» - это вектор, перпендикулярный поверхности в какой-то точке. Также он является единичным вектором, то есть его длина равна 1. Мы будем называть этот вектор .

Моделирование диффузного отражения

Итак, луч света с направлением и яркостью падает на поверхность с нормалью . Какая часть отражается обратно сцену как функция от , и ?

Для геометрической аналогии давайте представим яркость света как «ширину» луча. Его энергия распределяется по поверхности размером . Когда и имеют одно направление, то есть луч перпендикулярен поверхности, , а это значит, что энергия, отражённая на единицу площади равна падающей энергии на единицу площади; < . С другой стороны, когда угол между и приближается к , приближается к , то есть энергия на единицу площади приближается к 0; . Но что происходит в промежутках?

Ситуация отображена на схеме ниже. Мы знаем , и ; я добавил углы и , а также точки , и , чтобы сделать связанные с этой схемой записи проще.

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

Луч света с шириной падает на поверхность в точке под углом . Нормаль в точке равна , а энергия, переносимая лучом, распределяется по . Нам нужно вычислить .

Один из углов равен , а другой - . Тогда третий угол равен . Но нужно заметить, что и тоже образуют прямой угол, то есть тоже должны быть . Следовательно, :

Давайте рассмотрим треугольник . Его углы равны , и . Сторона равна , а сторона равна .

И теперь… тригонометрия спешит на помощь! По определению ; заменяем на , а на , и получаем


что преобразуется в
Мы почти закончили. - это угол между и , то есть можно выразить как
И наконец
Итак, мы получили очень простое уравнение, связывающее отражённую часть света с углом между нормалью к поверхности и направлением света.

Заметьте, что при углах больше значение становится отрицательным. Если мы не задумываясь используем это значение, то в результате получим источники света, вычитающие свет. Это не имеет никакого физического смысла; угол больше просто означает, что свет на самом деле достигает задней части поверхности, и не вносит свой вклад в освещение освещаемой точки. То есть если становится отрицательным, то мы считаем его равным .

Уравнение диффузного отражения

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

Нормали сферы

Здесь только отсутствует единственная мелочь: откуда берутся нормали?

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

Почему я написал «направление нормали», а не «нормаль»? Кроме перпендикулярности к поверхности, нормаль должна быть единичным вектором; это было бы справедливо, если бы радиус сферы был равен , что не всегда верно. Для вычисления самой нормали нам нужно разделить вектор на его длину, получив таким образом длину :


Это представляет в основном теоретический интерес, потому что записанное выше уравнение освещения содержит деление на , но хорошим подходом будет создание «истинных» нормалей; это упростит нам работу в дальнейшем.

Рендеринг с диффузным отражением

Давайте переведём всё это в псевдокод. Во-первых, давайте добавим в сцену пару источников освещения:

Light { type = ambient intensity = 0.2 } light { type = point intensity = 0.6 position = (2, 1, 0) } light { type = directional intensity = 0.2 direction = (1, 4, 4) }
Заметьте, что яркость удобно суммируется в , потому что из уравнения освещения следует, что никакая точка не может иметь яркость света выше, чем единица. Это значит, что у нас не получатся области со «слишком большой выдержкой».

Уравнение освещения довольно просто преобразовать в псевдокод:

ComputeLighting(P, N) { i = 0.0 for light in scene.Lights { if light.type == ambient { i += light.intensity } else { if light.type == point L = light.position - P else L = light.direction n_dot_l = dot(N, L) if n_dot_l > 0 i += light.intensity*n_dot_l/(length(N)*length(L)) } } return i }
И единственное, что осталось - использовать ComputeLighting в TraceRay . Мы заменим строку, возвращающую цвет сферы

Return closest_sphere.color
на этот фрагмент:

P = O + closest_t*D # вычисление пересечения N = P - closest_sphere.center # вычисление нормали сферы в точке пересечения N = N / length(N) return closest_sphere.color*ComputeLighting(P, N)
Просто ради интереса давайте добавим большую жёлтую сферу:

Sphere { color = (255, 255, 0) # Yellow center = (0, -5001, 0) radius = 5000 }
Мы запускаем рендерер, и узрите - сферы наконец начали выглядеть как сферы!

Но постойте, как большая жёлтая сфера превратилась в плоский жёлтый пол?

Этого и не было, просто она настолько велика относительно других трёх, а камера настолько к ней близка, что она выглядит плоской. Так же, как наша планета выглядит плоской, когда мы стоим на её поверхности.

Отражение от гладкой поверхности

Теперь мы обратим своё внимание на «блестящие» объекты. В отличие от «матовых» объектов, «блестящие» меняют свой внешний вид, когда смотришь на них под разными углами.

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

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

Почему это происходит? Мы можем начать с того, почему это не происходит на матовых объектах. Как мы видели в предыдущем разделе, когда луч света падает на поверхнось матового объекта, он равномерно рассеивается назад в сцену во всех направлениях. Интуитивно понятно, что так происходит из-за неровности поверхности объекта, то есть на микроскопическом уровне она похожа на множество мелких поверхностей, направленных в случайных направлениях:

Но что будет, если поверхность не настолько неровная? Давайте возьмём другую крайность - идеально отполированное зеркало. Когда луч света падает на зеркало, он отражается в единственном направлении, которое симметрично углу падения относительно нормали зеркала. Если мы назовём направление отражённого света и условимся, что указывает на источник света, то получим такую ситуацию:

В зависимости от степени «отполированности» поверхности, она более или менее похожа на зеркало; то есть мы получаем «зеркальное» отражение (specular reflection, от латинского «speculum», то есть «зеркало»).

Для идеально отполированного зеркала падающий луч света отражается в единственном направлении . Именно это позволяет нам чётко видеть объекты в зеркале: для каждого падающего луча есть единственный отражённый луч . Но не каждый объект отполирован идеально; хотя бОльшая часть света отражается в направлении , часть его отражается в направлениях, близких к ; чем ближе к , тем больше света отражается в этом направлении. «Блеск» объекта определяет то, насколько быстро отражённый свет уменьшается при отдалении от :

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

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

Моделирование «зеркального» отражения

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

Давайте возьмём . У него есть хорошие свойства: , , а значения постепенно уменьшаются от до по очень красивой кривой:

Соответствует всем требованиям к функции «зеркального» отражения, так почему бы не использовать его?

Но нам не хватает ещё одной детали. В такой формулировке все объекты блестят одинаково. Как изменить уравнение для получения различных степеней блеска?

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

Чем больше значение , тем «уже» становится функция в окрестностях , и тем более блестящим выглядит объект.

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

Давайте объединим всё вместе. Луч падает на поверхность в точке , где нормаль равна , а показатель отражения - . Какое количество света отразится в направлении обзора ?

Мы уже решили, что это значение равно , где - это угол между и , который в свою очередь является , отражённым относительно . То есть первым шагом будет вычисление из и .

Мы можем разложить на два вектора и , таких, что , где параллелен , а перпендикулярен :

Это проекция на ; по свойствам скалярного произведения и исходя из того, что , длина этой проекции равна . Мы определили, что будет параллелен , поэтому .

Поскольку , мы можем сразу получить .

Теперь посмотрим на ; поскольку он симметричен относительно , его компонент, параллельный , тот же, что и у , а перпендикулярный компонент противоположен компоненту ; то есть :

Подставляя полученные ранее выражения, мы получим


и немного упростив, получаем

Значение «зеркального» отражения

Теперь мы готовы записать уравнение «зеркального» отражения:

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

Рендеринг с «зеркальными» отражениями

Давайте добавим в сцену «зеркальные» отражения, над которыми мы сейчас работали. Во-первых, внесём некоторые изменения в саму сцену:

Sphere { center = (0, -1, 3) radius = 1 color = (255, 0, 0) # Красный specular = 500 # Блестящий } sphere { center = (-2, 1, 3) radius = 1 color = (0, 0, 255) # Синий specular = 500 # Блестящий } sphere { center = (2, 1, 3) radius = 1 color = (0, 255, 0) # Зелёный specular = 10 # Немного блестящий } sphere { color = (255, 255, 0) # Жёлтый center = (0, -5001, 0) radius = 5000 specular = 1000 # Очень блестящий }
В коде нам нужно изменить ComputeLighting , чтобы он при необходимости вычислял значение «зеркальности» и прибавлял его к общему освещению. Заметьте, что теперь ему требуются и :

ComputeLighting(P, N, V, s) { i = 0.0 for light in scene.Lights { if light.type == ambient { i += light.intensity } else { if light.type == point L = light.position - P else L = light.direction # Диффузность n_dot_l = dot(N, L) if n_dot_l > 0 i += light.intensity*n_dot_l/(length(N)*length(L)) # Зеркальность if s != -1 { R = 2*N*dot(N, L) - L r_dot_v = dot(R, V) if r_dot_v >
И наконец нам нужно изменить TraceRay , чтобы он передавал новые параметры ComputeLighting . очевиден; он берётся из данных сферы. Но как насчёт ? - это вектор, указывающий от объекта в камеру. К счастью, в TraceRay у нас уже есть вектор, направленный из камеры к объекту - это , направление трассируемого луча! То есть - это просто .

Вот новый код TraceRay с «зеркальным» отражением:

TraceRay(O, D, t_min, t_max) { closest_t = inf closest_sphere = NULL for sphere in scene.Spheres { t1, t2 = IntersectRaySphere(O, D, sphere) if t1 in and t1 < closest_t closest_t = t1 closest_sphere = sphere if t2 in and t2 < closest_t closest_t = t2 closest_sphere = sphere } if closest_sphere == NULL return BACKGROUND_COLOR P = O + closest_t*D # Вычисление пересечения N = P - closest_sphere.center # Вычисление нормали сферы в точке пересечения N = N / length(N) return closest_sphere.color*ComputeLighting(P, N, -D, sphere.specular) }
И вот наша награда за всё это жонглирование векторами:

Тени

Там, где есть свет и объекты, должны быть и тени. Так где же наши тени?

Давайте начнём с более фундаментального вопроса. Почему должны быть тени? Тени появляются там, где есть свет, но его лучи не могут достичь объекта, потому что на их пути есть другой объект.

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

Вместо этого нам нужно добавить немного логики, говорящей "если между точкой и источником есть объект, то не нужно добавлять освещение, поступающее от этого источника ".

Мы хотим выделить два следующих случая:

Похоже, что у нас есть все необходимые для этого инструменты.

Давайте начнём с направленного источника. Мы знаем ; это точка, которая нас интересует. Мы знаем ; это часть определения источника освещения. Имея и , мы можем задать луч, а именно , который проходит из точки до бесконечно отдалённого источника освещения. Пересекает ли этот луч другой объект? Если нет, то между точкой и источником ничего нет, то есть мы можем вычислить освещённость от этого источника и прибавить его к общей освещённости. Если пересекает, то мы игнорируем этот источник.

Мы уже знаем, как вычислить ближайшее пересечение между лучом и сферой; мы используем его для трассировки лучей от камеры. Мы снова можем использовать его для вычисления ближайшего пересечения между лучом света и остальной сценой.

Однако параметры немного отличаются. Вместо того, чтобы начинаться с камеры, лучи испускаются из . Направление равно не , а . И нас интересуют пересечения со всем после на бесконечное расстояние; это значит, что и .

Мы можем обрабатывать точечные источники очень похожим образом, но с двумя исключениями. Во-первых, не задан , но его очень просто вычислить из позиции источника и . Во-вторых, нас интересуют любые пересечения, начиная с , но только до (в противном случае, объекты за источником освещения могли бы создавать тени!); то есть в этом случае и .

Существует один пограничный случай, который нам нужно рассмотреть. Возьмём луч . Если мы будем искать пересечения, начиная с , то мы, вероятнее всего, найдём саму при , потому что действительно находится на сфере, и ; другими словами, каждый объект будет отбрасывать тени на самого себя (Примечание: если точнее, то мы хотим избежать ситуации, при которой точка, а не весь объект, отбрасывает тень на саму себя; объект с более сложной чем сфера формой (а именно любой вогнутый объект) может отбрасывать истинные тени на самого себя!

Простейший способ справиться с этим - использовать в качестве нижней границы значений вместо малое значение . Геометрически, мы хотим сделать так, чтобы луч начинается немного вдали от поверхности, то есть рядом с , но не точно в . То есть для направленных источников интервал будет , а для точечных - .

Рендеринг с тенями

Давайте превратим это в псевдокод.

В предыдущей версии TraceRay вычислял ближайшее пересечение луч-сфера, а затем вычислял освещение в пересечении. Нам нужно извлечь код ближайшего пересечения, поскольку мы хотим использовать его снова для вычисления теней:

ClosestIntersection(O, D, t_min, t_max) { closest_t = inf closest_sphere = NULL for sphere in scene.Spheres { t1, t2 = IntersectRaySphere(O, D, sphere) if t1 in and t1 < closest_t closest_t = t1 closest_sphere = sphere if t2 in and t2 < closest_t closest_t = t2 closest_sphere = sphere } return closest_sphere, closest_t }
В результате TraceRay получается гораздо проще:

TraceRay(O, D, t_min, t_max) { closest_sphere, closest_t = ClosestIntersection(O, D, t_min, t_max) if closest_sphere == NULL return BACKGROUND_COLOR P = O + closest_t*D # Compute intersection N = P - closest_sphere.center # Compute sphere normal at intersection N = N / length(N) return closest_sphere.color*ComputeLighting(P, N, -D, sphere.specular) }
Теперь нам нужно добавить в ComputeLighting проверку тени:

ComputeLighting(P, N, V, s) { i = 0.0 for light in scene.Lights { if light.type == ambient { i += light.intensity } else { if light.type == point { L = light.position - P t_max = 1 } else { L = light.direction t_max = inf } # Проверка тени shadow_sphere, shadow_t = ClosestIntersection(P, L, 0.001, t_max) if shadow_sphere != NULL continue # Диффузность n_dot_l = dot(N, L) if n_dot_l > 0 i += light.intensity*n_dot_l/(length(N)*length(L)) # Зеркальность if s != -1 { R = 2*N*dot(N, L) - L r_dot_v = dot(R, V) if r_dot_v > 0 i += light.intensity*pow(r_dot_v/(length(R)*length(V)), s) } } } return i }
Вот как будет выглядеть наша заново отрендеренная сцена:


Исходный код и рабочее демо >>

Теперь у нас уже что-то получается.

Отражение

У нас появились блестящие объекты. Но можно ли создать объекты, которые на самом деле ведут себя как зеркала? Конечно, и на самом деле их реализация в трассировщике лучей очень проста, но поначалу может показаться запутанной.

Давайте посмотрим, как работают зеркала. Когда мы смотрим в зеркало, то видим лучи света, отражающиеся от зеркала. Лучи света отражаются симметрично относительно нормали поверхности:

Допустим, мы трассируем луч и ближайшим пересечением оказывается зеркало. Какой цвет имеет луч света? Очевидно, то не цвет зеркала, а любой цвет, который имеет отражённый луч. Всё, что нам нужно - вычислить направление отражённого луча и выяснить, каким был цвет света, падающего из этого направления. Вот бы у нас была функция, возвращающая для заданного луча цвет света, падающего из этого направления…

О, постойте, у нас же она есть: она называется TraceRay .

Итак, мы начинаем с основного цикла TraceRay , чтобы увидеть, что «видит» луч, испущенный из камеры. Если TraceRay определяет, что луч видит отражающий объект, то он просто должен вычислить направление отражённого луча и вызвать… сам себя.

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

Не торопитесь, я подожду.

Теперь, когда эйфория от этого прекрасного момента эврика! немного спала, давайте немного это формализируем.

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

Есть множество способов предотвращения этой проблемы. Мы введём предел рекурсии алгоритма; он будет контролировать «глубину», на которую он сможет уйти. Давайте назовём его . При , то видим объекты, но без отражений. При мы видим некоторые объекты и отражения некоторых объектов. При мы видим некоторые объекты, отражения некоторых объектов и отражения некоторых отражений некоторых объектов . И так далее. В общем случае, нет особого смысла уходить вглубь больше чем на 2-3 уровня, потому что на этом этапе разница уже едва заметна.

Мы создадим ещё одно разграничение. «Отражаемость» не должна иметь значение «есть или нет» - объекты могут быть частично отражающими и частично цветными. Мы назначим каждой поверхности число от до , определяющее её отражаемость. После чего мы будем смешивать локально освещённый цвет и отражённый цвет пропорционально этому числу.

И наконец, нужно решить, какие параметры должен получать рекурсивный вызов TraceRay ? Луч начинается с поверхности объекта, точки . Направление луча - это направление света, отразившегося от ; в TraceRay у нас есть , то есть направление от камеры к , противоположное движению света, то есть направление отражённого луча будет , отражённый относительно . Аналогично тому, что происходит с тенями, мы не хотим, чтобы объекты отражали сами себя, поэтому . Мы хотим видеть объекты отражёнными вне зависимости от того, насколько они отдалены, поэтому . И последнее - предел рекурсии на единицу меньше, чем предел рекурсии, в котором мы находимся в текущий момент.

Рендеринг с отражением

Давайте добавим к коду трассировщика лучей отражение.

Как и ранее, в первую очередь мы изменяем сцену:

Sphere { center = (0, -1, 3) radius = 1 color = (255, 0, 0) # Красный specular = 500 # Блестящий reflective = 0.2 # Немного отражающий } sphere { center = (-2, 1, 3) radius = 1 color = (0, 0, 255) # Синий specular = 500 # Блестящий reflective = 0.3 # Немного более отражающий } sphere { center = (2, 1, 3) radius = 1 color = (0, 255, 0) # Зелёный specular = 10 # Немного блестящий reflective = 0.4 # Ещё более отражающий } sphere { color = (255, 255, 0) # Жёлтый center = (0, -5001, 0) radius = 5000 specular = 1000 # Очень блестящий reflective = 0.5 # Наполовину отражающий }
Мы используем формулу «луча отражения» в паре мест, поэтому может избавиться от неё. Она получает луч и нормаль , возвращая , отражённый относительно :

ReflectRay(R, N) { return 2*N*dot(N, R) - R; }
Единственным изменением в ComputeLighting является замена уравнения отражения на вызов этого нового ReflectRay .

В основной метод внесено небольшое изменение - нам нужно передать TraceRay верхнего уровня предел рекурсии:

Color = TraceRay(O, D, 1, inf, recursion_depth)
Константе recursion_depth можно задать разумное значение, например, 3 или 5.

Единственные важные изменения происходят ближе к концу TraceRay , где мы рекурсивно вычисляем отражения:

TraceRay(O, D, t_min, t_max, depth) { closest_sphere, closest_t = ClosestIntersection(O, D, t_min, t_max) if closest_sphere == NULL return BACKGROUND_COLOR # Вычисление локального цвета P = O + closest_t*D # Вычисление точки пересечения N = P - closest_sphere.center # Вычисление нормали к сфере в точке пересечения N = N / length(N) local_color = closest_sphere.color*ComputeLighting(P, N, -D, sphere.specular) # Если мы достигли предела рекурсии или объект не отражающий, то мы закончили r = closest_sphere.reflective if depth <= 0 or r <= 0: return local_color # Вычисление отражённого цвета R = ReflectRay(-D, N) reflected_color = TraceRay(P, R, 0.001, inf, depth - 1) return local_color*(1 - r) + reflected_color*r }
Пусть результаты говорят сами за себя:

Чтобы лучше понять предел глубины рекурсии, давайте ближе рассмотрим рендер с :

А вот тот же увеличенный вид той же сцены, на этот раз отрендеренный с :

Как вы видите, разница заключается в том, видим ли мы отражения отражений отражений объектов, или только отражения объектов.

Произвольная камера

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

Давайте начнём с положения. Вы наверно заметили, что используется во всём псевдокоде только один раз: в качестве начальной точки лучей, исходящих из камеры в методе верхнего уровня. Если мы хотим поменять положение камеры. то единственное , что нужно сделать - это использовать другое значение для .

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

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

Меняется только функция верхнего уровня:

For x in [-Cw/2, Cw/2] { for y in [-Ch/2, Ch/2] { D = camera.rotation * CanvasToViewport(x, y) color = TraceRay(camera.position, D, 1, inf) canvas.PutPixel(x, y, color) } }
Вот как выглядит наша сцена при наблюдении из другого положения и при другой ориентации:

Куда двигаться дальше

Мы закончим первую часть работы кратким обзором некоторых интересных тем, которые мы не исследовали.

Оптимизация

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

Параллелизация

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

На самом деле, трассировщики лучей относятся к классу алгоритмов, называемому чрезвычайно параллелизуемым именно потому что, сама их природа позволяет очень просто их распараллеливать.

Кэширование значений

Рассмотрим значения, вычисляемые IntersectRaySphere , на который трассировщик лучей обычно тратит большинство времени:

K1 = dot(D, D) k2 = 2*dot(OC, D) k3 = dot(OC, OC) - r*r
Некоторые из этих значений постоянны для всей сцены - как только вы узнаете, как расположены сферы, r*r и dot(OC, OC) больше не меняются. Можно вычислить их один раз во время загрузки сцены и хранить их в самих сферах; вам просто нужно будет пересчитать их, если сферы должны переместиться в следующем кадре. dot(D, D) - это константа для заданного луча, поэтому можно вычислить его в ClosestIntersection и передать в IntersectRaySphere .

Оптимизации теней

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

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

Аналогично, при вычислении пересечения между лучом света и объектами в сцене на самом деле нам не нужно ближайшее пересечение - достаточно знать, что существует по крайней мере одно пересечение. Можно использовать специальную версию ClosestIntersection , которая возвращает результат, как только найдёт первое пересечение (и для этого нам нужно вычислять и возвращать не closest_t , а просто булево значение).

Пространственные структуры

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

Подробное рассмотрение таких структур не относится к тематике нашей статьи, но общая идея такова: предположим, что у нас есть несколько близких друг к другу сфер. Можно вычислить центр и радиус наименьшей сферы, содержащей все эти сферы. Если луч не пересекает эту граничную сферу, то можно быть уверенным, что он не пересекает ни одну содержащуюся в нём сферу, и сделать это можно за одну проверку пересечения. Разумеется, если он пересекает сферу, то нам всё равно нужно проверять, пересекает ли он какую-нибудь из содержащихся в ней сфер.

Подробнее об этом можно узнать, прочитав о иерархии ограничивающих объёмов .

Субдискретизация

Вот простой способ сделать трассировщик лучей в раз быстрее: вычислять в раз пикселей меньше!

Предположим, мы трассируем лучи для пикселей и , и они падают на один объект. Можно логически предположить, что луч для пикселя тоже будет падать на тот же объект, пропустить начальный поиск пересечений со всей сценой и перейти непосредственно к вычислению цвета в этой точке.

Если сделать так в горизонтальном и вертикальном направлениях, то можно выполнять максимум на 75% меньшей первичных вычислений пересечений луч-сцена.

Разумеется, так можно запросто пропустить очень тонкий объект: в отличие от рассмотренных ранее, это «неправильная» оптимизация, потому что результаты её использования не идентичны тому, что бы мы получили без неё; в каком-то смысле, мы «жульничаем» на этой экономии. Хитрость в том, как догадаться сэкономить правильно, обеспечив удовлетворительные результаты.

Другие примитивы

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

Заметьте, что с точки зрения TraceRay может подойти любой объект, пока для него нужно вычислять только два значения: значение для ближайшего пересечения между лучом и объектом, и нормаль в точке пересечения. Всё остальное в трассировщике лучей не зависит от типа объекта.

Хорошим выбором будут треугольники. Сначала нужно вычислить пересечение между лучом и плоскостью, содержащей треугольник, и если пересечение есть, то определить, находится ли точка внутри треугольника.

Конструктивная блочная геометрия

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

Как это работает? Для каждого объекта можно вычислить места, где луч входит и выходит из объекта; например, в случае сферы луч входит в и выходит в . Предположим, что нам нужно вычислить пересечение двух сфер; луч находится внутри пересечения, когда находится внутри обеих сфер, и снаружи в противоположном случае. В случае вычитания луч находится внутри, когда он находится внутри первого объекта, но не внутри второго.

В более общем виде, если мы хотим вычислить пересечение между лучом и (где - любой булевый оператор), то сначала нужно по отдельности вычислить пересечение луч- и луч- , что даёт нам «внутренний» интервал каждого объекта и . Затем мы вычисляем , который находится во «внутреннем» интервале . Нам нужно просто найти первое значение , которое находится и во «внутреннем» интервале и в интервале , которые нас интересуют:

Нормаль в точке пересечения является или нормалью объекта, создающего пересечение, или её противоположностью, в зависимости от того, глядим ли мы «снаружи» или «изнутри» исходного объекта.

Разумеется, и не обязаны быть примитивами; они сами могут быть результатами булевых операций! Если реализовать это чисто, то нам даже не потребуется знать, чем они являются, пока мы можем получить из них пересечения и нормали. Таким образом, можно взять три сферы и вычислить, например, .

Прозрачность

Не все объекты обязаны быть непрозрачными, некоторые могут быть частично прозрачными.

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

Преломление

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

Например, приблизительно равен , а приблизительно равен . То есть для луча, входящего в воду под углом получаем




Остановитесь на мгновение и осознайте: если реализовать конструктивную блочную геометрию и прозрачность, то можно смоделировать увеличительное стекло (пересечение двух сфер), которое будет вести себя как физически правильное увеличительное стекло!

Суперсэмплинг

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

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

Решить эту проблему можно трассированием нескольких лучей на пиксель - 4, 9, 16, и так далее, а затем усредняя их, чтобы получить цвет пикселя.

Разумеется, при этом трассировщик лучей становится в 4, 9 или 16 раз медленнее, по той же причине, по которой субдискретизация делает его в раз быстрее. К счастью, существует компромисс. Мы можем предположить, что свойства объекта вдоль его поверхности меняются плавно, то есть испускание 4 лучей на пиксель, которые падают на один объект в немного отличающихся точках, не слишком улучшит вид сцены. Поэтому мы можем начать с одного луча на пиксель и сравнивать соседние лучи: если они падают на другие объекты или их цвет отличается больше, чем на переделённое пороговое значение, то применяем к обоим подразделение пикселей.

Псевдокод трассировщика лучей

Ниже представлена полная версия псевдокода, созданного нами в главах о трассировке лучей:

CanvasToViewport(x, y) { return (x*Vw/Cw, y*Vh/Ch, d) } ReflectRay(R, N) { return 2*N*dot(N, R) - R; } ComputeLighting(P, N, V, s) { i = 0.0 for light in scene.Lights { if light.type == ambient { i += light.intensity } else { if light.type == point { L = light.position - P t_max = 1 } else { L = light.direction t_max = inf } # Проверка теней shadow_sphere, shadow_t = ClosestIntersection(P, L, 0.001, t_max) if shadow_sphere != NULL continue # Диффузность n_dot_l = dot(N, L) if n_dot_l > 0 i += light.intensity*n_dot_l/(length(N)*length(L)) # Блеск if s != -1 { R = ReflectRay(L, N) r_dot_v = dot(R, V) if r_dot_v > 0 i += light.intensity*pow(r_dot_v/(length(R)*length(V)), s) } } } return i } ClosestIntersection(O, D, t_min, t_max) { closest_t = inf closest_sphere = NULL for sphere in scene.Spheres { t1, t2 = IntersectRaySphere(O, D, sphere) if t1 in and t1 < closest_t closest_t = t1 closest_sphere = sphere if t2 in and t2 < closest_t closest_t = t2 closest_sphere = sphere } return closest_sphere, closest_t } TraceRay(O, D, t_min, t_max, depth) { closest_sphere, closest_t = ClosestIntersection(O, D, t_min, t_max) if closest_sphere == NULL return BACKGROUND_COLOR # Вычисление локального цвета P = O + closest_t*D # Вычисление точки пересечения N = P - closest_sphere.center # Вычисление нормали сферы в точке пересечения N = N / length(N) local_color = closest_sphere.color*ComputeLighting(P, N, -D, sphere.specular) # Если мы достигли предела рекурсии или объект не отражающий, то мы закончили r = closest_sphere.reflective if depth <= 0 or r <= 0: return local_color # Вычисление отражённого цвета R = ReflectRay(-D, N) reflected_color = TraceRay(P, R, 0.001, inf, depth - 1) return local_color*(1 - r) + reflected_color*r } for x in [-Cw/2, Cw/2] { for y in [-Ch/2, Ch/2] { D = camera.rotation * CanvasToViewport(x, y) color = TraceRay(camera.position, D, 1, inf) canvas.PutPixel(x, y, color) } }
А вот сцена, использованная для рендеринга примеров:

Viewport_size = 1 x 1 projection_plane_d = 1 sphere { center = (0, -1, 3) radius = 1 color = (255, 0, 0) # Красный specular = 500 # Блестящий reflective = 0.2 # Немного отражающий } sphere { center = (-2, 1, 3) radius = 1 color = (0, 0, 255) # Синий specular = 500 # Блестящий reflective = 0.3 # Немного более отражающий } sphere { center = (2, 1, 3) radius = 1 color = (0, 255, 0) # Зелёный specular = 10 # Немного блестящий reflective = 0.4 # Ещё более отражающий } sphere { color = (255, 255, 0) # Жёлтый center = (0, -5001, 0) radius = 5000 specular = 1000 # Очень блестящий reflective = 0.5 # Наполовину отражающий } light { type = ambient intensity = 0.2 } light { type = point intensity = 0.6 position = (2, 1, 0) } light { type = directional intensity = 0.2 direction = (1, 4, 4) }

Теги: Добавить метки

Не так давно 4A Games, создатель удивительно реалистичных игр Metro, выпустила видеоролик с использованием технологии RTX от Nvidia на примере METRO: EXODUS. Это графическое нововведение является большим и уверенным шагом вперед в вопросах трассировки лучей. Но что это все это значит?

За завесой тайны

Начнем с самого начала. Во-первых, рендеринг-трассировка лучей – это один из базовых видов визуализации, который применяется в фильмах и разных видах дизайна: от промышленности до архитектуры. То есть то, что вы видите на сайтах в качестве фотографий техники – это именно 3d рендер.

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

Это компьютерное изображение, созданное Enrico Cerica с использованием OctaneRender, показывает лучи, тени и отражения на сложной поверхности пола

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


Упрощенная схема трассировки лучей

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

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

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

Особенно заметно, что в матовых площадках отражение сильно зависит от близости объекта к поверхности. То есть, чем объект дальше от поверхности, тем он сильнее становится размытым. Это важное свойство, которое в жизни мы даже не замечаем, хотя оно есть.

Но самое важное – это тени. Нет ничего более некачественного, чем тени в любых играх.

Это, как правило, просто проекции объектов, имеющие резкие неестественные края. Однако есть более качественные, по игровым меркам, тени. Это мягкие варианты с линией перехода, то есть тень и полутень.


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

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

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

Трассировка лучей – дело несложное, но лучей очень много, и, к сожалению, на текущий момент сделать все в режиме реального времени не получается. Дело в том, что в жизни лучи расходятся в совершенно разных направлениях. Идеально было бы, чтобы падающий луч разделялся на бесконечное количество лучей, суммарная яркость которых была бы зависима от свойства отражающего объекта и начальной яркости падающего луча.

Для уменьшения нагрузки можно ограничивать число лучей, число соударений, но эти ограничения приводят к тому, что на картинке получаются куцые куски тени и неестественно яркие пятна от источников света. То есть, недостаточный объем данных приводит к появлению шума, и вся сложность состоит в том, что нельзя один раз просчитать сцену и дальше менять в ней только то, что изменяется от кадра к кадру, так как любой движущийся объект изменяет все маршруты всех лучей. От каждого движения камеры и объектов нужно “пересобирать” всю сцену заново, поэтому фильмы и создаются рендер-фермами и многочисленными серверами, которые непрерывно, по несколько месяцев, рендерят графику. Но, к сожалению, в реальном времени в играх такое сделать пока невозможно.

Поэтому встает вопрос, как же Nvidia и партнеры выкрутились из данной ситуации: чем они пожертвовали, чтобы добиться трассировки в реальном времени?

Две половинки одного целого

Если внимательно проследить, как поэтапно рендерится картинка, то можно увидеть, что где-то после пятой интеграции сами тени и свет уже едва заметно меняются. Становится понятно, где, что и как будет выглядеть в финальном виде. Для этого в принципе и нужен в софте real-time, чтобы можно было покрутить источники света, понять где будут какие-то блики и затем запустить финальную отрисовку. Остается узнать, как по мутной картинке можно понять финальный кадр. На самом деле компьютер сначала моделирует исходную зашумленную картинку, затем анализирует ее и на основе полученных данных рисует уже другую – итоговую. Как показала практика, такой подход проще в плане ресурсных затрат.

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

Сегодня утверждается, что подобная техника убирания шумов работает только на тендерных ядрах в будущих картах от Nvidia. Но на самом деле эту технологию массово показали только сейчас, а появилась она, судя по всему, в прошлом году, так как в октябре, на одном из мероприятий, Unity показала эту самую технологию по удалению шума в трассировке в реальном времени.

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


Отражение лучей света от поверхности

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

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

Когда мы увидим выгоду?

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

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

Текст: Алексей Харитонов, QA, Bytex


За последние несколько лет метод трассировки лучей (ray tracing), похоже, стал "мечтой номер один" мира 3D-графики в реальном времени. Интерес к этой технологии рендеринга вырос до максимума, когда молодой исследователь Дэниел Похл (Daniel Pohl) объявил о своём проекте в области этой технологии ещё в 2004 году.

Причина интереса широких масс публики к работе заключалась, по большей мере, в том, что Похл сфокусировался на знаменитых играх id Software Quake III, Quake IV и шутере-франшизе Quake Wars 3D. Исследователь привлёк немало внимания со стороны прессы, а геймеры начали мечтать о светлом будущем, когда их любимые игры будут просчитываться по методу трассировки лучей и избавятся от растеризации.

Intel довольно быстро обратила внимание на проект, и компании он показался идеальным способом для оправдания увеличения числа ядер в процессорах. Компания быстро запустила собственную исследовательскую программу, и сегодня Intel никогда не упускает возможность подчеркнуть, что трассировка лучей является будущим 3D-игр в реальном времени. Но так ли это на самом деле? Какие технологические реальности скрываются за маркетинговой шумихой? Каковы реальные преимущества метода трассировки лучей? Можем ли мы ожидать, что трассировка лучей заменит растеризацию? Мы попытаемся ответить на эти вопросы.


Нажмите на картинку для увеличения.

Основные принципы

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

Но одного этого мало для вывода реалистичной сцены. Необходимо определить освещение пикселя, что требует проведения вторичных лучей (в отличие от первичных лучей, которые определяют видимость разных объектов, составляющих сцену). Чтобы рассчитать эффекты освещения сцены, проводятся вторичные лучи от точек пересечения к разным источникам света. Если эти лучи блокируются объектом, то данная точка находится в тени, которую отбрасывает рассматриваемый источник света. Иначе источник света влияет на освещение. Совокупность всех вторичных лучей, которые достигают источника света, определяет качество освещения, которое попадает на наш элемент сцены.

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

В итоге мы получаем несколько типов лучей. Первичные лучи используются для определения видимости объекта и напоминают своего рода Z-буфер, используемый в растеризации. А вторичные лучи разделяются на следующие:

  • лучи тени/освещения;
  • лучи отражения;
  • лучи преломления.

Классический алгоритм трассировки лучей. Нажмите на картинку для увеличения.

Данный алгоритм трассировки лучей является результатом работы Тёрнера Виттеда (Turner Whitted), исследователя, который изобрёл алгоритм 30 лет назад. До того времени алгоритм трассировки лучей работал только с первичными лучами. И улучшения, внесённые Виттедом, оказались гигантским шагом в сторону реализма рендеринга сцены.

Если вы знакомы с физикой, то наверняка обратили внимание на то, что алгоритм трассировки лучей работает "в обратную сторону" от явлений, протекающих в реальном мире. В отличие от распространённого в Средние века мнения, наши глаза не излучают лучи света, напротив, они получают лучи света от источников света, которые отражаются на различных объектах, окружающих нас. В принципе, так и работали самые первые алгоритмы трассировки лучей.

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


Оригинальный алгоритм трассировки лучей приводил к большому количеству ненужных расчётов. Нажмите на картинку для увеличения.

Преимущества трассировки лучей

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


Карта окружения (environment map) даёт хорошее приближение к симуляции отражений окружающей среды, однако метод трассировки лучей может симулировать даже отражения глаз машины Луиджи на капоте. Нажмите на картинку для увеличения.

Отражения - это одна из областей, в которых метод трассировки лучей превосходно показывает себя. Сегодня в 3D-движках современных игр отражения рассчитываются с помощью карт окружений (environment map). Эта технология даёт хорошее приближение к отражениям объектов, расположенных "в бесконечности" или в окружающей среде (как можно видеть по названию), но для близко расположенных объектов подход показывает свои ограничения.

Разработчики игр-гонок, в частности, создали свои трюки для симуляции отражений близких объектов с помощью так называемых динамических кубических карт (dynamic cube maps). Камера располагается на уровне машины геймера, после чего проводится рендеринг в основных направлениях. Затем результаты рендеринга сохраняются в кубических картах, которые и используются для вывода отражений.


Динамические кубические карты могут симулировать отражения близких объектов, например, самолёта на чайнике. Но они не справляются с отражениями частей объекта друг на друге, например, носика чайника на его корпусе. Нажмите на картинку для увеличения.

Конечно, у динамических кубических карт тоже есть свои недостатки. Довольно накладно по вычислительной мощности просчитывать несколько результатов рендеринга, и, чтобы производительность не падала слишком сильно, кубические карты не пересчитываются столько раз, сколько основная картинка. Это может привести к небольшой задержке отражений. Чтобы снизить нагрузку на скорость заполнения (fill rate), рендеринг выполняется с меньшим разрешением, что может привести к появлению пикселизации в отражениях. Наконец, эта технология часто ограничивается машиной геймера, а все другие объекты используют более простые (сферические) карты окружения.

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


Метод трассировки лучей позволяет симулировать отражения частей объектов друг на друге, например, отражение зеркала бокового вида на корпусе машины. Нажмите на картинку для увеличения.

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

Но на практике эта задача слишком тяжёлая с точки зрения вычислений, да и ошибки прозрачности тоже возможны, поскольку выполняется сортировка полигонов, а не пикселей. Существует несколько технологий, позволяющий обойти сортировку полигонов сцены (таких как depth peeling и A-буферы), но на данный момент ни одну из них нельзя назвать спасительной. Вместе с тем алгоритм трассировки лучей позволяет элегантно справиться с эффектами прозрачности.


Правильная обработка эффектов прозрачности с помощью растеризатора требует использования сложных алгоритмов, например, A-буферов. Нажмите на картинку для увеличения.

Другим важным преимуществом является расчёт теней. В мире растеризации стандартом стала технология карт теней (shadow mapping). Но у неё несть несколько проблем, такие как "лесенки" на контурах и объём используемой памяти. Алгоритм трассировки лучей позволяет решить проблему теней весьма элегантно, не прибегая к сложным алгоритмам, используя тот же объект-примитив и не требуя дополнительную память.

Наконец, ещё одним сильным преимуществом метода трассировки лучей является "родная" возможность работы с искривлёнными поверхностями. Уже несколько лет у современных GPU есть поддержка искривлённых поверхностей (она то появляется, то исчезает по мере выхода новых драйверов и новых архитектур). Но если растеризаторам приходится выполнять изначальный проход тесселяции, чтобы создавать треугольники (это единственный примитив, с которым может работать движок растеризации), то движок методом трассировки лучей может просто работать с пересечением лучей, без точного математического определения поверхности.

Мифы о трассировке лучей

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

Начнём с того, что многие геймеры считают алгоритм трассировки лучей принципиально лучше, чем растеризации, поскольку его используют в фильмах. Это не так. Большинство фильмов с синтезированной/рисованной картинкой (например, все фильмы студии Pixar) используют алгоритм под названием REYES, который базируется на растеризации. Pixar добавила трассировку лучей к своему движку рендеринга RenderMan только позднее, во время производства мультфильма "Тачки/Cars". Но даже для этого фильма метод трассировки лучей использовался избирательно, чтобы не перегрузить существующие вычислительные мощности. До этого проекта Pixar использовала внешний модуль для ограниченного использования метода трассировки лучей, например, для эффектов затенения ambient occlusion (AO).


Нажмите на картинку для увеличения.

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

Ниже показано, как алгоритм растеризации работает с каждым треугольником сцены.

  • Определяется набор пикселей, который покрывает каждый треугольник;
  • для каждого задействованного пикселя его глубина сравнивается с глубиной соседнего пикселя.

Основное ограничение метода растеризации касается числа треугольников. Алгоритм имеет сложность O(n), где n - число треугольников. Алгоритм в данном случае имеет линейную сложность в зависимости от числа треугольников, поскольку для каждого кадра составляется список треугольников, которые нужно обработать, один за другим.

Напротив, алгоритм трассировки лучей работает следующим образом.

Для каждого пикселя кадра:

  • проводится луч, определяющий, какой треугольник является самым близким;
  • для каждого треугольника рассчитывается расстояние от треугольника до плоскости вывода изображения.

Как видим, последовательность обработки стала обратной. В первом случае мы брали каждый полигон и смотрели, какие пиксели он покрывает. А во втором случае мы брали каждый пиксель и смотрели, какой полигон ему соответствует. Поэтому можно подумать, что метод трассировки лучей меньше зависит от числа полигонов, чем метод растеризации, поскольку число полигонов не влияет на основной цикл. Но на практике это не так. Фактически, чтобы определить, какой треугольник будет пересекаться с лучом, нам нужно обработать все треугольники сцены. Здесь, конечно, защитники метода трассировки лучей скажут, что не нужно обрабатывать все треугольники сцены с каждым лучом. Если использовать соответствующий тип структуры данных, то очень легко организовать треугольники таким образом, чтобы только небольшой их процент тестировался с каждым лучом, то есть мы получаем, что метод трассировки лучей имеет сложность O(log n), где n - число полигонов.

Да, доводы можно признать верными. Но защитники метода трассировки лучей немного лукавят в том, что то же самое верно и для растеризации. Игровые движки уже многие годы используют BSP-деревья (binary space partitioning) и другие методы, ограничивающие число полигонов, которые нужно рассчитать для каждого кадра. Ещё один спорный момент - такие структуры более всего эффективны для статических данных. Всё, что нам нужно: рассчитать данные один раз, после чего просто обеспечивать к ним доступ, и это даёт очень хорошие результаты. Но что делать с динамическими данными? В данном случае данные придётся пересчитывать для каждого изображения, а для этого нет никаких чудесных формул. Всё равно придётся изучать каждый полигон.

Простой алгоритм?

Последний миф касается естественной простоты и элегантности алгоритма трассировки лучей. Конечно, алгоритм трассировки лучей можно написать несколькими строчками кода (некоторые алгоритмы умещаются на одной стороне "визитки"), но высокопроизводительный алгоритм трассировки лучей - совершенно иное дело.

Дэвид Любке (David Luebke), инженер nVidia, сделал следующий комментарий, прекрасно отражающий реальность: "Растеризация выполняется быстро, но необходимо тщательно продумывать то, как выполнять сложные визуальные эффекты. Метод трассировки лучей поддерживает сложные визуальные эффекты, но необходимо тщательно продумывать то, как сделать его быстрым".


Код минимального алгоритма трассировки лучей, написанный Полем Хекбертом (Paul Heckbert), чтобы уместить его на "визитке". Нажмите на картинку для увеличения.

Всё, что вам нужно - прочитать несколько статей про оптимизации, которые необходимо внести в алгоритм трассировки лучей, чтобы оценить слова Любке. Например, самые мощные алгоритмы трассировки лучей не обрабатывают лучи независимо, они используют так называемые наборы лучей, что позволяет оптимизировать производительность с лучами, которые имеют одинаковое происхождение и одинаковое направление. Подобная оптимизация прекрасно подходит для функциональных блоков "одна инструкция много данных" (SIMD) внутри CPU и GPU, а также очень эффективна для основных лучей с некоторой степенью когерентности (сонаправленности) или для теневых лучей. Но, с другой стороны, оптимизация уже не подходит для лучей преломления или отражения.

Более того, как указывает Даниэль Похл в своей статье по поводу Quake Wars RT , использование наборов лучей может стать проблематичным с прозрачными текстурами (знаменитые альфа-текстуры, используемые для деревьев), поскольку если все лучи в наборе не будут вести себя одинаково (некоторые затрагивают поверхность, другие проходят через неё), то возникающие дополнительные издержки могут стать намного больше, чем преимущества от оптимизаций, которые даёт использование наборов лучей.


Визуализация "стоимости" рендеринга каждого пикселя, где красные пиксели являются самыми "дорогими". Как можно видеть, рендеринг деревьев стоит очень дорого в версии Quake Wars с методом трассировки лучей. Нажмите на картинку для увеличения.

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

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

Теперь, когда мы рассеяли некоторые мифы, связанные с трассировкой лучей, давайте обратимся на реальные проблемы, которые связаны с этой технологией.

И начнём мы с основной проблемы, связанной с данным алгоритмом рендеринга: его медлительностью. Конечно, некоторые энтузиасты скажут, что это уже не проблема, поскольку алгоритм трассировки лучей хорошо распараллеливается, а число ядер процессора увеличивается каждый год, поэтому мы должны увидеть линейный рост производительности трассировки лучей. Кроме того, исследования по поводу оптимизаций, которые можно применить к трассировке лучей, всё ещё находятся в младенческом состоянии. Если посмотреть первые 3D-ускорители и сравнить их с тем, что доступно сегодня, то для оптимизма действительно есть поводы.

Однако такая точка зрения не учитывает важный момент: самое интересное в методе трассировки лучей заключается во вторичных лучах. На практике расчёт картинки только с первичными лучами не даст особого улучшения качества изображения по сравнению с классическим алгоритмом с Z-буфером. Но проблема со вторичными лучами заключается в том, что у них абсолютно отсутствует когерентность (сонаправленность). При переходе от одного пикселя к другому нужно рассчитывать совершенно разные данные, что сводит на нет все обычные техники кэширования, очень важные для хорошей производительности. Это означает, что расчёт вторичных лучей очень сильно зависит от подсистемы памяти, в частности, от её задержек. Это наиболее худший сценарий из возможных, поскольку из всех характеристик памяти именно задержки менее всего улучшились за последние годы, и нет никаких поводов считать, что ситуация исправится в обозримом будущем. Довольно легко увеличить пропускную способность памяти, используя несколько чипов параллельно, но задержки всё равно останутся прежними.


У видеокарт задержки памяти (latency) уменьшаются намного медленнее, чем увеличивается пропускная способность (bandwidth). Если последняя улучшается в 10 раз, то задержки улучшаются только в два раза.

Причина популярности GPU кроется в том, что создание "железа", специализирующегося на растеризации, оказалось очень эффективным решением. При растеризации доступ к памяти выполняется когерентно (параллельно), независимо от того, работаем мы с пикселями, текселями или вершинами. Поэтому небольшие кэши, дополненные серьёзной пропускной способностью памяти, будут идеальным решением для получения великолепной производительности. Конечно, увеличение пропускной способности обходится весьма накладно, но такое решение вполне подходит при условии окупаемости. Напротив, сегодня нет каких-либо решений для ускорения доступа к памяти при расчёте нескольких лучей. Именно по этой причине метод трассировки лучей никогда не будет таким эффективным, как растеризация.

Ещё одна характерная проблема метода трассировки лучей касается сглаживания (AA). Лучи проводятся в виде простой математической абстракции, и реального размера они не учитывают. Проверка на пересечение с треугольником является простой логической функцией, которая даёт ответ "да" или "нет", но не даёт таких деталей, как "луч на 40% пересекает треугольник". Прямым следствием такого эффекта будет появление "лесенок".

Чтобы решить эту проблему, было предложено несколько технологий, таких как трассировка пучков (beam tracing) и трассировка конусов (cone tracing), которые учитывают толщину лучей, но их сложность не позволяет получить эффективную реализацию. И единственной технологией, которая может дать хорошие результаты, является расчёт большего числа лучей, чем есть пикселей, то есть суперсэмплинг (рендеринг при большем разрешении). Вряд ли стоит лишний раз упоминать, что эта технология намного более накладна по вычислительной мощности, чем мультисэмплинг, использующийся в современных GPU.

Гибридный движок рендеринга?

Если вы прочитали всю статью до этого места, то наверняка уже подумываете о том, что метод трассировки лучей пока что не может заменить растеризацию, но, возможно, стоит смешать две технологии вместе? И на первый взгляд кажется, что две технологии дополняют друг друга. Легко представить растеризацию треугольников для определения видимой картинки, получая преимущество от великолепной производительности данной технологии, после чего будет применяться трассировка лучей только для некоторых поверхностей, добавляя реализм там, где это необходимо, например, для добавления теней или получения хороших отражений и прозрачности. Собственно, такой подход Pixar и использовала для мультфильма "Тачки/Cars". Геометрические модели создаются с помощью REYES, а трассировка лучей используется "по требованию" там, где нужно симулировать определённые эффекты.


Для мультфильма "Тачки/Cars" Pixar использовала гибридный движок рендеринга, сочетающий REYES для визуализации и трассировку лучей "по требованию" для отражений и ambient occlusion. Нажмите на картинку для увеличения.

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

Более того, что касается производительности, то самой большой проблемой является доступ к памяти, связанный со вторичными лучами, а эти лучи как раз и необходимы в нашем гибридном движке рендеринга. Так что прирост производительности не будет таким большим, как можно было бы ожидать изначально. Поскольку большая часть времени рендеринга будет уходить на вторичные лучи, то выигрыш от отсутствия просчёта основных лучей будет мизерным.

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

Как мы уже неоднократно упомянули в нашей статье, предстоит решить немало проблем, прежде чем метод трассировки лучей станет достойной альтернативой растеризации в сфере рендеринга в реальном времени. И если вдуматься, то станет ли этот метод панацеей от всех бед? Преимущества метода трассировки лучей не такие революционные, чтобы оправдать существенное снижение производительности. Сильные моменты алгоритма связаны с отражениями и прозрачностью, поскольку два этих эффекта сложнее всего вывести на существующих алгоритмах растеризации. Но, опять же, такой ли это серьёзный недостаток? Мир вокруг нас не состоит целиком из очень прозрачных или сияющих объектов, поэтому наше зрение вполне может удовлетвориться грубым приближением.

Если посмотреть на последние автосимуляторы, например, Gran Turismo и Forza, то там достаточно хорошо заметно вполне удовлетворительное качество рендеринга, пусть даже отражения на корпусе полностью лживые. И точное отражение зеркала заднего вида на краске вряд ли можно считать достаточным, чтобы признать ещё один шаг в сторону фотореализма.


На самом деле отражений нет. Например, зеркало бокового вида на корпусе машины не отражается. Но нужен ли вам "честный" рендеринг Audi R8 с помощью метода трассировки лучей? Нажмите на картинку для увеличения.

Большинство энтузиастов считают, что метод трассировки лучей по своей природе даёт лучшее изображение, чем растеризация - но они часто основывают своё мнение на картинке, произведённой оффлайновым движком, работающем не в реальном времени. Однако результаты таких движков намного лучше, чем способности современных игр. Кроме того, вокруг трассировки лучей наблюдается определённая путаница. Энтузиасты часто сравнивают с растеризацией фотореалистичные изображения, которые получены комбинацией нескольких техник, таких как трассировка лучей для прямых отражений, метод излучательности (radiosity) для диффузного отражения, фотонное отображение (photon mapping) для каустики и т.д. Все эти технологии сочетаются, чтобы обеспечить максимально фотореалистичное качество.


Чтобы получить фотореалистичный рендеринг, необходимо сочетать несколько технологий. Трассировка лучей сама по себе недостаточна для симуляции сложных взаимодействий между материалами разных типов и света. Нажмите на картинку для увеличения.

В своей базовой версии метод трассировки лучей, если рассматривать существующие попытки реализации в реальном времени, подходит только для идеальных отражений и жёстких (резких) теней. Игра Doom 3 несколько лет назад доказала, что можно создать надёжный 3D-движок, который идеально бы справлялся с динамическими тенями и через растеризацию, но если посмотреть в прошлое, то игра показала и то, что жёсткие тени не являются реалистичными.


Нажмите на картинку для увеличения.

Чтобы создавать мягкие тени или диффузные отражения (такие, какие вы видите на текстурированном металле, например), требуются более развитые техники трассировки лучей, такие как трассировка путей (path tracing) и или распределённая трассировка лучей (distributed ray tracing). Но подобные техники требуют существенно большего количества лучей, так что они пока ещё слабо подходят для реального времени.

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

Впрочем, нас это вряд ли убедит. В любом случае, мы ещё пока далеки до того времени, когда мы сможем пожертвовать производительностью из-за элегантности и простоты. Просто посмотрите, что произошло за последние 10 лет в мире оффлайнового рендеринга. Если рендеринг одного кадра мультфильма "История игрушек/Toy Story" выполнялся, в среднем, за два часа, то кадр из мультфильма "Рататуй/Ratatouille" - уже за шесть с половиной часов, несмотря на вычислительную мощность, которая увеличилась в промежутке между двумя картинами более чем в 400 раз. Другими словами, чем больше вычислительной мощности и ресурсов вы предоставляете компьютерным художникам, тем быстрее они их поглощают.

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

Министерство образования Российской Федерации

Московский Государственный Институт Электроники и Математики

(Технический Университет)

Кафедра Информационно-коммуникационных

технологий

Курсовая работа на тему:

«Анализ перспективности использования метода трассировки лучей в 3D моделировании»

Выполнили :

Гулиян Борис

Подзоров Иван

Группа С -35

Москва 2010

1. 3D-графика. Введение

3. Алгоритмы трассировки лучей

4. Основные достоинства и недостатки трассировки лучей

5. Применение метода трассировки лучей

6. Эксперимент.

Задача: "Анализ перспективности использования метода трассировки лучей в 3D моделировании"

Постановка задачи

Ознакомиться с методом трассировки лучей и его использованием в области 3D графики, поставить эксперимент с использованием одного из алгоритмов трассировки лучей.

В нашем эксперименте мы рассматриваем:
1)производительность алгоритма трассировки лучей в зависимости от числа полигонов модели(в качестве модели берутся 3 шара: матовый, прозрачный и зеркальный).

2)Анализ полученых изображений с применением трассировки лучей и без нее.

В Качестве среды для проведения эксперимента используется ПО Blender.

3D-графика. Введение.

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

Любое 3D изображение определяется следующими параметрами и объектами:

· Геометрия (построенная медели)

· Материалы (информация о визуальных свойствах модели)

· Источники света (настройки направления, мощности, спектра освещения)

· Виртуальные камеры (выбор точки и угла построения проекции)

· Силы и воздействия (настройки динамических искажений объектов, применяется в основном в анимации)

· Дополнительные эффекты (объекты, имитирующие атмосферные явления: свет в тумане, облака, пламя и пр.)

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

Основной проблемой 3D графики и моделировния является получение максимально фотореалистичной картинки с минимальными затратами ресурсов компьютера и времени на обработку сцены. Так как в различных областях существую различные потребности - создаются различные идеи и алгоритмы для решения конкретно поставленной задачи. Одной из таких идей является трассировка лучей, которую мы рассмотрим в нашей работе.

Прямая и обратная трассировка лучей

Трассировка лучей - это метод обработки 3D моделей с получением фотореалистичного изображения, в котором учитывается взаимное расположение объектов, а также такие физические свойства объектов как отражающая и преломляющая способность.

Существует 2 метода трассировки лучей: прямой и обратный

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

При обратной трассировке лучей все лучи исходят из глаза наблюдателя, тем самым определяя сцену с объектами, для которых в дальнейшем будет произведена обработка. Данный метод позволяет не обрабатывать объекты, не попадающие в видимую область, что значительно уменьшает объем необходимых вычислений.

Все алгоритмы трассировки лучей основаны на методе обратной трассировки лучей.

Алгоритмы трассировки лучей

Рассмотрим принципиальный алгоритм трассировки(Рис. 1). Объектом возьмем сферу.

1. Для каждого пиксела на экране из глаза наблюдателя выпускается луч.

2. После пересечения лучом объекта определяется:

· Прозрачность/непрозрачность объекта. Если объект прозрачный, то из пересечения испускается луч преломления, если непрозрачный - не испускается.

· Освещенность/тень. Из точки пересечения лучом сферы испускаются луч к источнику света (или поочередно для каждого источника света, если их несколько). Если этот луч не пересекает другие непрозрачные объекты или поверхности, значит, источник света непосредственно влияет на освещенность данной точки. Если имеется несколько источников света, то по влиянию всех лучей вычисляется результат, определенный RGB-значением данной точки.

· Отражающая способность. Если объект способен отражать лучи, то из точки пересечения лучом сферы испускается отраженный луч к объектам, которые будут отражены в сфере.

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

· лучи преломления;

· лучи тени/освещения;

· лучи отражения.

Рис. 1 Схема алгоритма трассировки лучей


Все остальные алгоритмы основаны на алгоритме, показанном выше, и призваны оптимизировать вычисления.

kd-дерево

Алгоритм построения kd-дерева можно представить следующим образом (будем называть прямоугольный параллелепипед англоязычным словом "бокс" (box)).

1. "Добавить" все примитивы в ограничивающий бокс. Т. е построить ограничивающий все примитивы бокс, который будет соответствовать корневому узлу дерева.

2. Если примитивов в узле мало или достигнут предел глубины дерева, завершить построение.

3. Выбрать плоскость разбиения, которая делит данный узел на два дочерних . Будем называть их правым и левым узлами дерева.

4. Добавить примитивы, пересекающиеся с боксом левого узла в левый узел, примитивы, пересекающиеся с боксом правого узла в правый.

5. Для каждого из узлов рекурсивно выполнить данный алгоритм начиная с шага 2.

Regular grid

Все 3D пространство разбивается на мелкую регулярную сетку, состоящую из N*N*N кубиков. Идея заключается в том, что можно пробегать только по тем по кубикам, через которые пошел луч.

Метод не используется на практике.

Д остоинства и недостатки

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

1. Возможность рендеринга гладких объектов без интерполяции их полигональными поверхностями (например, треугольниками).

2. Вычислительная сложность метода слабо зависит от сложности сцены.

3. Высокая алгоритмическая распараллеливаемость вычислений - можно параллельно и независимо трассировать два и более лучей.

4. При методе трассировки лучей отражения отображаются идеально (рис.2), причём без сложных алгоритмов, поскольку всё просчитывается основным алгоритмом рендеринга.

font-size:14.0pt"> Рис. 2 Отражения двух зеркальных шаров друг в друге

У метода трассировки лучей имеются недостатки, наблюдаемые во всех алгоритмах которые определяют сферу использования данного метода.

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

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

3. Отсутствие аппаратной поддержки метода (все GPU специализируются на растеризации).

4. Ещё одна характерная проблема метода трассировки лучей касается сглаживания (AA). Лучи проводятся в виде простой математической абстракции , и реального размера они не учитывают. Проверка на пересечение с треугольником является простой логической функцией, которая даёт ответ "да" или "нет", но не даёт таких деталей, как "луч на 40% пересекает треугольник". Прямым следствием такого эффекта будет появление "лесенок"(Рис.3).

Рис. 3 сглаживание теней

И единственной технологией, которая может дать хорошие результаты, является расчёт большего числа лучей, чем есть пикселей, то есть суперсэмплинг(Oversampling или Anti-Aliasing) (рендеринг при большем разрешении).

Также следует помнить, что скорость рендеринга и его качество методом трассировки лучей сильно зависит от оптимизации кода.

Применение метода трассировки лучей

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

· 3D мультипликация;

· Спецэффекты киноиндустрии;

· Реалистичный рендеринг фотоизображения;

· Cad - системы.

Специальные термины:

Полигональная сетка-совокупность вершин и полигонов, которая определяет форму отображаемого объекта.

Рендеринг (Render) - (англ. rendering - «визуализация») - процесс получения изображения по модели.

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


Рис 4. полигональная сетка

Эксперимент.

В качестве ПО для проведения эксперемента мы выбрали 3D - редактор Blender.

Он достаточно легок в освоении и содержит в себе все не обходимые функции:

· Рендеринг изображения с возможность подключения и отключения трассировщика.

· Oversampling(anti-aliasing или сглаживание )

Мы замеряли время, необходимое на рендеринг 3-х различных сфер(стеклянной, зеркальной и матовой) на различных Уравных Multeris (каждый уровень повышает число полигонов в 4 раза). При повышении уровня время считали от 0.

0 " style="margin-left:48.35pt;border-collapse:collapse">

Ур. Multeris

Время рендеринга каждого ур. с 0

Без RayT [c]

С RayT [c]

0,53

3,36

0,46

0,54

2,84

0,55

3,02

0,61

3,85

0,96

5,96

10,64

29,12

43,9

Таблица 1.

Рендеринг производился с максимальными параметрами, чтобы увеличить разницу в скорости обработки.

В результате видим, что время на обработку трех сфер с уровнем 4 (по 256 полигонов на каждой сфере) меньше, чем время, потраченное на обработку сфер с уровнем 2 (по 16 полигонов).


Рис 5. полигональные сетки для различных уровней

Итог

Из проведенного эксперимента видно, что время, затраченное на рендеринг 3-х шаров с использованием трассировки существенно больше, чем время, затраченное на рендеринг без использования трассировки лучей. Но в процессе эксперемента было замечено интересное наблюдение: время на обработку 3, 4 и 5 уровневых моделей меньше времени обработи двухуровневой модели.

Анализ полученый изображений:
1)На картинке, полученной без использования трассировки (далее А), видно, что прозрачная сфера не дает эффект линзы (применение альфа-канала), в то время как на картинке, с использованием трассировки лучей (далее Б) прозрачный шар увеличивает объекты за ним(рис. 6).

Рис. 6 прозрачные сферы (слева alpha-канал, справа трассировка лучей)


2)На картинке А нет зеркального шара, т. к получение отражения на нем основано на трассировке лучей(рис. 7).

Рис 7. модель эксперимента (сверху alpha-канал, снизу трассировка лучей).


3)На рисунке 8 видно, что при рендеренге без использывания трассировки лучей, происходит освещение внутренних полостей, куда, по логике, свет проникать не должен.


Рис.8 Падения света на впадены в шаре(слева А, справа Б)

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

Которая должна наконец-то принести метод трассировки лучей в игры. Трассировка лучей (рейтрейсинг) - метод далеко не новый. Применительно к играм о нём говорили ещё лет 20 назад, а сам термин относительно компьютерной графики возник в 1982 году, и с тех пор в играх метод так и не появился. Но что это вообще такое?

Трассировка лучей - это метод построения трёхмерных моделей, в котором используется принцип, аналогичный реальным физическим процессам. То есть для построения объекта система отслеживает траекторию виртуального луча от экрана к этому самому объекту.

В реальности мы видим не объекты сами по себе, а отражённый от них свет. Исключением являются объекты, которые сами служат источниками света. Метод трассировки лучей использует примерно те же принципы применительно к виртуальной среде.

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

Для примера, для построения изображения разрешением 1024 х 768 пикселей посредством рейтрейсинга необходимо сформировать 768 432 луча. При этом каждый луч может как отражаться, так и преломляться, что в итоге приводит к увеличению количества трассируемых лучей в несколько раз. И если в случае обычных методов рендеринга необходимые полигоны просто нужно успеть отрисовать, то в случае рейтрейсинга каждый луч требует постоянных математических расчётов, начиная с момента его испускания.

Именно по этой причине рейтрейсинг уже давно используется там, где не требуется работа метода в режиме реального времени. В играх же нам как раз нужно последнее. Nvidia утверждает, что ранее не существовало графических адаптеров, имеющих достаточную производительность для такой работы. А теперь вроде есть Volta, которая должна аппаратно ускорять трассировку лучей. Правда, никаких подробностей на этот счёт нет. Пока совершенно неясно, насколько активно разработчики игр смогут использовать рейтрейсинг, и что мы, как игроки, получим на выходе. Но приблизительно понять это можно уже сейчас. К примеру, по видео ниже.

Это ожидаемая многими Metro Exodus. Она будет первой игрой AAA-класса, использующей технологию RTX. Но видите ли вы на видео что-то необычное? Если не знать, куда смотреть, большинство просто не поймёт, что данный ролик демонстрирует нам ожидаемую последние пару десятков лет технологию. На самом же деле рейтрейсинг в Metro будет использоваться для некоторых эффектов глобального освещения. Если точнее, для модели затенения ambient occlusion и для непрямого освещения indirect lighting. При этом классическая растеризация никуда не денется.

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

Думаю, стоит также отдельно оговорить момент касательно технологии Nvidia RTX и карт Volta. Как известно, в продаже имеется лишь две модели, использующие GPU GV100 - это ускоритель Tesla V100 и более близкий к народу, но безумно дорогой адаптер Titan V. Несомненно, в ближайшее время на рынок выйдут массовые видеокарты с GPU Volta, но ведь они достаточно долгое время будут в меньшинстве. Особенно, учитывая, что текущих адаптеров Pascal большинству хватает с головой. Да и майнинговую лихорадку, которая может испортить выход новинок, сбрасывать со счетов не стоит. Так неужели насладиться рейтрейсингом в играх в ближайшие пару лет смогут лишь владельцы новых карт Nvidia? Вовсе нет. Да, Nvidia говорит о неких возможностях GPU Volta аппаратно ускорять трассировку лучей, хотя подробностей пока ноль. Но выводить на потребительский рынок технологию, которая недоступна массам - глупость. Поэтому у нас есть Microsoft DirectX Raytracing (DXR) - набор новых инструментов и методов для API DirectX 12. Именно он будет отвечать как за аппаратную, так и за сугубо программную реализацию рейтрейсинга в играх. Последний вариант как раз интересен всем тем, у кого не будет видеокарт Volta.

Однако тут возникает вопрос касательно оптимизации и производительности. Приведу пример. Многие знают технологии Nvidia HairWorks и AMD TressFX Hair, которые позволяют более реалистично отрисовать волосы и шерсть. И эти технологии оптимизированы под адаптеры GeForce и Radeon соответственно. При этом на «чужих» видеокартах они прекрасно работают, только сильнее просаживают fps. Аналогичная ситуация будет и с рейтрейсингом в играх. Вопрос лишь в том, насколько сильно у адаптеров, не относящихся к поколению Volta, будет просаживаться fps при активации эффектов, связанных с трассировкой лучей. Это, к слову, ещё один довод в пользу того, что в ближайшие годы рейтрейсинг в играх будет в зачаточном виде.

Что касается AMD, компания на днях также представила собственный движок трассировки лучей - Radeon Rays 2.0. Только он базируется уже на основе API Vulkan. При этом ни о каких ограничениях в поколении GPU указано не было.

Подводя итог, можно сказать, что для большинства трассировка лучей в играх в ближайшие годы либо будет недоступна из-за недостаточно производительных видеокарт, либо пройдёт мимо попросту по той причине, что пользователь не заметит и не поймёт, где именно она применяется в той или иной сцене той или иной игры. Хотя, кто знает, возможно, всё будет совершенно иначе.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter .