Ссылка на оригинал статьи:
http://http.developer.nvidia.com/GPUGems3/gpugems3_ch17.html
17.3 Отражения и преломления
В первую очередь позвольте представить зеркальные отражения. Сгенерировав расслоенную карту расстояний, мы представляем отражающие объекты и активируем программы вершинного и фрагментарного шейдера. Вершинный шейдер изменяет отражающие объекты в вырезанное пространство – и также в систему координат кубической карты – первым применением изменения моделирования, затем translatingвввввв опорную точку. Вектор вида V и собственный вектор N также находятся в этом пространстве. Листинг 17-3 показывает вершинный шейдер отражений и преломлений.
Пример 17-3. Вершинный шейдер отражения и преломления в HLSL
1. void SpecularReflectionVS(
2. in float4 Pos : POSITION, // Вершина в моделируемом пространстве
3. in float3 Norm : NORMAL, // Нормаль в моделируемом пространстве
4. out float4 hPos : POSITION, // Вершина в вырезанном пространстве
5. out float3 x : TEXCOORD1, // Вершина в пространстве кубической матрицы
6. out float3 N : TEXCOORD2, // Нормаль в пространстве кубической матрицы
7. out float3 V : TEXCOORD3, // Направление взгляда
8. uniform float4x4 WorldViewProj, // Моделирование в вырезанном пространстве
9. uniform float4x4 World, // Моделирование в мировом пространстве
10. uniform float4x4 WorldIT, // Обратное транспонирование Мира
11. uniform float3 RefPoint, // Опорная точка в мировом пространстве
12. uniform float3 EyePos // Глаз в мировом пространстве
13. ) {
14. hPos = mul(Pos, WorldViewProj);
15. float3 wPos = mul(Pos, World).xyz; // Мировое пространство
16. N = mul(Norm, WorldIT);
17. V = wPos - EyePos;
18. x = wPos - RefPoint; // Перевод в пространство кубической матрицы
19. }
void SpecularReflectionVS(
in float4 Pos : POSITION, // Vertex in modeling space
in float3 Norm : NORMAL, // Normal in modeling space
out float4 hPos : POSITION, // Vertex in clip space
out float3 x : TEXCOORD1, // Vertex in cube-map space
out float3 N : TEXCOORD2, // Normal in cube-map space
out float3 V : TEXCOORD3, // View direction
uniform float4x4 WorldViewProj, // Modeling to clip space
uniform float4x4 World, // Modeling to world space
uniform float4x4 WorldIT, // Inverse-transpose of World
uniform float3 RefPoint, // Reference point in world space
uniform float3 EyePos // Eye in world space
) {
hPos = mul(Pos, WorldViewProj);
float3 wPos = mul(Pos, World).xyz; // World space
N = mul(Norm, WorldIT);
V = wPos - EyePos;
x = wPos - RefPoint; // Translate to cube-map space
}
Фрагментарный шейдер вызывает функцию Hit(), которая возвращает первую целевую l, ее яркость Il и собственный вектор Nl. Если поверхность – идеальное зеркало, входящая яркость должна быть умножена на член Френеля, установленный для угла между нормальной поверхностью N и направлением отражения R. Мы применяем аппроксимацию функции Френеля (Schlick 1993), которая учитывает не только индекс преломления n, но и коэффициент затухания k (который присущ реалистичным металлам) (Bjorke 2004; Lazányi и Szirmay-Kalos 2005):
является функцией Френеля (при которой возможно отражение фотона) для перпендикулярного отражения. Заметьте, что Fp – константа для данного материала. Таким образом, это значение может быть вычислено на CPU из индекса преломления и коэффициента затухания и передано на GPU как глобальная переменная. Листинг 17-4 показывает фрагментарный шейдер одиночного отражения.
Пример 17-4. Фрагментарный шейдер одиночного отражения в HLSL
1. float4 SingleReflectionPS(
2. float3 x : TEXCOORD1, // Затененная точка в пространстве кубической матрицы
3. float3 N : TEXCOORD2, // Нормальный вектор
4. float3 V : TEXCOORD3, // Направление взгляда
5. uniform float3 Fp0 // Член Френеля при перпендикулярном направлении
6. ) : COLOR
7. {
8. V = normalize(V);
9. N = normalize(N);
10. float3 R = reflect(V, N); // Направление отражения
11. float3 Nl; // Нормальный вектор в целевой точке
12. float3 Il; // Освещение в целевой точке
13. // Трассируем луч x+R*d и получаем цель l, освещение Il, нормаль Nl
14. float3 l = Hit(x, R, Il, Nl);
15. // Отражение Френеля
16. float3 F = Fp0 + pow(1-dot(N, -V), 5) * (1-Fp0);
17. return float4(F * Il, 1);
18. }
19.
float4 SingleReflectionPS(
float3 x : TEXCOORD1, // Shaded point in cube-map space
float3 N : TEXCOORD2, // Normal vector
float3 V : TEXCOORD3, // View direction
uniform float3 Fp0 // Fresnel at perpendicular direction
) : COLOR
{
V = normalize(V);
N = normalize(N);
float3 R = reflect(V, N); // Reflection direction
float3 Nl; // Normal vector at the hit point
float3 Il; // Radiance at the hit point
// Trace ray x+R*d and obtain hit l, radiance Il, normal Nl
float3 l = Hit(x, R, Il, Nl);
// Fresnel reflection
float3 F = Fp0 + pow(1-dot(N, -V), 5) * (1-Fp0);
return float4(F * Il, 1);
}
Шейдер одиночного преломления аналогичен, но при вычислении направления следует использовать закон преломления, вместо закона отражения. Другими словами, операция отражения должна быть замещена операцией преломления, а входящую яркость должна быть умножена на 1 – F вместо F.
Свет может быть отражен или преломлен на идеальные отражатели или преломители несколько раз до того, как он достигнет диффузной поверхности. Если целевая точка находится на зеркальной поверхности, то отраженный или преломленный луч должен быть вычислен, а трассировка луча должна продолжаться, повторяя этот же алгоритм для второго, третьего и всех последующих лучей. Когда отраженный или преломленный луч вычислен, нам необходим собственный вектор на целевой поверхности, функция Френеля, и индекс преломления (в случае преломления). Эта информация может быть сохранена в двух кубических картах для каждого слоя. Первая кубическая карта включает свойства материала (отраженный цвет для диффузных поверхностей или член Френеля при перпендикулярном отражении для зеркальных поверхностей) и индекс преломления. Вторая кубическая карта сохраняет собственные вектора и значения расстояний.
Для разделения отражателей, преломителей и диффузных поверхностей, мы проверяем подпись индексов преломления. Отрицательные, нулевые и положительные значения определяют отражатель, диффузную поверхность или преломитель соответственно. Листинг 17-5 показывает код для вычисления множественного зеркального отражения/преломления.
Пример 17-5. Фрагментарный HLSL шейдер, вычисляющий множественное зеркальное отражение/преломление
Этот шейдер организует процесс трассировки луча в динамическом цикле.
1. float4 MultipleReflectionPS(
2. float3 x : TEXCOORD1, // Затененная точка в пространстве кубической матрицы
3. float3 N : TEXCOORD2, // Нормальный вектор
4. float3 V : TEXCOORD3, // Направление взгляда
5. uniform float3 Fp0, // Член Френеля при перпендикулярном направлении
6. uniform float3 n0 // Индекс преломления
7. ) : COLOR
8. {
9. V = normalize(V); N = normalize(N);
10. float3 I = float3(1, 1, 1); // Освещение пути
11. float3 Fp = Fp0; // Член Френеля при 90 градусах на первой цели
12. float n = n0; // Индекс преломления первой цели
13. int depth = 0; // Длина пути
14. while (depth < MAXDEPTH) {
15. float3 R; // Отражения или преломления dir
16. float3 F = Fp + pow(1-abs(dot(N, -V)), 5) * (1-Fp);
17. if (n <= 0) {
18. R = reflect(V, N); // Отражение
19. I *= F; // Отражение Френеля
20. } else { // Преломление
21. if (dot(V, N) > 0) { // Coming from inside
22. n = 1 / n;
23. N = -N;
24. }
25. R = refract(V, N, 1/n);
26. if (dot(R, R) == 0) // Направление преломления не существует
27. R = reflect(V, N); // Общее отражение
28. else I *= (1-F); // Преломление Френеля
29. }
30. float3 Nl; // Нормальный вектор в целевой точке
31. float4 Il; // Освещение в целевой точке
32. // Трассируем луч x+R*d и получаем цель l, освещение Il, нормаль Nl
33. float3 l = Hit(x, R, Il, Nl);
34. n = Il.a;
35. if (n != 0) { // Целевая точка на отражающей поверхности
36. Fp = Il.rgb; // Член Френеля при 90 градусах
37. depth += 1;
38. } else { // Целевая точка на диффузной поверхности
39. I *= Il.rgb; // Умножаем на освещение
40. depth = MAXDEPTH; // Завершение
41. }
42. N = Nl; V = R; x = l; // Целевая точка – следующая затененная точка
43. }
44. return float4(I, 1);
45. }
float4 MultipleReflectionPS(
float3 x : TEXCOORD1, // Shaded point in cube-map space
float3 N : TEXCOORD2, // Normal vector
float3 V : TEXCOORD3, // View direction
uniform float3 Fp0, // Fresnel at perpendicular direction
uniform float3 n0 // Index of refraction
) : COLOR
{
V = normalize(V); N = normalize(N);
float3 I = float3(1, 1, 1); // Radiance of the path
float3 Fp = Fp0; // Fresnel at 90 degrees at first hit
float n = n0; // Index of refraction of the first hit
int depth = 0; // Length of the path
while (depth < MAXDEPTH) {
float3 R; // Reflection or refraction dir
float3 F = Fp + pow(1-abs(dot(N, -V)), 5) * (1-Fp);
if (n <= 0) {
R = reflect(V, N); // Reflection
I *= F; // Fresnel reflection
} else { // Refraction
if (dot(V, N) > 0) { // Coming from inside
n = 1 / n;
N = -N;
}
R = refract(V, N, 1/n);
if (dot(R, R) == 0) // No refraction direction exists
R = reflect(V, N); // Total reflection
else I *= (1-F); // Fresnel refraction
}
float3 Nl; // Normal vector at the hit point
float4 Il; // Radiance at the hit point
// Trace ray x+R*d and obtain hit l, radiance Il, normal Nl
float3 l = Hit(x, R, Il, Nl);
n = Il.a;
if (n != 0) { // Hit point is on specular surface
Fp = Il.rgb; // Fresnel at 90 degrees
depth += 1;
} else { // Hit point is on diffuse surface
I *= Il.rgb;// Multiply with the radiance
depth = MAXDEPTH; // terminate
}
N = Nl; V = R; x = l; // Hit point is the next shaded point
}
return float4(I, 1);
}
17.4 Результаты
Наш алгоритм был использован в окружении DirectX 9 HLSL и протестирован на графической карте NVIDIA GeForce 8800 GTX. Для упрощения использования, мы представили отражающие объекты двумя слоями (содержащими лицевой и задний виды, соответственно) и растеризировали все диффузные поверхности в третий слой. Это упрощение помогло нам применить процесс расслаивания глубины и максимизировало число слоев до трех. Для управления более сложными отражающими объектами, нам понадобилось интегрировать алгоритм расслаивания глубины.
Все изображения были обработаны в разрешении 800x600. Кубические карты имели разрешение 6x512x512 и были обновлены в каждом кадре. Мы должны аккуратно выбрать размер шага линейного поиска и число итераций секущего поиска, потому что они могут значительно повлиять на качество изображения и скорость обработки. Если мы установили размер шага линейного поиска больше, чем расстояние между двумя соседними текселями карты расстояний, мы можем ускорить алгоритм, но также увеличить возможность потери отражения тонких объектов. Если геометрия, обработанная в слое, гладкая (состоит из большого количества многоугольников), линейный поиск может занять большее количество шагов, а секущий поиск может найти точное решение за несколько итераций. Как бы то ни было, когда значение расстояния в слое изменяется неожиданно, линейный поиск должен выполнить точные шаги во избежание потери вершин карты расстояний, которые соответствуют тонким отраженным объектам.
Другой источник субдискретизированных артефактов – ограничение числа слоев и разрешения карты расстояний. В отражениях мы можем видеть части сцены, которые частично или совсем не представлены в картах расстояния из-за преград и их касательной угловой ориентации к центру кубической карты. Рисунок 17-6 показывает эти артефакты и демонстрирует, как они могут быть уменьшены соответствующей установкой размера шага линейного поиска и числа итераций секущего поиска.
Рисунок 17-6 Особенности сглаживания, когда число линейных/секущих шагов увеличено до 15/1, 15/10, 80/1 и 80/10, соответственно
Заметьте, как ступенчатые артефакты устраняются при добавлении секущих шагов. Тонкие объекты, увеличенные в зеленых и красных рамках, требуют тонких линейных шагов из-за того, что в случае их потери, последующий секущий шаг не всегда способен быстро исправить ошибку. Заметьте, что на Рисунке 17-6b секущий поиск был более успешным для объекта стола, чем для рамы зеркала, потому что стол занимает большее количество текселей в карте расстояний. Сглаживание отражения затененной области под столом в левом зеркале Рисунков 17-6a и 17-6c, которая увеличена в синей рамке, вызвано ограничением слоев карты расстояний тремя. Эта область не представлена в слоях, поэтому даже секущий поиск не в состоянии вычислить точные отражения (Рисунки 17-6b и 17-6d). Мы можем объяснить эти типы проблем с увеличением числа слоев.
Рисунок 17-7 показывает изображения отражающего чайника в коробке, обработанные предлагаемым методом и с помощью Maya (для сравнения), ограничение максимального числа отражений до одного, двух и трех, соответственно.
Рисунок 17-7 Одиночные и множественные отражения на чайнике и сравнение с программной трассировкой луча в обработчике Maya 7
Заметьте высокую схожесть между изображениями, обработанными GPU и программным обеспечением. Стоит также отметить, что когда возрастает максимальное число отражений, частота кадров уменьшается незначительно. Этот сценарий показывает отличное динамическое ветвление производительности последних карт NVIDIA и также показывает, что раннее завершение луча может улучшить производительность даже в решениях GPU.
Рисунок 17-8 показывает изображения более сложной сцены, содержащей отражающую сферу и зеркало, когда максимальное значение переменной глубины луча возрастает от одного до восьми. Назначение всех диффузных поверхностей на один слой вызывает размытие мелких особенностей при возникновении видо-зависимых преград (например, в отражении фактуры лампы на зеркальной сфере). Мы могли исключить эти особенности, используя больше слоев, но при условии более сложной реализации и меньшей скорости представления.
Рисунок 17-8 Множественные отражения увеличивают значение переменной максимальной глубины от одного до восьми
Рисунок 17-9 показывает сферу, которая отражает и преломляет свет. В завершение Рисунок 17-10 содержит снимки видео с отражающим чайником в сочетании с окружающей средой. Видео представлено со скоростью 25 fps без использования min-max ускорения. Когда пара min-max вычислена для каждого слоя, скорость возрастает до 50 fps. Максимальное число отражений – два.
Рисунок 17-9 Сфера, сочетающая в себе отражение и преломление
Рисунок 17-10 Снимки видео при 25/50 FPS
17.5 Заключение
В этой главе мы представили робастный алгоритм для трассировки лучей в сценах, представленных расслоенными картами расстояний. Метод используется для вычисления одиночных и множественных отражений и преломлений на GPU. Важное преимущество трассировки лучей в обработанных геометрических представлениях вместо прямого применения классической трассировки луча в том, что эти методы могут быть объединены в игровые движки, применяемые для разработки игр (Wimmer и Bittner 2005), и использовать полную возможность современных графических карт. Этот метод в высокой степени эффективен, если геометрия, обработанная в слой, гладкая (то есть состоит из большого количества многоугольников). В данном случае, линейный поиск может занять большее количество шагов, а секущий поиск может находить точные решения всего за несколько итераций. Так или иначе, когда значение расстояния в слое изменяется случайным образом, линейный поиск должен проходить с точными шагами для предотвращения потери тонких отражающих объектов. Заметьте, что подобная проблема также возникает при упрощенном картировании, и было разработано уже много решений (Donnelly 2005; Policarpo и Oliveira 2007 – Глава 18 этой книги). В будущем, мы предполагаем включить похожие решения в наш механизм.
Комментариев нет:
Отправить комментарий