GPU Gems 1 · Chapter 19. Image-Based Lighting

把通常位于无限远的 cube map 放入有限 lighting space,结合反射求交、Fresnel、diffuse IBL 与接触阴影获得更可信的环境光照。

学习目标

  • 能解释普通 cube map 为什么产生无限远反射,并用 lighting space 描述有限环境中的对象位置
  • 能实现局部化反射的顶点与片元数据流:变换位置、法线、切线和视线,求反射射线与单位球的交点
  • 能把 Fresnel attenuation、bump normal、surface color 与反射 lookup 组合,并识别坐标、半径和 miss 处理的边界
  • 能设计 diffuse IBL、specular IBL 与接触阴影的混合方案,在真实感、稳定性和性能之间做出取舍

普通 cube map 假设环境无限远:反射向量只决定“从哪个方向看”,不会因为物体在房间里移动而改变反射图案的尺度和位置。这个假设对天空或远处环境很划算,但在小房间、窗户、显示器和可识别的墙面上,反射会像贴在物体表面,失去空间对齐。

把环境辐射编码到 cube map 等纹理中,再以法线、反射方向或预卷积结果近似积分。GPU Gems 1 第 19 章的重点不是换一种纹理格式,而是用少量 shader 数学让这个环境具有大小、位置和空间对齐。

localized IBL:从无限远反射到有限房间worldobject + cameralighting spacePₗfinite roomreflectionray / sphereshadeFresnel + IBL同一 cube map + 新坐标系与交点数学 = 更贴合场景的反射
局部化 IBL 的关键不是重建 cube map,而是让同一张环境图在有限空间中随对象位置产生正确的反射尺度与对齐。

1. 为什么要把无限环境局部化

无限 cube map 的反射方向通常是 R = reflect(V, N),其中 V、N 只描述表面方向;对象的位置 P 不参与 lookup。因此模型从房间的一端移动到另一端时,窗户和墙面在反射中的角度与尺度不会跟着移动。视觉上,环境像粘在物体上,而不是物体在环境中运动。

解决的是位置缺失:把环境体积当作一个半径为 1 的单位空间,对象的 position、normal、tangent、binormal 和 eye vector 都转换到这个坐标系。环境的真实尺寸和方向由应用传入的 lighting transform 控制,动画时只需更新对象在该空间中的位置。

并没有改变 cube map 的采样硬件,而是改变了查询方向的构造。反射射线从 lighting-space 的表面位置出发,与以原点为中心的单位球求交;交点方向随后作为 cube map 的地址。对象移动时,起点改变,交点也改变,反射中的房间特征便会重新对齐。

lighting space:把有限房间写进反射方向surface PₗRₗray / unit sphereb = −2 · dot(Rₗ, Pₗ)c = dot(Pₗ, Pₗ) − 1b² − 4c → hithit point → texCUBE调试期可把 miss 显示为红色,及时暴露对象跑出 lighting volume
反射射线必须同时携带方向与起点;起点随对象在 lighting space 中的位置变化,交点才会让环境特征产生局部化的尺度和对齐。

这是一种有限环境的近似:球或立方体只是 texturing hardware 友好的边界,不会自动表达真实房间的所有遮挡。开发阶段可以把不存在交点的像素显示为醒目的红色,避免 miss 被误认为是“材质颜色”。生产路径再决定使用 fallback、裁剪对象、扩大 volume 或回退到无限反射。

2. 顶点阶段:同时输出 world 与 lighting 数据

顶点 shader 需要把常规的 clip-space position 保留下来,同时把几何数据带入 lighting space。法线、切线和双切线使用 inverse-transpose 变换,位置和眼睛位置使用普通位置变换。若 CPU 愿意预先拼接矩阵,可以减少重复乘法,但要保留两套空间的命名,避免 fragment 阶段混用。

struct VertexOutput {
  float4 clipPosition : POSITION;
  float2 uv : TEXCOORD0;
  float3 lightingNormal : TEXCOORD1;
  float3 lightingTangent : TEXCOORD2;
  float3 lightingBinormal : TEXCOORD3;
  float3 lightingEyeVec : TEXCOORD4;
  float3 lightingPos : TEXCOORD5;
};
 
