2025-11-30 · game_math
【游戏中的数学】游戏中的数学 (10) - 渲染管线中的数学
深入渲染管线后半段:投影矩阵的构造原理、透视除法与 w 分量、视口变换,以及深度缓冲精度问题与 Reversed-Z 技巧。
光照是计算机图形学的灵魂。虽然现代引擎使用复杂的 PBR (Physically Based Rendering),但理解经典的光照模型仍然是基础。 经典光照模型通常由三部分组成: \[ Color = Ambient + Diffuse + Specular \]
漫反射模拟的是粗糙表面(如砖墙、布料)对光的散射。 光线照射到表面后,向四面八方均匀反射。 强度取决于光线方向 \(\vec{L}\) 与表面法线 \(\vec{N}\) 的夹角。
\[ I_{diffuse} = \max(0, \vec{N} \cdot \vec{L}) \]
镜面反射模拟的是光滑表面(如金属、塑料)的高光 (Highlight)。 它取决于观察方向 \(\vec{V}\) 和反射方向 \(\vec{R}\) 的夹角。
\[ \vec{R} = \text{reflect}(-\vec{L}, \vec{N}) \] \[ I_{specular} = (\max(0, \vec{R} \cdot \vec{V}))^{shininess} \]
Phong 模型有一个缺点:计算反射向量 \(\vec{R}\) 比较昂贵。 Jim Blinn 提出了一种优化:使用半程向量 (Halfway Vector) \(\vec{H}\)。 \(\vec{H}\) 是光线方向 \(\vec{L}\) 和观察方向 \(\vec{V}\) 的中间向量。
\[ \vec{H} = \text{normalize}(\vec{L} + \vec{V}) \] \[ I_{specular} = (\max(0, \vec{N} \cdot \vec{H}))^{shininess} \]
为了模拟间接光照(光线在墙壁间反弹),经典模型简单地加上一个常数颜色。 \[ I_{ambient} = K_a \] 这是一种非常粗糙的近似,现代游戏通常使用全局光照 (GI) 或环境光遮蔽 (AO) 来替代。
PBR 是一个庞大的主题,但掌握核心概念对游戏开发者至关重要。本节覆盖能量守恒、菲涅尔效应和 Cook-Torrance BRDF 的整体结构。如果你想深入学习 PBR,推荐 Google 的 Filament 文档 和 Naty Hoffman 在 SIGGRAPH 的演讲。
现代游戏引擎(Unity URP/HDRP, Unreal)默认使用 PBR。它比 Blinn-Phong 更符合物理规律。
出射光线的能量不能超过入射光线的能量。 漫反射和镜面反射是互斥的:光线要么进入物体内部(漫反射),要么被表面反弹(镜面反射)。 \[ k_D + k_S \le 1 \] 物体越光滑(金属),镜面反射越强,漫反射就越弱(看起来越黑)。
观察一下你身边的物体(比如桌子或手机屏幕)。当你垂直看它时,反光可能不强;但当你以近乎平行的角度(掠射角)看它时,反光会变得非常强烈,像镜子一样。 这就是菲涅尔效应。 Schlick 近似公式: \[ F(\theta) = F_0 + (1 - F_0)(1 - \cos\theta)^5 \] 其中 \(F_0\) 是基础反射率(垂直观察时的反射率)。不同材质的 \(F_0\) 差异巨大——非金属通常在 0.02–0.05 之间,金属则可以达到 0.5–1.0。
PBR 的镜面反射部分通常使用 Cook-Torrance 微表面 BRDF:
\[ f_{spec} = \frac{D \cdot G \cdot F}{4(\vec{N} \cdot \vec{L})(\vec{N} \cdot \vec{V})} \]
三个核心项各自描述了一种物理现象:
| 项 | 名称 | 作用 |
|---|---|---|
| D | 法线分布函数 (NDF) | 控制有多少微表面朝向半程向量 \(\vec{H}\),直接决定高光的形状和大小。常用 GGX / Trowbridge-Reitz。 |
| G | 几何遮蔽函数 | 模拟微表面之间的互相遮挡(self-shadowing),粗糙度越高遮蔽越强。常用 Smith-GGX。 |
| F | 菲涅尔项 | 即上文的 Schlick 近似,决定不同角度的反射率。 |
分母 \(4(\vec{N} \cdot \vec{L})(\vec{N} \cdot \vec{V})\) 是校正因子,确保 BRDF 在数学上保持能量守恒。
最终的光照方程将漫反射和镜面反射组合起来: \[ L_o = (k_D \frac{c}{\pi} + f_{spec}) \cdot L_i \cdot (\vec{N} \cdot \vec{L}) \] 其中 \(c\) 是表面颜色 (albedo),\(L_i\) 是入射光辐照度。
下表列出了常见材质的 PBR 参数,对美术和 Shader 开发者非常实用:
| 材质 | F₀ (线性值) | Roughness 范围 | Metallic |
|---|---|---|---|
| 水 (Water) | 0.02 | 0.0–0.1 | 0 |
| 皮肤 (Skin) | 0.028 | 0.3–0.5 | 0 |
| 塑料 (Plastic) | 0.04 | 0.3–0.6 | 0 |
| 布料 (Fabric) | 0.04 | 0.8–1.0 | 0 |
| 玻璃 (Glass) | 0.04 | 0.0–0.05 | 0 |
| 铁 (Iron) | 0.56 | 0.3–0.7 | 1 |
| 铜 (Copper) | (0.95, 0.64, 0.54) | 0.2–0.5 | 1 |
| 金 (Gold) | (1.0, 0.76, 0.33) | 0.1–0.4 | 1 |
| 银 (Silver) | (0.97, 0.96, 0.91) | 0.1–0.3 | 1 |
注意:非金属的 \(F_0\) 是单通道灰度值(通常 0.02–0.05),金属的 \(F_0\) 是 RGB 三通道(因为不同波长的反射率不同,这正是金属呈现颜色的原因)。
业界存在两种主流 PBR 工作流,了解它们的区别有助于跨引擎协作和资产迁移。
这是 Unity Standard Shader 和 Unreal Engine 4/5 的默认工作流。
较早的工作流,部分引擎仍支持(如 Unity 的 Specular Setup)。
| 特性 | Metallic/Roughness | Specular/Glossiness |
|---|---|---|
| 贴图数量 | 3 张 (BaseColor, Metallic, Roughness) | 3 张 (Diffuse, Specular, Glossiness) |
| 金属反射色 | 由 BaseColor 提供 | 由 Specular 贴图提供 |
| 物理正确性 | 更容易保持——metallic 只有 0/1 | 需要美术手动控制 Specular 范围 |
| 引擎支持 | Unity Standard, Unreal, Godot, Filament | Unity Specular Setup, 部分移动端引擎 |
| 推荐场景 | 新项目首选 | 旧资产迁移或需要精细控制 |
实践建议:新项目统一使用 Metallic/Roughness 工作流。它更简洁,不容易出错,引擎支持最广泛。
光照效果不对是 Shader 开发中最常见的问题。以下是几种典型症状和排查方法:
| 症状 | 可能原因 | 修复方法 |
|---|---|---|
| 高光过亮 / 泛白 (Blown out) | 未做能量守恒或缺少 HDR Tonemapping | 确保 \(k_D + k_S \le 1\);启用 ACES 或其他 Tonemapping |
| 物体看起来很”平” (Flat) | 法线方向错误或 N·L 计算有误 | 将法线可视化(color = N * 0.5 + 0.5)检查是否正确 |
| 边缘菲涅尔光环过强 | \(F_0\) 值设置过高 | 非金属 \(F_0\) 应在 0.04 左右,不要超过 0.05 |
| 金属看起来像塑料 | Metallic 不是 1.0 | 纯金属 Metallic 必须为 1.0;0.5 在物理上无意义 |
| 高光形状怪异 / 拉伸 | 切线空间法线贴图的切线/副切线错误 | 检查 tangent 和 bitangent 是否正交且方向正确 |
一个非常实用的调试手法:在 Fragment Shader 中临时输出中间变量作为颜色,例如:
// 可视化法线方向
FragColor = vec4(N * 0.5 + 0.5, 1.0);
// 可视化 NdotL(漫反射贡献)
float NdotL = max(dot(N, L), 0.0);
FragColor = vec4(vec3(NdotL), 1.0);
// 可视化 Fresnel
float fresnel = pow(1.0 - max(dot(N, V), 0.0), 5.0);
FragColor = vec4(vec3(fresnel), 1.0);// 输入: N (法线), L (光照方向), V (观察方向)
vec3 N = normalize(Normal);
vec3 L = normalize(LightDir);
vec3 V = normalize(ViewDir);
// Diffuse
float diff = max(dot(N, L), 0.0);
// Specular (Blinn-Phong)
vec3 H = normalize(L + V);
float spec = pow(max(dot(N, H), 0.0), 32.0);
// Result
vec3 color = (ambient + diff + spec) * objectColor;< 上一篇: 渲染管线 | 回到目录 | 下一篇: 曲线数学 >
把当前热点继续串成多页阅读,而不是停在单篇消费。
2025-11-30 · game_math
深入渲染管线后半段:投影矩阵的构造原理、透视除法与 w 分量、视口变换,以及深度缓冲精度问题与 Reversed-Z 技巧。
2025-11-30 · game_math
详解游戏引擎中常用的标量数学函数:Lerp, InverseLerp, Remap, Clamp, SmoothStep 等。不仅有公式,更有实际应用场景。
2025-11-30 · game_math
深入浅出地讲解游戏开发中的三角函数:Sin, Cos, Atan2。从单位圆原理到圆周运动、波浪动画和朝向计算的实际应用。
2025-11-30 · game_math
深入理解游戏开发中的矩阵:TRS 变换与代码实践、矩阵乘法与组合变换、MVP 管线详解、逆矩阵、法线变换,以及旋转表示方式对比。
光照是计算机图形学的灵魂。虽然现代引擎使用复杂的 PBR (Physically Based Rendering),但理解经典的光照模型仍然是基础。 经典光照模型通常由三部分组成: \[ Color = Ambient + Diffuse + Specular \]
漫反射模拟的是粗糙表面(如砖墙、布料)对光的散射。 光线照射到表面后,向四面八方均匀反射。 强度取决于光线方向 \(\vec{L}\) 与表面法线 \(\vec{N}\) 的夹角。
\[ I_{diffuse} = \max(0, \vec{N} \cdot \vec{L}) \]
镜面反射模拟的是光滑表面(如金属、塑料)的高光 (Highlight)。 它取决于观察方向 \(\vec{V}\) 和反射方向 \(\vec{R}\) 的夹角。
\[ \vec{R} = \text{reflect}(-\vec{L}, \vec{N}) \] \[ I_{specular} = (\max(0, \vec{R} \cdot \vec{V}))^{shininess} \]
Phong 模型有一个缺点:计算反射向量 \(\vec{R}\) 比较昂贵。 Jim Blinn 提出了一种优化:使用半程向量 (Halfway Vector) \(\vec{H}\)。 \(\vec{H}\) 是光线方向 \(\vec{L}\) 和观察方向 \(\vec{V}\) 的中间向量。
\[ \vec{H} = \text{normalize}(\vec{L} + \vec{V}) \] \[ I_{specular} = (\max(0, \vec{N} \cdot \vec{H}))^{shininess} \]
为了模拟间接光照(光线在墙壁间反弹),经典模型简单地加上一个常数颜色。 \[ I_{ambient} = K_a \] 这是一种非常粗糙的近似,现代游戏通常使用全局光照 (GI) 或环境光遮蔽 (AO) 来替代。
PBR 是一个庞大的主题,但掌握核心概念对游戏开发者至关重要。本节覆盖能量守恒、菲涅尔效应和 Cook-Torrance BRDF 的整体结构。如果你想深入学习 PBR,推荐 Google 的 Filament 文档 和 Naty Hoffman 在 SIGGRAPH 的演讲。
现代游戏引擎(Unity URP/HDRP, Unreal)默认使用 PBR。它比 Blinn-Phong 更符合物理规律。
出射光线的能量不能超过入射光线的能量。 漫反射和镜面反射是互斥的:光线要么进入物体内部(漫反射),要么被表面反弹(镜面反射)。 \[ k_D + k_S \le 1 \] 物体越光滑(金属),镜面反射越强,漫反射就越弱(看起来越黑)。
观察一下你身边的物体(比如桌子或手机屏幕)。当你垂直看它时,反光可能不强;但当你以近乎平行的角度(掠射角)看它时,反光会变得非常强烈,像镜子一样。 这就是菲涅尔效应。 Schlick 近似公式: \[ F(\theta) = F_0 + (1 - F_0)(1 - \cos\theta)^5 \] 其中 \(F_0\) 是基础反射率(垂直观察时的反射率)。不同材质的 \(F_0\) 差异巨大——非金属通常在 0.02–0.05 之间,金属则可以达到 0.5–1.0。
PBR 的镜面反射部分通常使用 Cook-Torrance 微表面 BRDF:
\[ f_{spec} = \frac{D \cdot G \cdot F}{4(\vec{N} \cdot \vec{L})(\vec{N} \cdot \vec{V})} \]
三个核心项各自描述了一种物理现象:
| 项 | 名称 | 作用 |
|---|---|---|
| D | 法线分布函数 (NDF) | 控制有多少微表面朝向半程向量 \(\vec{H}\),直接决定高光的形状和大小。常用 GGX / Trowbridge-Reitz。 |
| G | 几何遮蔽函数 | 模拟微表面之间的互相遮挡(self-shadowing),粗糙度越高遮蔽越强。常用 Smith-GGX。 |
| F | 菲涅尔项 | 即上文的 Schlick 近似,决定不同角度的反射率。 |
分母 \(4(\vec{N} \cdot \vec{L})(\vec{N} \cdot \vec{V})\) 是校正因子,确保 BRDF 在数学上保持能量守恒。
最终的光照方程将漫反射和镜面反射组合起来: \[ L_o = (k_D \frac{c}{\pi} + f_{spec}) \cdot L_i \cdot (\vec{N} \cdot \vec{L}) \] 其中 \(c\) 是表面颜色 (albedo),\(L_i\) 是入射光辐照度。
下表列出了常见材质的 PBR 参数,对美术和 Shader 开发者非常实用:
| 材质 | F₀ (线性值) | Roughness 范围 | Metallic |
|---|---|---|---|
| 水 (Water) | 0.02 | 0.0–0.1 | 0 |
| 皮肤 (Skin) | 0.028 | 0.3–0.5 | 0 |
| 塑料 (Plastic) | 0.04 | 0.3–0.6 | 0 |
| 布料 (Fabric) | 0.04 | 0.8–1.0 | 0 |
| 玻璃 (Glass) | 0.04 | 0.0–0.05 | 0 |
| 铁 (Iron) | 0.56 | 0.3–0.7 | 1 |
| 铜 (Copper) | (0.95, 0.64, 0.54) | 0.2–0.5 | 1 |
| 金 (Gold) | (1.0, 0.76, 0.33) | 0.1–0.4 | 1 |
| 银 (Silver) | (0.97, 0.96, 0.91) | 0.1–0.3 | 1 |
注意:非金属的 \(F_0\) 是单通道灰度值(通常 0.02–0.05),金属的 \(F_0\) 是 RGB 三通道(因为不同波长的反射率不同,这正是金属呈现颜色的原因)。
业界存在两种主流 PBR 工作流,了解它们的区别有助于跨引擎协作和资产迁移。
这是 Unity Standard Shader 和 Unreal Engine 4/5 的默认工作流。
较早的工作流,部分引擎仍支持(如 Unity 的 Specular Setup)。
| 特性 | Metallic/Roughness | Specular/Glossiness |
|---|---|---|
| 贴图数量 | 3 张 (BaseColor, Metallic, Roughness) | 3 张 (Diffuse, Specular, Glossiness) |
| 金属反射色 | 由 BaseColor 提供 | 由 Specular 贴图提供 |
| 物理正确性 | 更容易保持——metallic 只有 0/1 | 需要美术手动控制 Specular 范围 |
| 引擎支持 | Unity Standard, Unreal, Godot, Filament | Unity Specular Setup, 部分移动端引擎 |
| 推荐场景 | 新项目首选 | 旧资产迁移或需要精细控制 |
实践建议:新项目统一使用 Metallic/Roughness 工作流。它更简洁,不容易出错,引擎支持最广泛。
光照效果不对是 Shader 开发中最常见的问题。以下是几种典型症状和排查方法:
| 症状 | 可能原因 | 修复方法 |
|---|---|---|
| 高光过亮 / 泛白 (Blown out) | 未做能量守恒或缺少 HDR Tonemapping | 确保 \(k_D + k_S \le 1\);启用 ACES 或其他 Tonemapping |
| 物体看起来很”平” (Flat) | 法线方向错误或 N·L 计算有误 | 将法线可视化(color = N * 0.5 + 0.5)检查是否正确 |
| 边缘菲涅尔光环过强 | \(F_0\) 值设置过高 | 非金属 \(F_0\) 应在 0.04 左右,不要超过 0.05 |
| 金属看起来像塑料 | Metallic 不是 1.0 | 纯金属 Metallic 必须为 1.0;0.5 在物理上无意义 |
| 高光形状怪异 / 拉伸 | 切线空间法线贴图的切线/副切线错误 | 检查 tangent 和 bitangent 是否正交且方向正确 |
一个非常实用的调试手法:在 Fragment Shader 中临时输出中间变量作为颜色,例如:
// 可视化法线方向
FragColor = vec4(N * 0.5 + 0.5, 1.0);
// 可视化 NdotL(漫反射贡献)
float NdotL = max(dot(N, L), 0.0);
FragColor = vec4(vec3(NdotL), 1.0);
// 可视化 Fresnel
float fresnel = pow(1.0 - max(dot(N, V), 0.0), 5.0);
FragColor = vec4(vec3(fresnel), 1.0);// 输入: N (法线), L (光照方向), V (观察方向)
vec3 N = normalize(Normal);
vec3 L = normalize(LightDir);
vec3 V = normalize(ViewDir);
// Diffuse
float diff = max(dot(N, L), 0.0);
// Specular (Blinn-Phong)
vec3 H = normalize(L + V);
float spec = pow(max(dot(N, H), 0.0), 32.0);
// Result
vec3 color = (ambient + diff + spec) * objectColor;< 上一篇: 渲染管线 | 回到目录 | 下一篇: 曲线数学 >
把当前热点继续串成多页阅读,而不是停在单篇消费。
2025-11-30 · game_math
深入渲染管线后半段:投影矩阵的构造原理、透视除法与 w 分量、视口变换,以及深度缓冲精度问题与 Reversed-Z 技巧。
2025-11-30 · game_math
详解游戏引擎中常用的标量数学函数:Lerp, InverseLerp, Remap, Clamp, SmoothStep 等。不仅有公式,更有实际应用场景。
2025-11-30 · game_math
深入浅出地讲解游戏开发中的三角函数:Sin, Cos, Atan2。从单位圆原理到圆周运动、波浪动画和朝向计算的实际应用。
2025-11-30 · game_math
深入理解游戏开发中的矩阵:TRS 变换与代码实践、矩阵乘法与组合变换、MVP 管线详解、逆矩阵、法线变换,以及旋转表示方式对比。