【问题标题】:OpenGL Computing Normals and TBN Matrix from Depth Buffer (SSAO implementation)来自深度缓冲区的 OpenGL 计算法线和 TBN 矩阵(SSAO 实现)
【发布时间】:2016-10-27 21:50:46
【问题描述】:

我正在按照本教程在 OpenGL 中实现 SSAO:Jhon Chapman SSAO

基本上,所描述的技术使用沿片段法线定向的半球内核。然后将样本的视图空间 z 位置与其屏幕空间深度缓冲区值进行比较。 如果深度缓冲区中的值较高,则意味着样本最终位于几何体中,因此应该遮挡该片段。

此技术的目标是摆脱经典的实现工件,即对象平面变灰。

我有相同的实现,但有两个小区别

  • 我没有使用噪波纹理来旋转我的内核,所以我有条带伪影,现在没问题
  • 我无法访问具有每像素法线的缓冲区,因此我必须仅使用深度缓冲区来计算我的法线和 TBN 矩阵。

算法似乎运行良好,我可以看到片段被遮挡,但我的脸仍然是灰色的...... IMO 它来自我计算 TBN 矩阵的方式。法线看起来不错,但一定有问题,因为我的内核似乎没有正确对齐,导致样本最终出现在脸上。

屏幕截图具有 8 个样本的内核和 0.1 的半径。第一个只是 SSAO pass 的结果,第二个是生成的法线的调试渲染。

这是计算 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);
}

更新 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);
}

【问题讨论】:

    标签: c++ opengl shader depth-buffer ssao


    【解决方案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));
    }
    

    【讨论】:

    • 我确定我理解你所说的我只采样一个深度的意思...在我的向量上使用 dFdx 和 dFdy 实际上会比较 x 和两个的两个深度位置y 轴的深度位置并将这两个轴上的导数返回给我。我尝试修改我的 TBN 函数以反映您的评论,但结果相同,请参阅 Update 1
    • 对不起。你是对的。我并没有真正使用过 dFdx/dFdy 函数。我没有意识到他们是这样工作的。无论如何,我仍然认为问题在于您没有在视图空间中计算它。请看我的编辑
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-27
    • 1970-01-01
    • 2014-02-18
    • 1970-01-01
    相关资源
    最近更新 更多