GameTech/URP 渲染实战

案例 19.5菲涅尔与天空反射进阶

目标:水面反射天空;低头看时几乎透明,看向远处时像镜子。

水的 F0

第 9 章讲过,电介质在垂直入射时的反射率 F0 由折射率决定:

F0=(n−1n+1)2≈(0.332.33)2≈0.02

水的折射率约 1.33,F0 只有 2%:正对着看,98% 的光都透进水里,所以脚下的水清澈见底。随着视线变平,Schlick 近似 F = F0 + (1 − F0)(1 − N·V)5 迅速上升,掠射时接近 100%。这条曲线就是水面“近处透、远处亮”的全部原因。

Assets/Cookbook/Ch19/WaterCore.hlsl(节选:fresnel)
// Schlick 菲涅尔。水的折射率约 1.33,F0 = ((1.33-1)/(1.33+1))² ≈ 0.02:正看几乎全透,掠射角几乎全反
half WaterFresnel(half3 normalWS, half3 viewWS)
{
    half NdotV = saturate(dot(normalWS, viewWS));
    return 0.02h + 0.98h * pow(1.0h - NdotV, 5.0h);
}
// Schlick 菲涅尔。水的折射率约 1.33,F0 = ((1.33-1)/(1.33+1))² ≈ 0.02:正看几乎全透,掠射角几乎全反
float WaterFresnel(vec3 normalWS, vec3 viewWS)
{
    float NdotV = saturate(dot(normalWS, viewWS));
    return 0.02 + 0.98 * pow(1.0 - NdotV, 5.0);
}

天空反射:GlossyEnvironmentReflection

反射颜色先用环境反射:反射方向 reflect(-V, N) 去查天空盒或反射探针。URP 14 的 GlobalIllumination.hlsl 提供了 GlossyEnvironmentReflection,它会根据粗糙度选择 mip 级别,并处理反射探针混合与盒体投影。这里有一个 URP 14 特有的坑:

// 摘自 URP 14 ShaderLibrary/GlobalIllumination.hlsl(节选)
half3 GlossyEnvironmentReflection(half3 reflectVector, float3 positionWS, half perceptualRoughness,
                                  half occlusion, float2 normalizedScreenSpaceUV) { ... }

#if !USE_FORWARD_PLUS
half3 GlossyEnvironmentReflection(half3 reflectVector, float3 positionWS, half perceptualRoughness, half occlusion)
{ ... }
#endif

4 参数的版本被 #if !USE_FORWARD_PLUS 包住了。Forward+ 下反射探针是按屏幕分块(cluster)查找的,必须传入屏幕 UV。如果你只写了 4 参数调用,在 Forward 路径下能编译,切换到 Forward+ 就会报 “no matching 4 parameter function”。所以本书统一使用 5 参数版本。

反射与水下颜色按菲涅尔混合:color = lerp(underwater, reflection, F)。注意 alpha 也同时向 1 插值——在没有折射的简单版本里,强反射处的水面不应该再透明。

图 19-8左:没有反射;中:不用菲涅尔、恒定 50% 反射——近处的水也像蒙了一层雾,失去通透感;右:菲涅尔反射——近处清澈见底,远处逐渐倒映出天空。
提示风格化水面常常故意“作弊”:把菲涅尔的指数从 5 调小(比如 3),让反射更早出现;或者给反射乘一个大于 1 的强度。物理正确是起点,不是终点——但先有正确的版本,你才知道自己在偏离什么。