我一直在OpenGL中工作SSAO。我决定从OpenGL的this教程中为我的延迟渲染器实现SSAO。不幸的是,我一直无法让它运作良好。 SSAO变暗的区域会根据摄像机的位置而发生很大变化。据我所知,当相机移动时,SSAO的输出可能会有一些变化,但这比我在其他SSAO实现中观察到的要大得多。
这是片段着色器代码
void main() {
vec3 origin = positionFromDepth(texture2D(gDepth, samplePosition));
vec3 normal = texture2D(gNormal, samplePosition).xyz; //multiplying this
//by 2 and subtracting 1 doesn't seem to help
vec2 random = getRandom(samplePosition);
float radius = uRadius/origin.z;
float occlusion = 0.0;
int iterations = samples/4;
for (int i = 0; i<iterations; i++) {
vec2 coord1 = reflect(kernel[i], random)*radius;
vec2 coord2 = vec2(coord1.x*0.707 - coord1.y*0.707, coord1.x*0.707 + coord1.y*0.707);
occlusion += occlude(samplePosition, coord1 * 0.25, origin, normal);
occlusion += occlude(samplePosition, coord2 * 0.50, origin, normal);
occlusion += occlude(samplePosition, coord1 * 0.75, origin, normal);
occlusion += occlude(samplePosition, coord2, origin, normal);
}
color = vec4(origin, 1);
}
positionFromDepth()
功能:
vec3 positionFromDepth(float depth) {
float near = frustrumData.x;
float far = frustrumData.y;
float right = frustrumData.z;
float top = frustrumData.w;
vec2 ndc;
vec3 eye;
eye.z = near * far / ((depth * (far - near)) - far);
ndc.x = ((gl_FragCoord.x/buffersize.x) - 0.5) * 2.0;
ndc.y = ((gl_FragCoord.y/buffersize.y) - 0.5) * 2.0;
eye.x = (-ndc.x * eye.z) * right/near;
eye.y = (-ndc.y * eye.z) * top/near;
return eye;
}
occlude()
函数:
float occlude(vec2 uv, vec2 offsetUV, vec3 origin, vec3 normal) {
vec3 diff = positionFromDepth(texture2D(gDepth,(uv+offsetUV)))-origin;
vec3 vec = normalize(diff);
float dist = length(diff)/scale;
return max(0.0,dot(normal,vec)-bias)*(1.0/(1.0+dist))*intensity;
}
我觉得问题可能出在positionFromDepth()
函数中,除了我在渲染器的照明阶段使用相同的代码(我认为)。我已经对这段代码进行了一千次的处理,并没有找到任何突出的错误。我为bias
,radius
,intenisty
和scale
尝试了各种值,但这似乎不是问题。我担心我的法线或位置是错误的,所以这里有一些屏幕截图:
重建位置: 和正常的缓冲区:
我会包含遮挡缓冲区的图像,但问题主要是在相机移动时才会显而易见,图像无法显示。
有没有人知道这里有什么问题?
答案 0 :(得分:3)
奇怪的是,乘以2并减去1对你的法线贴图没有帮助。通常这样做是为了克服与以无符号/标准化纹理格式存储法线相关的问题。除非您的普通G-Buffer是有符号/非标准化格式,否则您可能需要在第一次写入时使用* 0.5 + 0.5
打包并解压缩法线,并在对纹理进行采样时使用* 2.0 - 1.0
。
在任何情况下,SSAO都有多种方法,许多方法甚至根本不使用表面法线。因此,关于存储法线的向量空间的讨论经常被忽略。
我强烈怀疑你的法线是在视野中,而不是世界空间。如果您将法线乘以顶点着色器中的“普通矩阵”,就像许多教程一样,那么法线将位于视图空间中。
事实证明,视图空间法线实际上并没有那么有用,现在使用世界空间法线可以更好地处理后处理效果。大多数现代延迟着色引擎(例如虚幻引擎4,CryEngine 3等)将普通G-Buffer存储在世界空间中,然后将其转换为像素着色器中的视图空间(如果需要)。
顺便说一句,我已经包含了一些代码,用于从传统的深度缓冲区重建对象空间位置。您似乎正在使用视图空间位置/法线。您可能想要尝试对象/世界空间中的所有内容。
flat in mat4 inv_mv_mat; in vec2 uv; ... float linearZ (float z) { #ifdef INVERT_NEAR_FAR const float f = 2.5; const float n = 25000.0; #else const float f = 25000.0; const float n = 2.5; #endif return n / (f - z * (f - n)) * f; } vec4 reconstruct_pos (in float depth) { depth = linearZ (depth); vec4 pos = vec4 (uv * depth, -depth, 1.0); vec4 ret = (inv_mv_mat * pos); return ret / ret.w; }
在延迟着色光照过程的顶点着色器阶段需要一些额外的设置,如下所示:
#version 150 core in vec4 vtx_pos; in vec2 vtx_st; uniform mat4 modelview_mat; // Matrix used when the G-Buffer was built uniform mat4 camera_matrix; // Matrix used to stretch the G-Buffer over the viewport uniform float buffer_res_x; uniform float buffer_res_y; out vec2 tex_st; flat out mat4 inv_mv_mat; out vec2 uv; // Hard-Coded 45 degree FOV //const float fovy = 0.78539818525314331; // NV pukes on the line below! //const float fovy = radians (45.0); //const float tan_half_fovy = tan (fovy * 0.5); const float tan_half_fovy = 0.41421356797218323; float aspect = buffer_res_x / buffer_res_y; vec2 inv_focal_len = vec2 (tan_half_fovy * aspect, tan_half_fovy); const vec2 uv_scale = vec2 (2.0, 2.0); const vec2 uv_translate = vec2 (1.0, 1.0); void main (void) { inv_mv_mat = inverse (modelview_mat); tex_st = vtx_st; gl_Position = camera_matrix * vtx_pos; uv = (vtx_st * uv_scale - uv_translate) * inv_focal_len; }