VertexOutput Localize(VertexInput input, Matrices m) {
  VertexOutput output;
  float4 pw = mul(m.world, float4(input.position, 1.0));
  output.clipPosition = mul(m.worldViewProj, float4(input.position, 1.0));
  output.lightingPos = mul(m.lighting, pw).xyz;
  output.lightingEyeVec = m.lightingEyePosition - output.lightingPos;
  output.lightingNormal = mul(m.lightingInverseTranspose, mul(m.worldInverseTranspose, input.normal)).xyz;
  output.lightingTangent = mul(m.lightingInverseTranspose, mul(m.worldInverseTranspose, input.tangent)).xyz;
  output.lightingBinormal = mul(m.lightingInverseTranspose, mul(m.worldInverseTranspose, input.binormal)).xyz;
  output.uv = input.uv;
  return output;
}

传到片元阶段的数据应满足三个不变量:位置和方向在同一个 lighting space,法线与切线仍然可以归一化,反射所用的 eye vector 方向与应用的 reflect 约定一致。先验证无 bump 的法线,再加入法线贴图,能把坐标问题和高频材质问题分开。

3. 片元阶段:反射射线与环境球求交

片元阶段先构造局部法线,再从 lighting-space 的视线向量得到反射向量。假设环境球半径为 1、球心在原点,射线为 P + tR。代入球方程后可写成:

b=2(RP),c=PP1,Δ=b24cb = -2(R \cdot P), \qquad c = P \cdot P - 1, \qquad \Delta = b^2 - 4c

当判别式为正且交点在有效方向上,就能求出 near intersection,将该点作为 cube map 方向。实际 shader 会使用一个很小的 epsilon 排除数值误差;没有交点的像素不应静默采样一个随机方向。

片元伪代码如下:

float4 LocalizedReflection(VertexOutput input, samplerCUBE envMap,
                           sampler2D normalMap, Material material) {
  float3 n = normalize(input.lightingNormal);
  float3 t = normalize(input.lightingTangent);
  float3 b = normalize(input.lightingBinormal);
  float3 bump = material.bumpiness * (tex2D(normalMap, input.uv).xyz - 0.5.xxx);
  float3 nb = normalize(n + bump.x * t + bump.y * b);
  float3 v = normalize(input.lightingEyeVec);
  float fresnel = FresnelWeight(dot(v, nb), material.krMin, material.kr, material.fresnelExp);
  float3 r = normalize(reflect(v, nb));
  float quadraticB = -2.0 * dot(r, input.lightingPos);
  float quadraticC = dot(input.lightingPos, input.lightingPos) - 1.0;
  float discriminant = quadraticB * quadraticB - 4.0 * quadraticC;
  if (discriminant <= 0.0) return material.debugMissColor;
  float nearT = (-quadraticB - sqrt(discriminant)) * 0.5;
  if (nearT <= 0.0) return material.debugMissColor;
  float3 hit = nearT * r - input.lightingPos;
  return material.surfaceColor * fresnel * texCUBE(envMap, hit);
}

4. 让反射更像材质,而不是纯色贴图

在正视表面时降低反射,在掠射角提高反射,使塑料、涂层和金属边缘更可信。可以使用 KrMin + (Kr - KrMin) * pow(1 - abs(dot(V, N)), exponent),也可以把一维曲线预烘焙为 LUT。纯光滑金属可以简化为固定反射强度,但真实金属仍可能因污渍和表面状态出现颜色与强度变化。

normal map 会把无 bump 的法线 Nu 改为带细节的 Nb,从而改变反射方向。高频 bump 如果直接用于 Fresnel 夹角,边缘可能出现 sparkle;一个实用对照是用 Nu 计算 Fresnel,用 Nb 计算反射方向,然后根据目标材质决定是否合并。

反射增强:Fresnel attenuation + bump normalreflection weightnormal viewgrazing viewbumpnormal map → Nb → Rb先用 unbumped N 稳定 Fresnel,再决定是否让高频 bump 影响边缘反射
Fresnel 控制视角相关的反射强度,bump 控制局部反射方向;两者应分别验证,避免用高频法线把边缘反射变成闪烁。

表面颜色也应作为材质决策:金属反射可以吸收表面颜色,塑料和水等 dielectric 往往使用白色或单独的 tint。环境图的坐标约定还可能存在上下方向差异,尤其是不同工具链混用 OpenGL 与 DirectX 资源时,必须用一张已知方向的测试图验证 y 翻转。

5. diffuse IBL:低频环境的另一条路径

把 diffuse 环境项从全方向积分变成以 N 为 lookup 的纹理。它的频率很低,所以由对象位置偏离环境中心造成的误差通常不如高光显眼;但它仍然编码了某个参考点的可见半球,离开中心越远,结果可能越不准确。

specular 反射使用反射向量或局部化交点,diffuse 则常用法线索引预卷积图。二者可以组合成一个便宜的基础光照,再用少量直接光、AO 或额外的 shadow pass 处理接触和局部方向性。

