Скорая Компьютерная Помощь г. Калуга

Полный спектр компьютерных услуг!

Здравствуйте, гость ( Вход | Регистрация )

> Внимание!

  • Вся информация, расположенная в данном и других разделах форума получена из открытых источников (интернет-ресурсы, средства массовой информации, печатные издания и т.п.) и/или добавлена самими пользователями. Администрация форума предоставляет его участникам площадку для общения / размещения файлов / статей и т.п. и не несет ответственности за содержание сообщений, а также за возможное нарушение авторских, смежных и каких-либо иных прав, которое может повлечь за собой информация, содержащаяся в сообщениях.
Ремонт компьютеров в калуге Рекламное место сдается
 
Ответить в эту темуОткрыть новую тему
> SSLR: Screen Space Local Reflections в AAA-играх
Decker
сообщение 27.11.2014, 17:37
Сообщение #1


Администратор
*****

Группа: Главные администраторы
Сообщений: 14349
Регистрация: 12.10.2007
Из: Twilight Zone
Пользователь №: 1




Алгоритмы*,
Game Development*,
Работа с анимацией и 3D-графикой*


Привет, друг! В этот раз я опять подниму вопрос о графике в ААА-играх. Я уже разобрал методику HDRR (не путать с HDRI) тут и чуть-чуть поговорил о коррекции цвета. Сегодня я расскажу, что такое SSLR (так же известная как SSPR, SSR): Screen Space Local Reflections. Кому интересно — под кат.




Введение в Deferred Rendering


Для начала введу такое понятие как Deferred Rendering (не путать с Deferred Shading, т.к. последнее относится к освещению). В чем суть Deferred Rendering? Дело в том, что все эффекты (такие как освещение, глобальное затенение, отражения, DOF) можно отделить от геометрии и реализовать эти эффекты как особый вид постпроцессинга. К примеру, что нужно, чтобы применить DOF (Depth Of Field, размытие на дальних расстояниях) к нашей сцене? Иметь саму сцену (Color Map) и иметь информацию о позиции текселя (другими словами на сколько пиксель далеко от камеры). Далее — все просто. Применяем Blur к Color Map, где радиус размытия будет зависеть от глубины пикселя (из Depth Map). И если взглянуть на результат — чем дальше объект, тем сильнее он будет размыт. Так что же делает методика Deferred Rendering? Она строит так называемый GBuffer, который, обычно, в себя включает три текстуры (RenderTarget):



  • Color map (информация о диффузной составляющий или просто цвет пикселя)

  • Normal map (информация о нормали “пикселя”)

  • Depth map (информация о позиции “пикселя”, тут храним только глубину)





В случае с Color map, Normal map вроде все понятно, это обычные Surface.Color текстуры: пожалуй, за исключением того, что вектор нормали может лежать в пределах [-1, 1] (используется простая упаковка вектора в формат [0, 1]).



А вот ситуация с Depth map становится непонятной. Как же Depth map хранит в себе информацию о позиции пикселя, да еще и одним числом? Если говорить сильно упрощенно, трансформация примитива:



float4 vertexWVP = mul(vertex, World*View*Projection);



Дает нам экранные координаты:



float2 UV = vertexWVP.xy;



И некоторую информацию о том, насколько “далеко” от камеры пиксель:



float depth = vertexWVP.z / vertexWVP.w;



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



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



float3 GetPosition(float2 UV, float depth)
{
float4 position = 1.0f;

position.x = UV.x * 2.0f - 1.0f;
position.y = -(UV.y * 2.0f - 1.0f);

position.z = depth;

//Transform Position from Homogenous Space to World Space
position = mul(position, InverseViewProjection);

position /= position.w;

return position.xyz;
}



