源码 / Ch11/DFGLut.cs
Ch11/DFGLut.cs
// 程序生成 split-sum 的环境 BRDF 查找表(DFG LUT)。 // 对每个 (NoV, 感知粗糙度) 用 GGX 重要性采样数值积分: // ∫ f_s cosθ dω = F0 · A + F90 · B // A = 平均[(1 - Fc) · Gv],B = 平均[Fc · Gv],Fc = (1 - VoH)^5,Gv = V · 4 NoL VoH / NoH // 其中 V 是高度相关 Smith 可见性项 —— 与 MyPBR_BRDF.hlsl 里的直接光完全同一套模型。 using UnityEngine; namespace Cookbook.Ch11 { public static class DFGLut { public static Texture2D Generate(int size = 64, int sampleCount = 512) { var tex = new Texture2D(size, size, TextureFormat.RGBAHalf, false, true) // 线性、无 mip { name = "MyPBR_DFG_LUT", wrapMode = TextureWrapMode.Clamp, filterMode = FilterMode.Bilinear }; var px = new Color[size * size]; for (int y = 0; y < size; y++) { float perceptualRoughness = (y + 0.5f) / size; // 取像素中心,双线性采样时才对得上 float a = perceptualRoughness * perceptualRoughness; for (int x = 0; x < size; x++) { float NoV = (x + 0.5f) / size; Vector2 ab = Integrate(NoV, a, sampleCount); px[y * size + x] = new Color(ab.x, ab.y, 0, 1); } } tex.SetPixels(px); tex.Apply(false, false); return tex; } static Vector2 Integrate(float NoV, float a, int sampleCount) { // 切线空间里 N = (0,0,1),V 放在 xz 平面 var V = new Vector3(Mathf.Sqrt(1f - NoV * NoV), 0f, NoV); float a2 = a * a; float A = 0, B = 0; for (int i = 0; i < sampleCount; i++) { Vector2 xi = Hammersley(i, sampleCount); Vector3 H = ImportanceSampleGGX(xi, a2); Vector3 L = 2f * Vector3.Dot(V, H) * H - V; float NoL = Mathf.Clamp01(L.z); float NoH = Mathf.Clamp01(H.z); float VoH = Mathf.Clamp01(Vector3.Dot(V, H)); if (NoL <= 0f) continue; // pdf(L) = D · NoH / (4 VoH);被积函数 f_s·NoL = D·V·F·NoL // 两者相除 D 消掉:F · V · 4 · NoL · VoH / NoH float vis = SmithGGXCorrelated(NoV, NoL, a2); float gv = vis * 4f * NoL * VoH / Mathf.Max(NoH, 1e-6f); float fc = Mathf.Pow(1f - VoH, 5f); A += (1f - fc) * gv; B += fc * gv; } return new Vector2(A / sampleCount, B / sampleCount); } // 与 HLSL 的 MyV_SmithGGXCorrelated 逐字对应 static float SmithGGXCorrelated(float NoV, float NoL, float a2) { float lv = NoL * Mathf.Sqrt((NoV - NoV * a2) * NoV + a2); float ll = NoV * Mathf.Sqrt((NoL - NoL * a2) * NoL + a2); return 0.5f / Mathf.Max(lv + ll, 1e-5f); } // GGX 重要性采样:按 D(h)·NoH 的分布抽半角向量 static Vector3 ImportanceSampleGGX(Vector2 xi, float a2) { float phi = 2f * Mathf.PI * xi.x; float cosTheta = Mathf.Sqrt((1f - xi.y) / (1f + (a2 - 1f) * xi.y)); float sinTheta = Mathf.Sqrt(1f - cosTheta * cosTheta); return new Vector3(sinTheta * Mathf.Cos(phi), sinTheta * Mathf.Sin(phi), cosTheta); } // 低差异序列:比纯随机数收敛快得多,512 个样本就很平滑 static Vector2 Hammersley(int i, int n) { uint bits = (uint)i; bits = (bits << 16) | (bits >> 16); bits = ((bits & 0x55555555u) << 1) | ((bits & 0xAAAAAAAAu) >> 1); bits = ((bits & 0x33333333u) << 2) | ((bits & 0xCCCCCCCCu) >> 2); bits = ((bits & 0x0F0F0F0Fu) << 4) | ((bits & 0xF0F0F0F0u) >> 4); bits = ((bits & 0x00FF00FFu) << 8) | ((bits & 0xFF00FF00u) >> 8); return new Vector2((float)i / n, bits * 2.3283064365386963e-10f); } } }