OpenGL Computing Normals и TBN Matrix из буфера глубины (реализация SSAO)

Я реализую SSAO в OpenGL, следуя этому руководству: Джон Чепмен ССАО

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

Цель этого метода — избавиться от классического артефакта реализации, где плоские грани объектов отображаются серым цветом.

У меня такая же реализация с двумя небольшими отличиями

  • Я не использую текстуру Noise для вращения моего ядра, поэтому у меня есть артефакты с полосами, это нормально
  • У меня нет доступа к буферу с нормалями на пиксель, поэтому мне приходится вычислять свою нормаль и матрицу TBN только с использованием буфера глубины.

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

Скриншоты с ядром 8 образцов и радиусом .1. первый — только результат прохода SSAO, а второй — отладочный рендеринг сгенерированных нормалей.

Вот код для функции, которая вычисляет матрицу Normal и TBN



mat3 computeTBNMatrixFromDepth(in sampler2D depthTex, in vec2 uv)
{
// Compute the normal and TBN matrix
float ld = -getLinearDepth(depthTex, uv);
vec3 x = vec3(uv.x, 0., ld);
vec3 y = vec3(0., uv.y, ld);
x = dFdx(x);
y = dFdy(y);
x = normalize(x);
y = normalize(y);
vec3 normal = normalize(cross(x, y));
return mat3(x, y, normal);
}


И шейдер SSAO

#include "helper.glsl"
in vec2 vertTexcoord;
uniform sampler2D depthTex;

const int MAX_KERNEL_SIZE = 8;
uniform vec4 gKernel[MAX_KERNEL_SIZE];

// Kernel Radius in view space (meters)
const float KERNEL_RADIUS = .1;

uniform mat4 cameraProjectionMatrix;
uniform mat4 cameraProjectionMatrixInverse;

out vec4 FragColor;void main()
{
// Get the current depth of the current pixel from the depth buffer (stored in the red channel)
float originDepth = texture(depthTex, vertTexcoord).r;

// Debug linear depth. Depth buffer is in the range [1.0];
float oLinearDepth = getLinearDepth(depthTex, vertTexcoord);

// Compute the view space position of this point from its depth value
vec4 viewport = vec4(0,0,1,1);
vec3 originPosition = getViewSpaceFromWindow(cameraProjectionMatrix, cameraProjectionMatrixInverse, viewport, vertTexcoord, originDepth);

mat3 lookAt = computeTBNMatrixFromDepth(depthTex, vertTexcoord);
vec3 normal = lookAt[2];

float occlusion = 0.;

for (int i=0; i<MAX_KERNEL_SIZE; i++)
{
// We align the Kernel Hemisphere on the fragment normal by multiplying all samples by the TBN
vec3 samplePosition = lookAt * gKernel[i].xyz;

// We want the sample position in View Space and we scale it with the kernel radius
samplePosition = originPosition + samplePosition * KERNEL_RADIUS;

// Now we need to get sample position in screen space
vec4 sampleOffset = vec4(samplePosition.xyz, 1.0);
sampleOffset = cameraProjectionMatrix * sampleOffset;
sampleOffset.xyz /= sampleOffset.w;

// Now to get the depth buffer value at the projected sample position
sampleOffset.xyz = sampleOffset.xyz * 0.5 + 0.5;

// Now can get the linear depth of the sample
float sampleOffsetLinearDepth = -getLinearDepth(depthTex, sampleOffset.xy);

// Now we need to do a range check to make sure that object
// outside of the kernel radius are not taken into account
float rangeCheck = abs(originPosition.z - sampleOffsetLinearDepth) < KERNEL_RADIUS ? 1.0 : 0.0;

// If the fragment depth is in front so it's occluding
occlusion += (sampleOffsetLinearDepth >= samplePosition.z ? 1.0 : 0.0) * rangeCheck;
}

occlusion = 1.0 - (occlusion / MAX_KERNEL_SIZE);
FragColor = vec4(vec3(occlusion), 1.0);
}

Вычисленные нормали из буфера глубины
SSAO Pass

Обновление 1

Это изменение функции расчета TBN дает те же результаты

mat3 computeTBNMatrixFromDepth(in sampler2D depthTex, in vec2 uv)
{
// Compute the normal and TBN matrix
float ld = -getLinearDepth(depthTex, uv);
vec3 a = vec3(uv, ld);
vec3 x = vec3(uv.x + dFdx(uv.x), uv.y, ld + dFdx(ld));
vec3 y = vec3(uv.x, uv.y + dFdy(uv.y), ld + dFdy(ld));
//x = dFdx(x);
//y = dFdy(y);
//x = normalize(x);
//y = normalize(y);
vec3 normal = normalize(cross(x - a, y - a));
vec3 first_axis = cross(normal, vec3(1.0f, 0.0f, 0.0f));
vec3 second_axis = cross(first_axis, normal);
return mat3(normalize(first_axis), normalize(second_axis), normal);
}

0

Решение

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

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

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

vec3 viewFromDepth(in sampler2D depthTex, in vec2 uv, in vec3 view)
{
float ld = -getLinearDepth(depthTex, uv);

/// I assume ld is negative for fragments in front of the camera
/// not sure how getLinearDepth is implemented

vec3 z_scaled_view = (view / view.z) * ld;

return z_scaled_view;
}

mat3 computeTBNMatrixFromDepth(in sampler2D depthTex, in vec2 uv, in vec3 view)
{
vec3 view = viewFromDepth(depthTex, uv);

vec3 view_normal = normalize(cross(dFdx(view), dFdy(view)));
vec3 first_axis = cross(view_normal, vec3(1.0f, 0.0f, 0.0f));
vec3 second_axis = cross(first_axis, view_normal);

return mat3(view_normal, normalize(first_axis), normalize(second_axis));
}
1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]