Observe · Change · Recompute · Explain
每个效果都要露出它的成本和边界
Read-only · exact teaching model
代码页直接展示课程使用的最小 HLSL/GLSL 结构,按传播、pass、coverage、深度、管线、体积和数值 debug 阅读。
这一课怎么学
- 观察
- 预测
- 改一个量
- 看证据
- 再读代码
传播半径与采样
把次表面散射从“加颜色”拆成可预算的传播。
// SECTION:sss
float sssProfile(float radius, int samples, float thickness) {
float diffusion = 0.0;
for (int i = 0; i < samples; i++) {
diffusion += profile(radius, thickness, float(i) / float(samples));
}
return diffusion / float(samples);
}
顶点/片元结构
Q10 的手写 shader 先读入口、资源和 pass。
// SECTION:shaderlab
Shader "TA/Learning/NormalMapped" {
Properties { _NormalMap("Normal", 2D) = "bump" {} }
SubShader {
Pass { HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDHLSL }
}
}
coverage 不是 blend
透明路径的硬件 coverage 与排序成本不同。
// SECTION:alpha-coverage
float coverage = round(alpha * float(msaaSamples)) / float(msaaSamples);
float4 color = alphaMode == ATOC
? float4(baseColor, coverage)
: blendSortedLayers(baseColor, alpha);
深度决定可见性
软遮挡必须说明深度来源和边界。
// SECTION:occlusion
float visibility = smoothstep(-softness, softness, sceneDepth - sampleDepth);
visibility *= depthPrepass ? 1.0 : 0.82;
路径是资源选择
灯光、G-buffer、透明和带宽共同决定取舍。
// SECTION:forward-deferred
// Forward: light work follows each visible material.
// Deferred: write G-buffer, then light screen-space pixels.
float lightingCost = renderPath == FORWARD
? visiblePixels * lightCount
: gbufferBandwidth + visiblePixels;
步数×噪声
天气效果的质量和成本来自可解释的循环。
// SECTION:volume
for (int step = 0; step < volumeSteps; step++) {
float density = sampleNoise(rayPosition, noiseOctaves);
transmittance *= exp(-density * stepLength);
radiance += transmittance * density * exposure;
rayPosition += rayDirection * stepLength;
}
数值也要 debug
NaN/Inf/过曝必须有 clamp 和可见证据。
// SECTION:overflow
float3 safeRadiance = clampHdr ? min(radiance, maxHdr) : radiance;
debugColor = any(isnan(safeRadiance)) || max(safeRadiance) > maxHdr
? float3(1.0, 0.1, 0.1)
: safeRadiance;