Напомню, что для построения GBuffer необходима такая методика как MRT (Multiple Render Targets), которая рисует модель сразу в несколько Render Target (причем в каждом RT содержится разная информация). Одно из правил MRT — размерность всех Render Target должна быть одинаковой. В случае Color Map, Normal MapSurface.Color: 32-ух битная RT, где на каждый канал ARGB приходится по 8 бит, т.е. 256 градаций от 0 до 1.



Благодаря такому подходу мы можем применять сложные эффекты к любой геометрии, например самый популярный Space Screen эффект: SSAO (Space Screen Ambient Occlusion). Этот алгоритм анализирует буферы глубины и нормали, считая уровень затенения. Весь алгоритм я описывать не буду, он уже описывался на хабре, скажу лишь то, что задача алгоритма сводится к трассировки карты глубины: у нас есть набор случайных векторов, направленных из считаемого “пикселя” и нам нужно найти кол-во пересечений с геометрией.



Пример эффекта (слева без SSAO, справа с SSAO):





Так же Deferred Shading является Space Screen эффектом. Т.е. для каждого источника света на экране (без всяких оптимизаций) мы рисуем квад в режиме Additive в так называемый RenderTarget: Light Map. И зная мировую позицию “пикселя”, его нормаль, позицию источника света — мы можем посчитать освещенность этого пикселя.



Пример Deferred Shading (освещение выполнено отложено, после отрисовки геометрии):








Достоинства и проблемы Space Screen эффектов


Самый главный плюс Space Screen эффектов — независимость сложности эффекта от геометрии.



Самый главный минус — локальность всех эффектов. Дело в том, что мы постоянно будем сталкиваться с Information Lost, во многих случаях это сильно зависит обзора, поскольку SSE зависит от смежных глубин текселей, которые могут быть сгенерированы любой геометрией.



Ну и стоит отменить, что Screen Space эффекты выполняются полностью на GPU и являются пост-процессингом.




Наконец SSLR


После всей теории мы подошли к такому эффекту, как Screen Space Local Reflections: локальные отражения в экранном пространстве.



Для начала разберемся с перспективной проекцией:







Горизонтальный и вертикальный угол зрения задается FOV (обычно 45 градусов, я предпочитаю 60 градусов), в виртуальной камере они разные т.к. учитывается еще и Aspect Ratio (соотношение сторон).



Окно проекции (там, где мы оперируем UV-space данными) — это, что мы видим, на то мы проецируем нашу сцену.

Передняя и задняя плоскости отсечения это соответственно Near Plane, Far Plane, задаются так же в проекцию как параметры. Делать в случае Deferred Rendering слишком большим значением Far Plane стоит, т.к. точность Depth Buffer сильно упадет: все зависит от сцены.



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



float3 GetPosition(float2 UV, float depth)
{
float4 position = 1.0f;

position.x = UV.x * 2.0f - 1.0f;
position.y = -(UV.y * 2.0f - 1.0f);

position.z = depth;

position = mul(position, InverseViewProjection);

position /= position.w;

return position.xyz;
}



После нам нужно найти вектор взгляда на этот пиксель:



float3 viewDir = normalize(texelPosition - CameraPosition);

В качестве CameraPosition выступает позиция камеры.

И найти отражение этого вектора от нормали в текущем пикселе:



float3 reflectDir = normalize(reflect(viewDir, texelNormal));

Далее задача сводится к трассировке карты глубины. Т.е. нам нужно найти пересечение отраженного вектора с какой-либо геометрией. Понятное дело, что любая трассировка производится через итерации. И мы в них сильно ограниченны. Т.к. каждая выборка из Depth Map стоит времени. В моем варианте мы берем некоторое начальное приближение L и динамически меняем его исходя из расстояния между нашим текселем и позицией, которую мы “восстановили”:



float3 currentRay = 0;

float3 nuv = 0;
float L = LFactor;

for(int i = 0; i < 10; i++)
{
currentRay = texelPosition + reflectDir * L;

nuv = GetUV(currentRay); // проецирование позиции на экран
float n = GetDepth(nuv.xy); // чтение глубины из DepthMap по UV

float3 newPosition = GetPosition2(nuv.xy, n);
L = length(texelPosition - newPosition);
}



