Войти
ПрограммированиеФорумГрафика

Screen Space shadows, BGFX (DirectX, HLSL)

#0
12:23, 30 июля 2019

Приветствую.

Пытаюсь сделать работающую имплементацию теней в 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/

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


#1
(Правка: 12:30) 12:29, 30 июля 2019

Screen Space shadows, BGFX интересное название.
Он позволяет в один проход рендерить тени ?

Или все равно надо отрендерить в frame depth текстуру, а во втором проходе вывести тени на 3д модели.

#2
(Правка: 14:03) 13:56, 30 июля 2019

DEN 3D
s_depthTexture - это линейный depth? попробуй подебажить цветами каждую переменную. ну например ViewSpace3DToUV(UVToViewSpace3D()) должна давать исходную точку. потом можно добавлять invLightDirectionView, картинка должна смещаться по направлению лайта. сделай SAMPLES_COUNT=1, и увеличивай STEP_SIZE - должны появляться хоть какие то тени. ну и т.д

а вобще странный дифф. почему не float diff = length(vp_ray) - vp_ray_depth?

#3
(Правка: 20:03) 20:01, 30 июля 2019

У меня была такая же проблема. Решил путём создания матрицы вида вручную.
[D3D9] Screen Space Ray Traced Shadows

#4
15:06, 31 июля 2019

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..

Или я ошибаюсь?

#5
15:07, 31 июля 2019

ENAleksey
> У меня была такая же проблема. Решил путём создания матрицы вида вручную.
Спасибо. У меня раньше бывало так, что в некоторых случаях надо брать не чистую View-матрицу, а обратную и транспонированную (Inversed Transposed).

#6
16:37, 31 июля 2019

Глубина стандартно работает. Как именно называется, линейно или нелинейно - непонятно. Не преобразованное, не инвертированное. Как есть.

gl_Position = mul(u_viewProj, worldPosition);
v_depth = gl_Position.z;
#7
19:34, 31 июля 2019

DEN 3D
Линейная глубина означает, что значение глубины дано в юнитах (длина единичного вектора в world space), она меняется от 0 до far plane. Нелинейная - это та глубина, которая находится depth текстуре, она уже меняется не линейно относительно мировых координат от 0 до 1.

Вот функция

vec3 UVToViewSpace3D(vec2 uv, float viewDepth)
  расчитана на то, что в нее передается линейная глубина, а в коде ты пихаешь ее прямо из depth буфера.

#8
(Правка: 4:08) 4:05, 2 авг. 2019

В общем, потестировал, поотлаживал..

Функции "UVToViewSpace3D" и "ViewSpace3DToUV" взаимно работают,
на выводе в цвет протестировал..

Выводил в цвет также исходную глубину из текстуры, выглядит вот так:

RawDepthFromTexture | Screen Space shadows, BGFX (DirectX, HLSL)

Похоже, она в диапазоне 0..1, т.е. нелинейная.

Если преобразовать в линейную и вывести в цвет, то получается всё белое, что похоже
на правду, т.к. значения от zNear до ZFar больше единицы и соответственно выходят за диапазон
цвета.

Перевод глубины в линейный диапазон делаю вот такой функцией:

float getDepthValue(float depthTextureValue, float near, float far)
{
  return near * far / (far - depthTextureValue* (far - near));
}

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

SS Shadows Test | Screen Space shadows, BGFX (DirectX, HLSL)

Зависимости направления тени от вращения камеры теперь больше нету.
Более того, тестировал корректность направления света по всем осям. Всё корректно.

Но есть две проблемы:

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

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

SS Bug 1 | Screen Space shadows, BGFX (DirectX, HLSL)
SS Bug 2 | Screen Space shadows, BGFX (DirectX, HLSL)

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

SS Bug 3 | Screen Space shadows, BGFX (DirectX, HLSL)

Вот думаю, что дальше делать.. В каком направлении идти) Ух и капризные, однако эти 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;
}

Может есть какие мысли?

Заранее благодарю..

#9
10:32, 2 авг. 2019

Почему глубина как АО выглядит?

#10
19:53, 3 авг. 2019

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

Вот тут в статье есть картинка: https://www.geeks3d.com/20091216/geexlab-how-to-visualize-the-dep… ffer-in-glsl/

Там показано как выглядит линейная и нелинейная глубины.

#11
0:38, 4 авг. 2019

DEN 3D
Нет, не похоже. Сам смотри, у тебя глубина на плоскости пола должна быть градиентом, а она монотонная. Это точно не буффер глубины.

#12
11:11, 5 авг. 2019

MrOcelot
Хм.. да, может действительно, что-то не так с глубиной..

В движке она считается вот так:

gl_Position = mul(u_viewProj, worldPosition);
v_depth = gl_Position.z;
#13
13:55, 5 авг. 2019

DEN 3D
Точно ничего с юниформами не напутал? Этот код такую картинку не даст, там даже четко видно ореолы под объектами, которые гораздо темнее чем самая дальняя часть буффера. Реализован ли какой-нибудь из алгоритмов для АО? Есть вероятность, что вместо глубины в шейдер передается не та текстура.

#14
19:54, 5 авг. 2019

MrOcelot
Да, действительно, после прохода шейдера теней был второй проход с наложением AO.
Т.е. поверх результата моего шейдера наложилось AO.

Отключил AO и получил следующие результаты по глубине:

Исходная глубина из Depth-текстуры (non-linearized depth):

Non Linearized Z Buffer | Screen Space shadows, BGFX (DirectX, HLSL)

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

Linearized Z Buffer with camera zNear/zFar | Screen Space shadows, BGFX (DirectX, HLSL)

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

Linearized Z Buffer with custom zNear/zFar | Screen Space shadows, BGFX (DirectX, HLSL)

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

float linearizeDepth(float z, float n, float f)
{
    return (2.0 * n) / (f + n - z * (f - n));
}

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

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

ПрограммированиеФорумГрафика