Приветствую.
Пытаюсь сделать работающую имплементацию теней в screen-space. Использую библиотеку Bgfx (кроссплатформенная библиотека для работы с графикой и шейдерами) в режиме DirectX.
Пока что делаю для направленного источника света. В теории вроде бы всё понятно и просто - рейкаст в скринспэйсе.. Но на практике, что-то не получается получить корректные результат. Либо теней вообще нет, либо они выводятся, но в том направлении (и при этом направление меняется от камеры) - в общем, косяки с переводами в разные системы координат.
Приведу основные фрагменты кода шейдера (синтаксис шейдеров в Bgfx немного отличается, в целом похож больше на GLSL, но с небольшими изменениями - например, при умножении вектора на матрицу - первым аргументом указывается матрица)..
vec3 UVToViewSpace3D(vec2 uv, float viewDepth) { vec4 pos = vec4( ( uv.x * 2.0) - 1.0, ( uv.y * -2.0) + 1.0, 1.0, 1.0); vec3 viewPos = mul( invProjectionMatrix, pos).xyz; viewPos *= viewDepth; return viewPos; } vec2 ViewSpace3DToUV( vec3 viewPos) { vec4 posNDC = mul( projectionMatrix, vec4( viewPos, 1.0)); posNDC.xy /= posNDC.w; vec2 uv = vec2( 0.5 * posNDC.x + 0.5, -0.5 * posNDC.y + 0.5); return uv; } #define SAMPLES_COUNT 40 #define STEP_SIZE 0.01 float GetScreenSpaceShadowing( vec2 uv, float viewDepth, vec3 invLightDirectionView) { vec3 vp0 = UVToViewSpace3D( uv, viewDepth); for ( int i = 0; i < SAMPLES_COUNT; i++) { vec3 vp_ray = vp0 + invLightDirectionView * ( float)i * STEP_SIZE; vec2 vp_ray_uv = ViewSpace3DToUV( vp_ray); float vp_ray_depth = texture2D( s_depthTexture, vp_ray_uv).r; vec3 vp_ray_d = UVToViewSpace3D( vp_ray_uv, vp_ray_depth); float diff = vp_ray.z - vp_ray_d.z; if ( diff > 0.01) return 0; } return 1; } void main( ) { // ........ Пример использования функции в основном коде шейдера float rawDepth = texture2D( s_depthTexture, v_texCoord0).r; vec3 invLightDirectionView = normalize( mul( ( mat3)viewMatrix, -lightWorldDirection)); float ss_shadowing = GetScreenSpaceShadowing( v_texCoord0, rawDepth, invLightDirectionView); // ......... }
Прошу проанализировать и оценить код в шейдере, возможно, я не вижу какой-то очевидной ошибки.. Пробовал менять знак направления света, знак "больше" на "меньше" в функции рейкаста - всё бесполезно...
Код функций перевода во ViewSpace из NDC и обратно взят с этой статьи:
https://habr.com/ru/post/310360/
Пробовал другие варианты этих функций из других источников, результат был, но не корректный (тени не в том направлении, изменяющимся от вектора камеры). С этими же функциями вообще нет никаких теней... Но в теории ведь вроде всё правильно.. Вобщем, не знаю что дальше делать, уже три дня сижу безрезультатно...
Screen Space shadows, BGFX интересное название.
Он позволяет в один проход рендерить тени ?
Или все равно надо отрендерить в frame depth текстуру, а во втором проходе вывести тени на 3д модели.
DEN 3D
s_depthTexture - это линейный depth? попробуй подебажить цветами каждую переменную. ну например ViewSpace3DToUV(UVToViewSpace3D()) должна давать исходную точку. потом можно добавлять invLightDirectionView, картинка должна смещаться по направлению лайта. сделай SAMPLES_COUNT=1, и увеличивай STEP_SIZE - должны появляться хоть какие то тени. ну и т.д
а вобще странный дифф. почему не float diff = length(vp_ray) - vp_ray_depth?
У меня была такая же проблема. Решил путём создания матрицы вида вручную.
[D3D9] Screen Space Ray Traced Shadows
Ziltop
Это метод расчёта теней в screen-space пространстве. Избавляет от необходимости рендеринга depth-текстуры с позиции источника света. Всё делается на основании данных о глубине с позиции основной камеры. Второй проход, конечно понадобится, чтобы полученное затенение применить к сцене, но в случае использования техники Deferred Shading нам нужно будет в финальном проходе просто нарисовать квадрат на весь экран и скомбинировать полученную текстуру затенения с текстурой цвета сцены.
xruck
> s_depthTexture - это линейный depth?
Ммм.. Честно говоря сам путаюсь пока что в этом.. Если это depth-текстура, привязанная к фреймбуферу, то там разве может быть несколько вариантов формата представления глубины?
Линейный, как я понимаю, это когда значения в диапазоне от 0 до 1. Предполагаю, что формат такой.
> попробуй подебажить цветами каждую переменную.
Спасибо, попробую. Тоже была такая идея.
> а вобще странный дифф. почему не float diff = length(vp_ray) - vp_ray_depth?
Мм.. а зачем брать длину вектора? Мы же берём разность только с Z-компонент, т.е. работаем только с одной осью..
Ещё кстати, есть мысль, что по идее в этой строчке
vec3 vp_ray_d = UVToViewSpace3D(vp_ray_uv, vp_ray_depth);
вызов "UVToViewSpace3D" лишний, т.к. полученная глубина из depth-текстуры уже во ViewSpace..
т.е. по идее можно сделать float diff = vp_ray.z - vp_ray_depth..
Или я ошибаюсь?
ENAleksey
> У меня была такая же проблема. Решил путём создания матрицы вида вручную.
Спасибо. У меня раньше бывало так, что в некоторых случаях надо брать не чистую View-матрицу, а обратную и транспонированную (Inversed Transposed).
Глубина стандартно работает. Как именно называется, линейно или нелинейно - непонятно. Не преобразованное, не инвертированное. Как есть.
gl_Position = mul(u_viewProj, worldPosition); v_depth = gl_Position.z;
DEN 3D
Линейная глубина означает, что значение глубины дано в юнитах (длина единичного вектора в world space), она меняется от 0 до far plane. Нелинейная - это та глубина, которая находится depth текстуре, она уже меняется не линейно относительно мировых координат от 0 до 1.
Вот функция
vec3 UVToViewSpace3D(vec2 uv, float viewDepth)
расчитана на то, что в нее передается линейная глубина, а в коде ты пихаешь ее прямо из depth буфера.
В общем, потестировал, поотлаживал..
Функции "UVToViewSpace3D" и "ViewSpace3DToUV" взаимно работают,
на выводе в цвет протестировал..
Выводил в цвет также исходную глубину из текстуры, выглядит вот так:

