1. 可见性(Visibility)在全局光照中的作用
在全局光照(尤其是烘焙光照、间接光照)中,“可见性”指的是某一点(或体素、表面片元)能否直接看到某个光源或其他表面。
- 这决定了光线是否能从光源或其他反射面到达该点。
- 可见性信息用于计算阴影、间接光照、光子传递等。
2. Unity全局光照系统的可见性预计算
Unity的全局光照系统主要有两种模式:Baked GI(烘焙GI)和实时GI(Enlighten)。
可见性预计算主要体现在烘焙GI流程中。
2.1 烘焙GI中的可见性预计算
a. 光照贴图(Lightmap)烘焙流程
- 场景体素化/采样
- Unity会将场景划分为一系列采样点(如表面片元、体素、探针点)。
- 可见性测试(Visibility Test)
- 对每个采样点,Unity会发射射线(Ray)到场景中的光源或其他表面,判断是否被遮挡(即是否有物体阻挡)。
- 这通常用**射线追踪(Ray Tracing)或路径追踪(Path Tracing)**算法实现。
- 结果是一个二值(可见/不可见)或概率(部分可见,考虑软阴影)。
- 存储可见性信息
- 可见性结果不会直接存储为“可见性贴图”,而是用于计算最终的光照值(直接光、间接光、阴影等),这些值被烘焙进Lightmap、Light Probe、Reflection Probe等数据结构中。
b. Light Probe(光照探针)和可见性
- Light Probe用于动态物体的间接光照采样。
- 在预计算时,Unity会对每个探针点进行可见性测试,决定其能接收到多少间接光。
c. Shadowmask与可见性
- Shadowmask模式下,Unity会为每个像素存储来自静态光源的阴影信息(即可见性),用于混合实时和烘焙阴影。
2.2 实时GI(Enlighten)中的可见性
- Enlighten采用辐射度(Radiosity)方法,预先计算场景的可见性关系(如Patch之间的可见性),以加速实时间接光照的更新。
- 这些可见性关系以稀疏矩阵等形式存储在Enlighten的数据结构中,运行时用于快速更新光照。
3. 可见性信息的存储
3.1 Lightmap
- 最终的可见性信息(阴影、间接光)被编码进Lightmap贴图中。
- Lightmap本身不直接存储“可见性”,而是存储了可见性影响下的光照结果。
3.2 Shadowmask
- Shadowmask贴图为每个像素存储了静态光源的可见性(0=被遮挡,1=可见,或灰度=部分可见)。
3.3 Probe数据
- Light Probe和Reflection Probe的数据中包含了可见性影响下的间接光照信息。
3.4 Enlighten内部数据
- Enlighten会存储Patch之间的可见性矩阵(Visibility Matrix),但这些数据对开发者不可见。
4. 可见性信息的使用
- 渲染时,Unity会查找当前像素/物体对应的Lightmap、Shadowmask、Probe数据,结合实时光源,决定最终的光照和阴影效果。
- 动态物体通过采样Light Probe间接获得可见性影响下的间接光照。
- 混合阴影时,Shadowmask用于区分静态和动态阴影。
5. 总结
- 可见性预计算是全局光照烘焙的核心步骤,决定了光照和阴影的真实性。
- 存储:可见性本身不直接暴露为贴图,而是影响了Lightmap、Shadowmask、Probe等数据的内容。
- 使用:渲染时通过查表、插值等方式获得最终光照结果。
6. 参考资料
- Unity官方文档:全局光照
- Unity官方文档:光照贴图和Shadowmask
- Unity官方文档:Enlighten
- SIGGRAPH论文:Visibility in Global Illumination
下面我将更深入地剖析Unity全局光照中可见性(Visibility)预计算、存储与使用的底层原理和实际流程,并补充一些开发者常见疑问的解答。
一、可见性预计算的底层原理
1. 预计算的目标
- 加速运行时渲染:通过离线阶段预先计算好场景中各点的可见性关系,避免实时逐像素射线检测。
- 提升间接光照和阴影的真实性:可见性决定了光线能否到达某点,直接影响光照分布和阴影形状。
2. 预计算的主要方法
- 射线投射(Ray Casting):对每个采样点,向光源或其他表面发射射线,检测是否被遮挡。
- Hemisphere Sampling:对每个点的半球空间进行多次采样,统计可见方向的比例(用于软阴影、间接光)。
- Patch-to-Patch Visibility(Enlighten等辐射度算法):将场景表面划分为Patch,计算Patch之间的可见性(即Form Factor)。
二、可见性数据的存储方式
1. Lightmap/Shadowmask
- Lightmap:存储了每个表面点的最终光照(已包含可见性影响),通常为RGB贴图。
- Shadowmask:每个像素为每个静态光源存储一个可见性值(通常为0~1的灰度),用于混合实时和烘焙阴影。
2. Probe数据
- Light Probe:每个探针点存储了球谐系数(SH),这些系数已包含可见性影响下的间接光照分布。
- Reflection Probe:存储了环境贴图,间接反映了可见性。
3. Enlighten内部数据
- Visibility Matrix:Enlighten会为Patch之间存储稀疏的可见性矩阵(Form Factor Matrix),用于实时间接光照的快速更新。
- 这些数据通常以稀疏矩阵、压缩表等形式存储在Enlighten的专有数据结构中,开发者不可直接访问。
三、可见性数据的使用流程
1. 离线阶段(烘焙时)
- Unity的烘焙器(如Progressive Lightmapper)会:
- 对每个采样点/体素/探针点进行可见性测试。
- 结合光源、表面反射率、可见性,计算直接光、间接光、阴影等。
- 将结果写入Lightmap、Shadowmask、Probe等数据。
2. 运行时(实时渲染时)
- 静态物体:直接查表(Lightmap/Shadowmask)获得光照和阴影。
- 动态物体:通过插值采样Light Probe获得间接光照(已包含可见性影响)。
- 混合阴影:Shadowmask用于区分静态和动态阴影,动态阴影通过实时Shadow Map补充。
四、开发者常见疑问解答
1. Unity是否存储了“可见性贴图”?
- Unity不会单独存储“可见性贴图”,而是将可见性影响编码进Lightmap、Shadowmask等贴图中。
2. 能否自定义可见性数据?
- Unity不开放底层可见性数据的自定义接口,但你可以通过自定义Shader、脚本等方式实现自己的可见性检测(如自定义遮挡剔除、特殊阴影效果)。
3. 可见性预计算对性能的影响?
- 预计算阶段耗时较长,但极大提升了运行时性能。可见性预计算越精细,烘焙时间越长,但光照和阴影越真实。
4. 可见性与实时阴影的关系?
- 实时阴影(如Shadow Map)在运行时动态计算可见性,烘焙阴影则在预计算阶段通过可见性测试生成。
五、可见性预计算的实际例子(简化流程)
以一个表面点P为例:
- 采样点P
- 对每个光源L
- 从P向L发射射线
- 如果射线被阻挡,则P对L不可见(阴影)
- 如果射线未被阻挡,则P对L可见(受光)
- 对每个间接光方向
- 从P向半球空间发射多条射线
- 统计未被阻挡的比例,决定P的间接光强度
- 将结果写入Lightmap/Probe/Shadowmask
六、可见性预计算的优化
- 加速结构:如BVH、KD-Tree等空间划分结构,加速射线检测。
- 重要性采样:对重要方向(如主光源方向)增加采样密度。
- 稀疏存储:只存储有用的可见性关系,减少内存占用。
七、参考论文与资料
我们继续深入,从算法细节、数据结构、开发者可控性、与其他引擎对比等角度,进一步剖析Unity全局光照中可见性(Visibility)的预计算、存储与使用。
八、可见性预计算的算法细节
1. 射线投射与遮挡检测
- 核心算法:对每个采样点(如Lightmap texel、Probe点、Patch),向光源或半球空间发射射线。
- 遮挡检测:每条射线与场景几何体(Mesh、Collider)做交点测试,判断是否被遮挡。
- 加速结构:Unity内部会构建BVH(Bounding Volume Hierarchy)或KD-Tree等空间索引,大幅加速射线与场景的相交测试。
2. Form Factor(形状因子)计算
- 在Enlighten等辐射度算法中,Patch之间的可见性用Form Factor(Fij)表示,反映了能量从Patch i传递到Patch j的比例。
- 计算Fij时,需考虑两Patch之间的可见性(即是否有遮挡),通常通过多次射线采样估算。
3. 多重采样与软阴影
- 为了获得软阴影和更平滑的间接光照,Unity会对每个点进行多重采样(Monte Carlo Integration),统计可见方向的比例。
- 采样越多,结果越平滑,但烘焙时间也越长。
九、可见性数据的存储结构
1. Lightmap/Shadowmask
- Lightmap:每个像素存储最终光照(RGB),已包含可见性影响。
- Shadowmask:每个像素为每个静态光源存储一个可见性值(通常为RGBA通道,支持最多4个光源)。
2. Probe数据
- Light Probe:每个探针点存储球谐系数(SH),这些系数已包含可见性影响下的间接光分布。
- Reflection Probe:存储环境贴图,间接反映可见性。
3. Enlighten内部数据
- Patch可见性矩阵:稀疏存储Patch之间的可见性(Form Factor),用于实时间接光照更新。
- 这些数据对开发者不可见,仅用于Enlighten内部计算。
十、开发者可控性与调优
1. 可控参数
- 烘焙分辨率:影响采样点密度,分辨率越高,可见性计算越精细。
- 采样数:影响每点的射线数量,采样越多,阴影和间接光越平滑。
- 光照模式:选择Baked、Mixed、Realtime等模式,影响可见性数据的存储和使用方式。
- 光源类型:不同类型的光源(点光、聚光、平行光)可见性计算方式略有不同。
2. 常见调优建议
- 减少不必要的静态物体:只对需要烘焙的物体设置为Static,减少可见性计算量。
- 合理分区:用Lightmap分区(Lightmap Group)控制烘焙范围,提升效率。
- 遮挡剔除:利用Occlusion Culling减少运行时的可见性计算压力(但与GI烘焙可见性无直接关系)。
十一、与其他引擎的对比
1. Unreal Engine
- UE4/UE5的Lightmass系统也采用射线投射和体素化进行可见性预计算,存储在Lightmap和Volumetric Lightmap中。
- UE5的Lumen系统则采用实时全局光照,运行时动态计算可见性(基于Screen Space和体素追踪)。
2. 自研引擎/离线渲染器
- 通常会将可见性矩阵(Visibility Matrix)或可见性贴图(Visibility Map)单独存储,便于后续多次利用。
- Unity为节省内存和提升效率,直接将可见性影响编码进最终光照数据。
十二、可见性与运行时动态变化
- 静态场景:可见性在烘焙时一次性计算,运行时查表即可。
- 动态物体/光源:Unity通过实时Shadow Map、实时GI补充动态可见性变化,烘焙数据仅适用于静态部分。
- 混合模式:Shadowmask等模式下,静态阴影用烘焙数据,动态阴影用实时Shadow Map,两者混合。
十三、可见性预计算的局限与未来趋势
- 局限:烘焙可见性只适用于静态场景,动态变化需实时补充;高分辨率下存储和计算压力大。
- 趋势:实时全局光照(如UE5 Lumen、RTXGI)正逐步普及,未来可见性更多在运行时动态计算,减少预计算依赖。
十四、伪代码示例(简化版)
for each lightmap_texel in scene:
for each light_source in scene:
direction = (light_source.position - texel.position).normalized()
if not is_occluded(texel.position, direction):
direct_light += compute_light(light_source, texel)
else:
shadow += 1
for i in range(num_indirect_samples):
sample_dir = random_hemisphere_direction()
if not is_occluded(texel.position, sample_dir):
indirect_light += sample_indirect_light(sample_dir)
store_in_lightmap(texel, direct_light, indirect_light)
十五、参考资料补充
- Unity官方博客:Progressive Lightmapper
- SIGGRAPH Course: Practical Global Illumination with Unity
- Unreal Engine文档:Lightmass