IBL 的最后一公里:diffuse、specular 与接触阴影environmentpreconvolvenormal → diffusereflection → speculargroundcontact shadowIBL 提供环境外观;接触阴影提供“物体站在哪里”的证据
diffuse IBL 频率低、误差不易察觉;IBL 阴影不必模拟全部潜在光源,优先保证接触阴影让物体站在场景里。

IBL 阴影的难点在于环境图可能包含许多潜在光源,而 shadow map 或 stencil volume 通常来自有限方向。一个可控的策略是选择环境图中最亮方向生成主要阴影;另一个策略是只做黑白或彩色的接触阴影,避免对象在地面上漂浮。阴影不必完美解释每个环境像素,但必须服务于空间关系。

6. 背景代理与发布边界

局部 cube map 既可以只用作物体反射,也可以用简单的 room cube 作为背景代理。代理几何不需要完整 UV 和法线,只需在同一个 lighting transform 下把位置映射为 cube map lookup。这样能让反射物体和背景中的窗、墙、灯具对齐,代价是房间边界外的视角会出现弯曲或投影失真。

使用代理背景时,应把相机尽量放在 lighting volume 中心附近;墙和天花板是平面时,让它们对齐 volume 边界会更稳定。对象跑出球体或房间边界时,选择 fallback 颜色、裁剪反射、切换到无限环境或扩大 volume,不能把异常交给用户。

7. 动手实验:移动对象,观察环境是否跟着走

先预测:在 infinite 模式中移动 object offset,反射图案只会轻微改变或保持同一方向感;在 localized 模式中,窗户和墙面反射会随对象位置发生尺度和对齐变化。提高 Fresnel exponent 会让掠射角边缘更集中,提高 bumpiness 会让反射细节更碎;打开接触阴影则会增强对象与地面的连接。

Localized IBL Lab

先猜:对象在房间里移动,局部反射为何会改变?

可交互
finite lighting spacealign 0.81reflection scales and aligns with room features

观察:localized 模式中,object offset 会改变反射与房间特征的对齐;Fresnel 增强掠射角反射,bump 改变局部反射方向;接触阴影负责把对象钉在地面上。

这个实验是确定性的示意模型,不替代实际 cube map 与 GPU profile。工程验证应使用一张带明显窗格、墙边和灯具的测试环境,在对象平移、镜头绕行、normal map 开关、进入 volume 边界以及不同 HDR 曝光下分别截图对比。

小结:用少量数学换取更强的空间感

  • image-based lighting 用环境图提供丰富的全方向光照,但普通 cube map 默认环境无限远。
  • lighting space 与 localized cube map 把对象位置带进反射查询,射线与单位球求交后再查 environment map。
  • 顶点阶段要同时输出位置、法线、切线、双切线和视线的 lighting-space 数据;片元阶段先验证坐标,再加入 bump 和 Fresnel。
  • diffuse IBL 可以用 preconvolved diffuse cube map 快速查找,specular IBL 用反射方向或局部交点保持高光空间感。
  • IBL 阴影不需要覆盖所有潜在光源,优先保证接触阴影和背景代理的空间关系,再按预算增加 shadow map 或 stencil pass。

练习

问题 1|改 Demo 代码 将无限远 cube map 改为 localized cube map。请列出顶点阶段必须新增的三个 lighting-space 输出,并说明片元阶段为什么不能只使用反射向量。

问题 2|推导题 某片元在 lighting space 中 P = (0.3, 0, 0),反射方向 R = (1, 0, 0),单位球半径为 1。计算 bc 和判别式,并判断射线正方向是否有交点。

问题 3|独立实现题 为一个会在室内房间移动的金属角色设计 IBL 方案。要求窗户反射能够对齐、角色脚下不漂浮,并且角色移出局部 volume 时不出现异常颜色。请写出你的路径和验证清单。

名词解释

本章出现的专业名词,用大白话再讲一遍。

image-based lighting
用环境图近似多方向环境光照与反射的渲染方法。
lighting space
相对于环境体积定义,用于表达表面位置和方向的局部坐标系。
localized cube map
将 cube map 解释为有限环境并让 lookup 随对象位置变化的技术。
Fresnel attenuation
根据视线与法线夹角调节反射强度的边缘增强近似。
preconvolved diffuse cube map
预先积分 Lambertian 环境贡献、运行时按法线查找的 diffuse 环境纹理。
contact shadow
用于把对象与承接表面连接起来的局部阴影线索。

资料与写作方式声明

本章以GPU Gems 1 · Chapter 19. Image-Based Lighting公开完整正文核定章节范围、事实坐标与时代语境,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…