Вспомогательные функции, перевод мировой точки на экранное пространство:



float3 GetUV(float3 position)
{
float4 pVP = mul(float4(position, 1.0f), ViewProjection);
pVP.xy = float2(0.5f, 0.5f) + float2(0.5f, -0.5f) * pVP.xy / pVP.w;
return float3(pVP.xy, pVP.z / pVP.w);
}



После завершения итераций мы имеет позицию “пересечения с отраженной геометрией”. А наше значение nuv будет проекцией этого пересечения на экран, т.е. nuv.xy – это UV координаты в экранном нашем пространстве, а nuv.z это восстановленная глубина (т.е. abs(GetDepth(nuv.xy)-nuv.z) должен быть очень маленьким).



В конце итераций L будет показывать расстояние отраженного пикселя. Последний этап — собственно добавление отражения к Color Map:



float3 cnuv = GetColor(nuv.xy).rgb;
return float4(cnuv, 1);



Разбавим теорию иллюстрациями, исходное изображение (содержание Color Map из GBuffer):





После компиляции шейдера (отражения) мы получим следующую картину (Color Map из GBuffer + результат шейдера SSLR):







Не густо. И тут стоит еще раз напомнить, что Space-Screen эффекты это сплошной Information Lost (примеры выделены в красные рамки).



Дело в том, что если вектор отражения выходит за пределы Space-Screen – информация о Color-карте становится недоступной и мы видим Clamping нашего UV.



Чтобы частично исправить эту проблему, можно ввести дополнительный коэффициент, который будет отражать “дальность” отражения. И далее по этому коэффициенту мы будем затенять отражение, проблема частично решается:



L = saturate(L * LDelmiter);

float error *= (1 - L);



Результат, отражение умноженное на error (попытка убрать артефакт SSLR — information lost):







Уже лучше, но мы замечаем еще одну проблему, что будет, если вектор отразится в направлении камеры? Clamping’а UV происходить не будет, однако, несмотря на актуальность UV (x > 0, y > 0, x < 1, y < 1) он будет неверным:







Эту проблему так же можно частично решить, если как-нибудь ограничить углы допустимых отражений. Для этого идеально подходит фишка с углами от эффекта Френеля:



float fresnel = dot(viewDir, texelNormal);

Чуть-чуть модифицируем формулу:



float fresnel = 0.0 + 2.8 * pow(1+dot(viewDir, texelNormal), 2);

Значения Френеля, с учетом Normal-маппинга (значения fresnel-переменной для SSLR-алгоритма):







Те области, которые отражаются в “камеру” будут черными, и их мы не учитываем (взамен можно сделать fade в кубическую текстуру).



Отражение, умноженное на error и fresnel (попытка удалить большую часть артефактов SSLR):







Кстати, значение Fresnel стоит лимитировать по какому-либо параметру, т.к. из-за “шероховатости” нормалей значение будет на порядок больше единицы (или другого числа-лимитера).



И завершающий этап сегодняшний статьи — это размытие отражений, т.к. идеальное отражение только у зеркала. Степень размытия можно считать как 1-error (чем дальше отраженный пиксель — тем сильнее размыт). Это будет своеобразный вес размытия и хранить его можно в альфа-канале RT-отражений.



Результат (финальное изображение с убранными артефактами и с размытыми отражениями):








Заключение


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



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



Удачных разработок! wink.gif

Original source: habrahabr.ru (comments, light).

Читать дальше


--------------------

Вернуться в начало страницы
 
+Ответить с цитированием данного сообщения

Ответить в эту темуОткрыть новую тему
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 

Рекламное место сдается Рекламное место сдается
Текстовая версия Сейчас: 4.1.2025, 9:43
Рейтинг@Mail.ru
Яндекс.Метрика Яндекс цитирования