Observe · Change · Recompute · Explain
从 G-buffer 读出来的每一个证据
Read-only · exact runtime source
代码直接来自交互视口,按 projection、gbuffer、reconstruct、ssao、ssr、bounds、debug 的顺序阅读。
这一课怎么学
- 观察
- 预测
- 改一个量
- 看证据
- 再读代码
屏幕坐标与投影
全屏三角形让每个 fragment 拿到稳定的 UV。
varying vec2 vUv;
void main() {
// SECTION:projection - a full-screen triangle carries the screen coordinate
vUv = uv;
gl_Position = vec4(position.xy, 0.0, 1.0);
}G-buffer attachments
albedo、normal 与 depth 是不同的证据来源。
precision highp float;
uniform sampler2D uColorTexture;
uniform sampler2D uDepthTexture;
uniform sampler2D uNormalTexture;
uniform sampler2D uPositionTexture;
uniform vec2 uTexelSize;
uniform vec2 uViewport;
uniform float uNearPlane;
uniform float uFarPlane;
uniform float uSsaoRadius;
uniform float uSsaoBias;
uniform float uSsaoStrength;
uniform float uSsaoFalloff;
uniform int uSsaoSamples;
uniform bool uSsaoEnabled;
uniform float uSsrStepSize;
uniform float uSsrThickness;
uniform float uSsrMaxDistance;
uniform float uSsrEdgeFade;
uniform int uSsrMaxSteps;
uniform bool uSsrEnabled;
uniform int uDebugMode;
uniform int uProjection;
uniform int uPositionSource;
uniform float uTime;
uniform mat4 uInverseProjection;
uniform mat4 uInverseView;
varying vec2 vUv;
// SECTION:gbuffer - the color, normal and depth attachments are sampled independently
vec4 readAlbedo(vec2 uv) { return texture2D(uColorTexture, uv); }
float readDeviceDepth(vec2 uv) { return texture2D(uDepthTexture, uv).r; }
vec3 readNormal(vec2 uv) { return normalize(texture2D(uNormalTexture, uv).xyz * 2.0 - 1.0); }
float linearDepth(float depth) {
if (uProjection == 1) return mix(uNearPlane, uFarPlane, depth);
return (uNearPlane * uFarPlane) / max(uFarPlane - depth * (uFarPlane - uNearPlane), 0.0001);
}
vec3 reconstructViewPosition(vec2 uv, float depth) {
vec4 clip = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
vec4 view = uInverseProjection * clip;
return view.xyz / max(abs(view.w), 0.0001);
}
vec3 reconstructWorldPosition(vec2 uv, float depth) {
vec4 world = uInverseView * vec4(reconstructViewPosition(uv, depth), 1.0);
return world.xyz / max(abs(world.w), 0.0001);
}
vec3 readPosition(vec2 uv, float depth) {
return uPositionSource == 1 ? texture2D(uPositionTexture, uv).xyz : reconstructViewPosition(uv, depth);
}深度重建位置
device depth 通过 inverse projection 回到 view/world space。
// SECTION:reconstruct - depth is nonlinear in perspective projection, then inverse projection restores position
float depthDelta(vec2 uv, float center) {
float sampleDepth = readDeviceDepth(uv);
return linearDepth(center) - linearDepth(sampleDepth);
}
float screenEdgeFade(vec2 uv) {
float edge = min(min(uv.x, 1.0 - uv.x), min(uv.y, 1.0 - uv.y));
return uSsrEdgeFade <= 0.0 ? 1.0 : clamp(edge / uSsrEdgeFade, 0.0, 1.0);
}SSAO 采样
有限邻域与有效样本决定 AO 的可信范围。
// SECTION:ssao - bounded samples turn nearby depth differences into an occlusion estimate
float computeSsao(vec2 uv, float centerDepth) {
if (!uSsaoEnabled) return 0.0;
float occluded = 0.0;
float valid = 0.0;
vec2 golden = vec2(0.754877666, 0.569840296);
for (int index = 0; index < 32; index += 1) {
if (index >= uSsaoSamples) break;
float angle = float(index) * 6.2831853 * golden.x + uTime * 0.08;
float radius = uSsaoRadius * (0.35 + 0.65 * (float(index) + 1.0) / float(max(uSsaoSamples, 1)));
vec2 sampleUv = uv + vec2(cos(angle), sin(angle)) * uTexelSize * radius * uViewport;
if (sampleUv.x < 0.0 || sampleUv.x > 1.0 || sampleUv.y < 0.0 || sampleUv.y > 1.0) continue;
valid += 1.0;
float delta = depthDelta(sampleUv, centerDepth);
occluded += step(uSsaoBias, delta);
}
float ratio = occluded / max(valid, 1.0);
return clamp(pow(ratio, max(uSsaoFalloff, 0.01)) * uSsaoStrength, 0.0, 1.0);
}SSR 步进
沿反射方向读取屏幕深度,命中后混合反射颜色。
// SECTION:ssr - march a reflected screen ray until the depth buffer supplies a hit
vec4 traceSsr(vec2 uv, float centerDepth, vec3 normal) {
if (!uSsrEnabled) return vec4(0.0);
vec3 viewPosition = reconstructViewPosition(uv, centerDepth);
vec3 viewDirection = normalize(-viewPosition);
vec3 reflected = normalize(reflect(viewDirection, normal));
vec2 rayDirection = normalize(reflected.xy + vec2(0.0001));
float rayDepth = centerDepth;
for (int index = 1; index <= 64; index += 1) {
if (index > uSsrMaxSteps) break;
float distance = float(index) * uSsrStepSize;
if (distance > uSsrMaxDistance) break;
vec2 sampleUv = uv + rayDirection * distance;
if (sampleUv.x < 0.0 || sampleUv.x > 1.0 || sampleUv.y < 0.0 || sampleUv.y > 1.0) return vec4(0.0);
float sampleDepth = readDeviceDepth(sampleUv);
rayDepth += reflected.z * uSsrStepSize * 0.025;
if (sampleDepth <= rayDepth + uSsrThickness) {
float confidence = screenEdgeFade(sampleUv);
return vec4(readAlbedo(sampleUv).rgb, confidence);
}
}
return vec4(0.0);
}屏幕边界与失败
屏幕外没有几何证据,edge fade 让 miss 可见。
// SECTION:bounds - screen-space effects expose their own validity and edge fade
vec3 boundsDebug(vec2 uv, vec4 ssr) {
float edge = min(min(uv.x, 1.0 - uv.x), min(uv.y, 1.0 - uv.y));
return mix(vec3(0.95, 0.2, 0.18), vec3(0.2, 0.8, 0.55), clamp(edge * 12.0, 0.0, 1.0)) * (0.25 + 0.75 * ssr.a);
}Debug views
将位置、深度、AO、SSR 结果直接输出为可观察证据。
// SECTION:debug - switch from the final composite to evidence views for teaching
void main() {
vec4 albedo = readAlbedo(vUv);
float deviceDepth = readDeviceDepth(vUv);
float linear = linearDepth(deviceDepth);
vec3 normal = readNormal(vUv);
vec3 viewPosition = readPosition(vUv, deviceDepth);
vec3 worldPosition = reconstructWorldPosition(vUv, deviceDepth);
float ao = computeSsao(vUv, deviceDepth);
vec4 ssr = traceSsr(vUv, deviceDepth, normal);
vec3 color = albedo.rgb;
color *= 1.0 - ao * 0.72;
color = mix(color, ssr.rgb, ssr.a * 0.48);
if (uDebugMode == 1) color = albedo.rgb;
if (uDebugMode == 2) color = normal * 0.5 + 0.5;
if (uDebugMode == 3) color = normalize(worldPosition) * 0.5 + 0.5;
if (uDebugMode == 4) color = vec3(deviceDepth);
if (uDebugMode == 5) color = vec3(clamp(linear / uFarPlane, 0.0, 1.0));
if (uDebugMode == 6) color = vec3(1.0 - ao, 0.24 + ao * 0.65, 0.08);
if (uDebugMode == 7) color = mix(vec3(0.08, 0.12, 0.18), ssr.rgb, ssr.a);
if (uDebugMode == 8) color = boundsDebug(vUv, ssr);
gl_FragColor = vec4(color, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}