Schlick近似更常用是因为它用简单多项式(F₀+(1−F₀)(1−cosθ)⁵)高效拟合菲涅尔反射率,误差

为什么 Schlick 近似比直接算 Fresnel 公式更常用
因为完整 Fresnel 方程要解偏振分量(Rs 和 Rp),涉及入射角、折射率、光波相位,实时渲染中开销太大。Schlick 近似用一个简单多项式拟合反射率,误差在 0.001 以内,且只依赖一个标量——入射方向与表面法向的点积 dot(wo, n)。
实操建议:
- 永远先归一化入射方向
wo和法向n,否则dot(wo, n)会越界,导致pow计算出 NaN - 当
dot(wo, n) ,说明光线从背面入射(比如从玻璃内部打向界面),此时应翻转法向或取绝对值,常见写法是 <code>F0 = pow((eta - 1) / (eta + 1), 2),其中eta是「从介质 A 到 B」的相对折射率,必须保证 > 0 - 不要在每次采样时重复计算
F0:它是材质属性,应在 BVH 构建或材质初始化阶段预存为material.fresnel_f0
如何写一个线程安全、无分支的 Schlick 函数
GPU 或多线程 CPU 渲染器中,if 分支会导致 warp divergence 或 cache line false sharing。Schlick 公式本身可完全无分支表达:
float schlick_fresnel(float cos_theta, float f0) {
float u = clamp(cos_theta, 0.0f, 1.0f);
float u2 = u * u;
return f0 + (1.0f - f0) * (1.0f - u2) * (1.0f - u2) * (1.0f - u2);
}注意点:
立即学习“C++免费学习笔记(深入)”;
-
clamp(cos_theta, 0.0f, 1.0f)必须做,防止因浮点误差导致cos_theta略小于 0 或大于 1,引发负数幂运算 - 指数用三次
(1 - u²)³而非pow(1 - u2, 3):前者是乘法,后者可能调用库函数并引入额外分支或精度抖动 - 如果用
double路径(如离线器调试模式),把0.0f换成0.0,避免隐式转换警告
折射方向怎么结合 Schlick 概率做 Russian Roulette
Schlick 给出的是反射概率 R,折射概率就是 1 - R。但不能直接按 R 决定走反射还是折射路径——那会破坏能量守恒。正确做法是用 Russian Roulette 引入随机性,同时保持期望值一致:
float r = schlick_fresnel(dot(wo, n), f0); float p = fmaxf(r, 1.0f - r); // 选较大者作存活概率 if (drand48() >= p) return make_spectrum(0.0f); // 被“终止” float weight = (p == r) ? 1.0f / p : 0.0f; // 反射分支权重 // 折射分支权重同理,但需注意:折射方向要用 Snell 定律重算,且必须检查全内反射(TIR)
关键细节:
- 全内反射发生时
sin_theta_t² > 1,此时折射方向无效,只能走反射分支,r应强制设为 1.0 —— 否则 Schlick 会低估反射率 -
weight不是简单的1/p,而要按实际选择的分支修正:若走反射,则权重是r / p;若走折射,是(1 - r) / p。上面示例简化了逻辑,真实代码中需显式分支判断 - 别在 TIR 后还调用
refract():GLSL 的refract()或自写函数若未检查输入,会返回nan向量
常见错误:F0 计算错介质顺序、忽略极化差异
很多人直接套用 F0 = ((n1 - n2) / (n1 + n2))²,但漏掉两个前提:
-
n1是光线出发侧介质折射率,n2是进入侧。若光线从空气(1.0)射向水(1.33),F0 ≈ 0.02;反过来从水射向空气,F0值不变 —— 因为公式对称,但 TIR 条件变了 - Schlick 近似默认处理的是非偏振光。如果渲染器支持偏振(如某些科学可视化),必须分别算
Rs和Rp,再按 50/50 混合,此时 Schlick 就不适用了 - 金属材质不能用标量
F0:它得是 RGB 三通道值(如铜的F0 ≈ vec3(0.95, 0.64, 0.54)),Schlick 函数也要改成vec3版本,且dot(wo, n)仍是标量
最易被忽略的一点:当使用 GGX 或其它微表面模型时,Schlick 里的 cos_theta 应该是相对于微表面法向(half vector)的夹角,而不是宏观法向——否则各向异性会失真。这要求把 Schlick 嵌入到 GTR2 或 Smith 衰减项内部,而非简单贴在 BRDF 外层。


















