详细资料:
http://developer.download.nvidia.com/SDK/10.5/direct3d/Source/ScreenSpaceAO/doc/ScreenSpaceAO.pdf ; http://developer.download.nvidia.com/presentations/2008/SIGGRAPH/HBAO_SIG08b.pdf
主要计算方式:
在屏幕空间进行射线检测,比较其与XY平面的夹角Sin值,最后进行积分,获得整个半球面的AO值。
图示如下:
ao计算公式
需要补充几点:
法线是基于像素的,不能插值 ,但可通过ddx/ddy微分计算
角度加个偏移值,防止采样点的sin h过小
ao衰减参数 W(r) = 1 -((s-p)/R)2
半球面ao积分计算
流程图,最后分别对X方向和Y方向进行高斯平滑即可。
具体shader代码如下(ssao.frag):
// 参数说明: m_R,相机坐标系ao计算半径;m_NumSteps,每方向的计算次数;m_NumDir,随机方向个数;m_inv_R,半径倒数;
// m_sqr_R,半径平方;m_FocalLen,Vector2f(1f / (tanFovY * aspectRatio), 1f / tanFovY);m_Resolution,ao
// depthTexture的宽高;m_Contrast, 3;
float M_PI = 3.14159265;
varying vec2 texCoord;
uniform sampler2D m_DepthTexture;
vec3 uv_to_eye(vec2 uv, float z) {
// texture origin is at bottom left corner in OpenGL
uv = (uv * vec2(2.0, 2.0) - vec2(1.0, 1.0));
return vec3(uv * m_InvFocalLen * z, z);
}
vec3 fetch_eye_pos(vec2 uv) {
float z = texture2D(m_DepthTexture, uv).x;
z = m_invFrustumFar / (1.0 - z);// (-2.0 * 10000 / (z * 9999 - 10001)) / 9999; to be precise, but only approximation is required
return uv_to_eye(uv, z);
}
float length2(vec3 v) {
return dot(v, v);
}
vec3 min_diff(vec3 P, vec3 Pr, vec3 Pl)
{
vec3 V1 = Pr - P;
vec3 V2 = P - Pl;
return (length2(V1) < length2(V2)) ? V1 : V2;
}
vec2 rotate_direction(vec2 Dir, vec2 CosSin)
{
return vec2(Dir.x * CosSin.x - Dir.y * CosSin.y, Dir.x * CosSin.y + Dir.y * CosSin.x);
}
vec2 snap_uv_offset(vec2 uv)
{
return round(uv * m_Resolution) * m_InvResolution;
}
vec3 tangent_vector(vec2 deltaUV, vec3 dPdu, vec3 dPdv)
{
return deltaUV.x * dPdu + deltaUV.y * dPdv;
}
float tangent(vec3 T)
{
return -T.z / length(T.xy);
}
vec2 snap_uv_coord(vec2 uv)
{
return uv - (fract(uv * m_Resolution) - 0.5) * m_InvResolution;
}
float tan_to_sin(float x)
{
return x / sqrt(1.0 + x * x);
}
float falloff(float r)
{
return 1.0 - m_Attenuation * r * r;
}
float tangent(vec3 P, vec3 S)
{
return (P.z - S.z) / length(S.xy - P.xy);
}
float AccumulatedHorizonOcclusion_Quality(vec2 deltaUV,
vec2 uv0,
vec3 P,
float numSteps,
float randstep,
vec3 dPdu,
vec3 dPdv)
{
// Jitter starting point within the first sample distance
vec2 uv = (uv0 + deltaUV) + randstep * deltaUV;
// Snap first sample uv and initialize horizon tangent
vec2 snapped_duv = snap_uv_offset(uv - uv0);
vec3 T = tangent_vector(snapped_duv, dPdu, dPdv);
float tanH = tangent(T) + m_TanAngleBias;
float ao = 0.0;
float h0 = 0.0;
for(float j = 0.0; j < numSteps; ++j) {
vec2 snapped_uv = snap_uv_coord(uv);
vec3 S = fetch_eye_pos(snapped_uv);
uv += deltaUV;
// Ignore any samples outside the radius of influence
float d2 = length2(S - P);
if (d2 < m_sqr_R) {
float tanS = tangent(P, S);
if (tanS > tanH) {
// Compute tangent vector associated with snapped_uv
vec2 snapped_duv = snapped_uv - uv0;
vec3 T = tangent_vector(snapped_duv, dPdu, dPdv);
float tanT = tangent(T) + m_TanAngleBias;
// Compute AO between tangent T and sample S
float sinS = tan_to_sin(tanS);
float sinT = tan_to_sin(tanT);
float r = sqrt(d2) * m_inv_R;
float h = sinS - sinT;
ao += falloff(r) * (h - h0);
h0 = h;
// Update the current horizon angle
tanH = tanS;
}
}
}
return ao;
}
void main() {
vec3 P = fetch_eye_pos(texCoord);
// Project the radius of influence m_R from eye space to texture space.
// The scaling by 0.5 is to go from [-1,1] to [0,1].
vec2 step_size = 0.5 * m_R * m_FocalLen / P.z;
// Early out if the projected radius is smaller than 1 pixel.
float numSteps = min (m_NumSteps, min(step_size.x * m_Resolution.x, step_size.y * m_Resolution.y));
if( numSteps < 1.0 ) {
gl_FragColor = vec4(1.0);
return;
}
step_size = step_size / ( numSteps + 1.0 );
// Nearest neighbor pixels on the tangent plane
vec3 Pr, Pl, Pt, Pb;
Pr = fetch_eye_pos(texCoord + vec2(m_InvResolution.x, 0));
Pl = fetch_eye_pos(texCoord + vec2(-m_InvResolution.x, 0));
Pt = fetch_eye_pos(texCoord + vec2(0, m_InvResolution.y));
Pb = fetch_eye_pos(texCoord + vec2(0, -m_InvResolution.y));
// Screen-aligned basis for the tangent plane
vec3 dPdu = min_diff(P, Pr, Pl);
vec3 dPdv = min_diff(P, Pt, Pb) * (m_Resolution.y * m_InvResolution.x);
vec2 jitter = texCoord * 1000.0;
vec3 rand = normalize(vec3(sin(jitter.x) + sin(jitter.y), cos(jitter.x) + cos(jitter.y), 0));
float ao = 0.0;
float d;
float alpha = 2.0 * M_PI / m_NumDir;
for (d = 0.0; d < m_NumDir; d++) {
float angle = alpha * float(d);
vec2 dir = vec2(cos(angle), sin(angle));
vec2 deltaUV = rotate_direction(dir, rand.xy) * step_size.xy;
ao += AccumulatedHorizonOcclusion_Quality(deltaUV, texCoord, P, numSteps, rand.z, dPdu, dPdv);
}
gl_FragColor = vec4(vec3(1.0 - ao / m_NumDir * m_Contrast), 1);
}