Вычислительные нормы OpenGL и матрица TBN из буфера глубины (реализация SSAO)

Я реализую SSAO в OpenGL, следуя этому руководству: Jhon Chapman SSAO

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

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

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

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

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

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

Вот код функции, которая вычисляет матрицу нормалей и 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

Обновление 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);
}

person Lucas C.    schedule 27.10.2016    source источник


Ответы (1)


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

Но давайте тогда представим, что одна из этих поверхностей находится намного дальше от камеры. Поскольку функции 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));
}
person bofjas    schedule 28.10.2016
comment
Замечу, что я понимаю, что вы имеете в виду, говоря, что я отбираю только одну глубину ... Использование dFdx и dFdy на моих векторах фактически сравнивает две позиции глубины для оси x и две позиции глубины для оси y и возвращает мне производные по этим двум осям. Я попытался изменить свою функцию TBN, чтобы отразить ваш комментарий, но в итоге получил тот же результат, см. Обновление 1 - person Lucas C.; 30.10.2016
comment
Прости. Ты прав. Я особо не использовал функции dFdx / dFdy. Я не понимал, что они так работают. Во всяком случае, я все еще думаю, что проблема в том, что вы не рассчитываете его в пространстве просмотра. Пожалуйста, посмотрите мою правку - person bofjas; 31.10.2016