John Chapman ( http://john-chapman-graphics.blogspot.nl/2013/01/ssao-tutorial.html ) によるチュートリアルに従って、遅延レンダラーに SSAO を実装しています。SSAO シェーダーへの入力バッファーは次のとおりです。
- w 成分として線形化された深さを持つワールド空間の位置。
- ワールド空間法線ベクトル
- ノイズ 4x4 テクスチャ
最初に完全なシェーダーをリストしてから、簡単に手順を説明します。
#version 330 core
in VS_OUT {
vec2 TexCoords;
} fs_in;
uniform sampler2D texPosDepth;
uniform sampler2D texNormalSpec;
uniform sampler2D texNoise;
uniform vec3 samples[64];
uniform mat4 projection;
uniform mat4 view;
uniform mat3 viewNormal; // transpose(inverse(mat3(view)))
const vec2 noiseScale = vec2(800.0f/4.0f, 600.0f/4.0f);
const float radius = 5.0;
void main( void )
{
float linearDepth = texture(texPosDepth, fs_in.TexCoords).w;
// Fragment's view space position and normal
vec3 fragPos_World = texture(texPosDepth, fs_in.TexCoords).xyz;
vec3 origin = vec3(view * vec4(fragPos_World, 1.0));
vec3 normal = texture(texNormalSpec, fs_in.TexCoords).xyz;
normal = normalize(normal * 2.0 - 1.0);
normal = normalize(viewNormal * normal); // Normal from world to view-space
// Use change-of-basis matrix to reorient sample kernel around origin's normal
vec3 rvec = texture(texNoise, fs_in.TexCoords * noiseScale).xyz;
vec3 tangent = normalize(rvec - normal * dot(rvec, normal));
vec3 bitangent = cross(normal, tangent);
mat3 tbn = mat3(tangent, bitangent, normal);
// Loop through the sample kernel
float occlusion = 0.0;
for(int i = 0; i < 64; ++i)
{
// get sample position
vec3 sample = tbn * samples[i]; // From tangent to view-space
sample = sample * radius + origin;
// project sample position (to sample texture) (to get position on screen/texture)
vec4 offset = vec4(sample, 1.0);
offset = projection * offset;
offset.xy /= offset.w;
offset.xy = offset.xy * 0.5 + 0.5;
// get sample depth
float sampleDepth = texture(texPosDepth, offset.xy).w;
// range check & accumulate
// float rangeCheck = abs(origin.z - sampleDepth) < radius ? 1.0 : 0.0;
occlusion += (sampleDepth <= sample.z ? 1.0 : 0.0);
}
occlusion = 1.0 - (occlusion / 64.0f);
gl_FragColor = vec4(vec3(occlusion), 1.0);
}
ただし、結果は満足のいくものではありません。オクルージョン バッファはほとんどすべて白で、オクルージョンは表示されません。ただし、オブジェクトに非常に近づくと、以下に示すように、奇妙なノイズのような結果が表示されます。
これは明らかに正しくありません。私はかなりの割合でデバッグを行い、関連するすべての変数が正しく渡されていると信じています (それらはすべて色として視覚化されています)。ビュー空間で計算を行います。
いずれかの手順で問題が発生した場合に備えて、私が実行した手順 (および選択) について簡単に説明します。
ビュー空間の位置/法線 John Chapman は、ビュー レイと線形化された深度値を使用して、ビュー空間の位置を取得します。フラグメントごとのワールド空間位置が既にある遅延レンダラーを使用しているので、単純にそれらを取得し、それらをビュー マトリックスで乗算して、ビュー空間に取得します。
法線ベクトルについても同様のアプローチをとります。バッファ テクスチャからワールド空間法線ベクトルを取得し、それらを [-1,1] 範囲に変換し、ビュー マトリックスの転置 (逆 (mat3(..))) で乗算します。
ビュー空間の位置と法線は、次のように視覚化されます。
これは私には正しいように見えます。
半球を法線
の周りに配置する マトリックスを作成する手順tbn
は、John Chapman のチュートリアルで説明されている手順と同じです。次のようにノイズ テクスチャを作成します。
std::vector<glm::vec3> ssaoNoise;
for (GLuint i = 0; i < noise_size; i++)
{
glm::vec3 noise(randomFloats(generator) * 2.0 - 1.0, randomFloats(generator) * 2.0 - 1.0, 0.0f);
noise = glm::normalize(noise);
ssaoNoise.push_back(noise);
}
...
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, 4, 4, 0, GL_RGB, GL_FLOAT, &ssaoNoise[0]);
フラグメント シェーダーでノイズを視覚化できるので、機能しているように見えます。
サンプル深度 すべてのサンプルを接線からビュー空間に変換します (サンプルは、xy 軸の [-1,1] と z 軸の [0,1] の間でランダムになり、それらをフラグメントの現在のビュー空間位置 (原点) に変換します)。
次に、線形化された深度バッファーからサンプリングします (オブジェクトを近くで見たときに以下で視覚化します)。
最後に、サンプリングされた深度値を現在のフラグメントの深度値と比較し、オクルージョン値を追加します。それがこの動作の原因であるとは思わないため、範囲チェックを実行しないことに注意してください。今のところ、できるだけ最小限に抑えたいと思います。
何がこの動作を引き起こしているのかわかりません。深度値のサンプリングのどこかにあると思います。私が正しい座標系で作業していると言える限り、線形化された深度値もビュー空間にあり、すべての変数はある程度適切に設定されています。