Похоже, она в диапазоне 0..1, т.е. нелинейная.
Если преобразовать в линейную и вывести в цвет, то получается всё белое, что похоже
на правду, т.к. значения от zNear до ZFar больше единицы и соответственно выходят за диапазон
цвета.
Перевод глубины в линейный диапазон делаю вот такой функцией:
float getDepthValue(float depthTextureValue, float near, float far) { return near * far / ( far - depthTextureValue* ( far - near)); }
Теперь передаю в функции преобразования координат глубину, преобразованную в линейный формат, результат стал больше похож на адекватный:

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


Вплоть до почти полного затенения:

Вот думаю, что дальше делать.. В каком направлении идти) Ух и капризные, однако эти Screen-Space эффекты..
Код основной функции сейчас выглядит так:
#define SAMPLES_COUNT 40 #define STEP_SIZE 0.01 float GetScreenSpaceShadowing(vec2 uv, float rawDepth, vec3 invLightDirectionView) { if ( rawDepth > 0.999999) return 1; // early-out float lDepth = getDepthValue( rawDepth, u_viewportOwnerNearClipDistance, u_viewportOwnerFarClipDistance); vec3 vp0 = UVToViewSpace3D( uv, lDepth); for ( int i = 0; i < SAMPLES_COUNT; i++) { vec3 vp_ray = vp0 + invLightDirectionView * ( float)i * STEP_SIZE; vec2 vp_ray_uv = ViewSpace3DToUV( vp_ray); float vp_ray_depth = texture2D( s_depthTexture, vp_ray_uv).r; float vp_ray_l_depth = getDepthValue( vp_ray_depth, u_viewportOwnerNearClipDistance, u_viewportOwnerFarClipDistance); vec3 vp_ray_d = UVToViewSpace3D( vp_ray_uv, vp_ray_l_depth); float diff = vp_ray.z - vp_ray_d.z; if ( diff > 0.0001) return 0; } return 1; }
Может есть какие мысли?
Заранее благодарю..
Почему глубина как АО выглядит?
MrOcelot
Полагаю, потому что это исходная глубина из текстуры, без линейного преобразования.
Вот тут в статье есть картинка: https://www.geeks3d.com/20091216/geexlab-how-to-visualize-the-dep… ffer-in-glsl/
Там показано как выглядит линейная и нелинейная глубины.
DEN 3D
Нет, не похоже. Сам смотри, у тебя глубина на плоскости пола должна быть градиентом, а она монотонная. Это точно не буффер глубины.
MrOcelot
Хм.. да, может действительно, что-то не так с глубиной..
В движке она считается вот так:
gl_Position = mul(u_viewProj, worldPosition); v_depth = gl_Position.z;
DEN 3D
Точно ничего с юниформами не напутал? Этот код такую картинку не даст, там даже четко видно ореолы под объектами, которые гораздо темнее чем самая дальняя часть буффера. Реализован ли какой-нибудь из алгоритмов для АО? Есть вероятность, что вместо глубины в шейдер передается не та текстура.
MrOcelot
Да, действительно, после прохода шейдера теней был второй проход с наложением AO.
Т.е. поверх результата моего шейдера наложилось AO.
Отключил AO и получил следующие результаты по глубине:
Исходная глубина из Depth-текстуры (non-linearized depth):

Глубина, преобразованная в линейный формат, используя zNear/zFar камеры:

Глубина, преобразованная в линейный формат, используя zNear = 1.0, zFar = 100.0

Для преобразования в линейный диапазон использовал функцию, отличную от предыдущей. Вот такую:
float linearizeDepth(float z, float n, float f) { return ( 2.0 * n) / ( f + n - z * ( f - n)); }
Она даёт результатом градиент, в отличие от предыдущей, которая давала просто везде белый цвет.
Но нормальных теней, увы, всё равно нет.. С этой функцией их либо вообще нет, либо есть равномерная чернота не там, где нужно..
Тема в архиве.