0

このチュートリアルに従って、OpenGL で SSAO を実装しています: Jhon Chapman SSAO

基本的に、説明されている手法では、フラグメントの法線に沿った向きの Hemispheric カーネルを使用します。次に、サンプルのビュー スペースの z 位置が、そのスクリーン スペースの深度バッファー値と比較されます。深度バッファの値が大きい場合は、サンプルがジオメトリで終了したことを意味するため、このフラグメントを遮る必要があります。

この手法の目的は、オブジェクトの平面がグレー表示される従来の実装アーティファクトを取り除くことです。

私は2つの小さな違いがある同じ実装を持っています

  • カーネルを回転させるためにノイズ テクスチャを使用していないので、バンディング アーティファクトがありますが、今のところ問題ありません。
  • ピクセル単位の法線を含むバッファーにアクセスできないため、深度バッファーのみを使用して法線と TBN マトリックスを計算する必要があります。

アルゴリズムは正常に機能しているようです。フラグメントがオクルードされているのがわかりますが、まだ顔がグレー表示されています... IMO TBN マトリックスを計算する方法から来ています。法線は問題ないように見えますが、カーネルが適切に位置合わせされていないようで、サンプルが顔で終わるため、何かが間違っているに違いありません。

スクリーンショットは、8 サンプルのカーネルと半径 .1 のものです。1 つ目は SSAO パスの結果のみで、2 つ目は生成された法線のデバッグ レンダーです。

これは、Normal および TBN Matrix を計算する関数のコードです。



    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);
}
4

1 に答える 1

1

問題は、おそらく座標系が混在していることだと思います。線形深度と組み合わせてテクスチャ座標を使用しています。画面のやや左側に面している 2 つの垂直面をイメージできます。どちらも垂直面から同じ角度を持っているため、同じ法線を持つ必要がありますか?

しかし、これらのサーフェスの 1 つがカメラからかなり離れていると想像してみましょう。fFdx/fFdy 関数は基本的に隣接するピクセルとの違いを教えてくれるので、カメラから遠く離れた表面は、カメラに近い表面よりも 1 ピクセルの線形深度の差が大きくなります。しかし、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));
}
于 2016-10-28T00:07:04.320 に答える