直接光照
全库目录221
本文目录16
给灯光增加强度以后,地面亮了,高光和阴影却未必按同样的比例变化。因为一个表面的直接光照同时取决于光源发出了多少光、光怎样到达表面,以及材质把它反射到哪里。光源单位约束输入,距离和可见性约束传播,BRDF约束表面响应;三者需要分别求值后组合。
光源模型
Directional Light
方向光假设光源无限远,所有点使用同一个入射方向,不随位置衰减。太阳在场景尺度下常用方向光近似。
它通常影响整个相机可见区域。Forward Rendering 会在物体材质中计算;Deferred Rendering 常用一次 Fullscreen Pass 或 Compute Pass。
Point Light
点光源从一个位置向所有方向发光。在与光线垂直的接收面上,各向同性点光的照度随距离平方衰减;倾斜面还需乘入射余弦。若总光通量为 ,均匀分到 立体角,则光强 ,距离 处的法向照度为 :
时会发散,引擎需要最小半径、光源尺寸或平滑衰减。为了性能,还会设置有限影响范围,在边界前平滑降到零。
只使用线性衰减虽然容易调,但材质和灯光在不同尺度下不稳定。物理单位工作流应结合引擎的 Light Unit 和曝光约定。
Spot Light
聚光灯在点光源基础上增加方向和锥角。通常有 Inner Cone 和 Outer Cone,在两者之间做平滑过渡。
设灯轴指向灯外, 是灯到着色点的单位方向,。内外半角满足 ,所以 ;可取 ,用 平滑过渡。内外角相同时使用硬阈值,避免零分母。这个锥权重只定义形状;保持总光通量还需按角分布重新归一化。
光照贡献可以拆成:
距离衰减 × 角度衰减 × 可见性 × BRDF × NdotL每一项都应单独可视化,方便判断问题来自灯光范围、阴影还是材质。
Area Light
真实面光源的每个表面点都能发光,需要对光源面积积分。它会产生随距离变化的软阴影和具有形状的高光。
实时近似包括:
- Lightmap 烘焙;
- 多点光源近似;
- Linearly Transformed Cosines(LTC)估计多边形光源对微表面 BRDF 的积分;
- 光线追踪直接采样面积光;
- 特定形状的解析近似。
仅把 Point Light 的阴影模糊,并不能得到完整面积光响应。高光形状和能量分布也要改变。
LTC 的执行过程
线性变换余弦(Linearly Transformed Cosines,LTC)用一个经过线性变换的余弦分布近似 GGX 镜面波瓣。余弦分布对多边形的积分有高效解析形式,因此运行时可以把矩形灯变换到 LTC 空间后做边积分。
离线阶段按 Roughness 和 拟合两张 LUT:
- 逆变换矩阵的参数;
- 幅度、Fresnel 和几何修正参数。
运行时流程是:
- 用 和 建立局部正交基。
- 以 Roughness、 查询 LTC LUT。
- 把矩形灯四个顶点变换到着色点局部空间。
- 应用 LTC 逆矩阵,把 GGX 波瓣变成余弦波瓣。
- 对归一化后的多边形边做积分,并乘回幅度和 Fresnel。
为使表约定可检查,以下HLSL使用公开LTC演示中的64×64纹理布局:matrixParams 解码为按行构造的矩阵
GLSL的三向量矩阵构造按列,直接照抄到HLSL会转置。这里用数学矩阵和 mul(Minv, vector) 固定语义。幅度表的两个通道按原拟合约定组合为 ,表资产、拟合BRDF和参数映射需成套使用。
多边形积分的主体如下。输入 vertices 已从着色点出发、变到局部基底并乘过 ,仍保留长度;输入是非退化、共面的凸四边形,四点按边界顺序排列;可逆线性变换保持这些性质。单平面裁剪至多产生五个顶点,因此下方数组容量为 5。代码先裁剪 ,再积分球面边。它采用双面发光,单面光源由调用端根据原灯面法线和着色点方向剔除。
float IntegrateLTCCosine(float3 vertices[4])
{
float3 clipped[5];
uint count = 0;
for (uint i=0; i<4; ++i) {
float3 a=vertices[i], b=vertices[(i+1)%4];
bool inA=a.z>=0.0, inB=b.z>=0.0;
if (inA) clipped[count++]=a;
if (inA != inB) {
float t=a.z/(a.z-b.z);
clipped[count++]=lerp(a,b,t);
}
}
if (count<3) return 0.0;
for (uint j=0; j<count; ++j) {
float len=length(clipped[j]);
if (len<1e-7) return 0.0; // 灯面穿过着色点,超出本近似的有效输入
clipped[j]/=len;
}
float integral=0.0;
for (uint k=0; k<count; ++k) {
float3 a=clipped[k], b=clipped[(k+1)%count];
float3 c=cross(a,b);
float sine=length(c);
float cosine=clamp(dot(a,b),-1.0,1.0);
if (sine<1e-7) {
if (cosine<0.0) return 0.0; // 近反向边需另行处理灯面接触
continue;
}
integral+=c.z*atan2(sine,cosine)/sine;
}
return abs(integral)/(2.0*3.14159265359);
}对于归一化相邻方向 ,边的球面角为 。每条边贡献 ,总和除以 得归一化余弦分布的积分。近平行同向边的贡献趋于零;反向边和着色点接触灯面的情况需要专门的近场策略,代码的零值回退不代表真实照度为零。
构造局部基底时,先把 投影到切平面:。若其长度接近零,选一个与 不平行的参考轴生成 ;归一化后取 。每个灯顶点 的局部向量为 ,随后应用上述逆矩阵。LTC所得积分已包含接收面余弦与面积投影,不应再乘一次 或统一的距离平方衰减;最后乘灯的出射辐射亮度和拟合幅度。
LTC不自动计算场景遮挡。另加单一中心阴影也只是可见性近似,精确面积光遮挡需要在积分中区分灯上各点的可见性。
Horizon Clipping 与 LUT 边界
变换后的灯光多边形可能跨过着色点的切平面。局部空间中 的方向位于地平线以下,不应参与上半球余弦积分。Horizon Clipping 会逐边判断端点符号,在边穿过 时插入交点,再对剩余的 3–5 个顶点做球面边积分。完全位于地平线下时贡献为 0;裁剪后顶点接近重合时要避免归一化零向量。
本例LUT坐标为 (roughness,sqrt(1-saturate(dot(N,V)))),使用 uv*(63/64)+0.5/64 映射到首尾纹素中心。LUT 的两个输入是 Roughness 与 的约定映射。运行时需要 Clamp 到拟合域,并使用半纹素缩放和偏移落在纹素中心。Roughness 为 0 或 接近 0 时最容易碰到拟合边界和数值尖峰,生产实现常设置与离线拟合一致的最小 Roughness。可把灯光缓慢移过地平线:若没有裁剪,贡献会在背面翻转或突然爆亮;若 LUT 地址错误,高光会在掠射角出现整条边界色带。
单位与空间分布
光的单位和曝光
现代引擎可能使用:
- Lumen:光通量;
- Candela:单位立体角光强;
- Lux:表面照度;
- Nit/cd/m²:显示或发光表面亮度。
数值只有在单位、引擎实现和曝光一致时才可比较。把灯光强度乘十后“看起来没变”,可能是 Auto Exposure 抵消了变化。
光源单位换算
同样填入 1000,Point Light 的 Lumen、Spot Light 的 Lumen/Candela 和 Area Light 的 Lumen/Nit 可能得到完全不同的场景亮度。Spot Cone 变窄时,如果总光通量保持不变,中心方向光强会提高。
引擎有时提供“保持亮度”或“保持总能量”的不同选项。修改光源尺寸、Cone Angle 后,需要确认哪个量被保持。
Light Cookie 和 IES
Light Cookie 用纹理调制光源的空间分布,可以表现窗格、云影或投影图案。它会增加纹理采样,也可能需要额外 Atlas 和更新管理。
IES Profile 描述现实灯具按方向测得的光强分布。它不是一张普通装饰纹理,使用时要配合正确的光源单位和朝向。
Cookie/IES 只改变光源分布。材质 BRDF、阴影可见性和距离衰减仍然独立计算。
光源尺寸的影响
增加光源半径或面积通常会同时影响:
- 阴影半影;
- 镜面高光形状和宽度;
- 近距离照度分布;
- 光追或随机采样的方差。
如果只让阴影变软,高光仍保持点状,就不是完整的面积光模型,而是视觉近似。项目可以接受这种近似,但文档要明确。
各向同性点光总光通量1000流明,光强为1000/(4π)≈79.58坎德拉。与光线垂直的接收面距灯2米时照度约19.89勒克斯,4米时约4.97勒克斯;表面法线与来光夹60°则再乘0.5。固定曝光以后,这份四倍距离平方关系可以在光源传播项中直接检查。
如果1000流明被重新分配到更窄的聚光角域,总光通量保持而中心方向光强增加;若接口保持坎德拉,缩角减少总光通量。两种行为都可合理,参数单位决定哪个量应保持。不能把点光的79.58直接当作面积光的辐亮度输入LTC。
表面求值与光源组织
直接光照的典型计算
对一个光源,简化形式:
visibility来自阴影;attenuation来自距离和灯光形状;- 对点光, 代表离散方向贡献,如 ,它不能被当成一个有限立体角上的普通辐射亮度样本;对面积光则必须做面积或方向积分;
- 、 是材质 BRDF。
多光源成本
Forward Rendering 中,一个物体可能对每盏影响它的灯重复计算,甚至重复 Draw。
Deferred/Forward+ 常先筛选哪些灯影响当前屏幕区域:
- Directional Light:全屏;
- Point/Spot Light:Light Volume;
- Tiled/Clustered:每个 Tile/Cluster 保存灯光列表。
因此“有 GBuffer”并不会让 100 盏灯自动只算一次。每个像素仍要累加影响它的灯,只是材质几何信息不必为每盏灯重复生成。
光源剔除
CPU 可以先用 Bounds 筛选物体和灯。GPU Tiled/Clustered Culling 进一步把灯分配到屏幕区域。
灯光列表过长时,成本仍然很高。需要限制灯光范围、重叠数量、阴影灯数量和每 Cluster 上限,并记录溢出行为。
验证方法
- 固定曝光后比较不同 Light Unit。
- 单独显示距离和角度衰减。
- 用多个重叠小灯测试 Tile/Cluster 灯光列表。
- 在帧捕获中查看逐灯 Pass、Light Volume 或 Compute Light List。
- 分别关闭阴影、Diffuse 和 Specular,确认各项归属。
test_gi.py 用独立的面积数值积分核对矩形余弦边积分,并检验尺度不变性、地平线裁剪与全背面情况。正文LTC函数通过DXC编译;拟合表误差、阴影可见性与目标GPU画面尚未验证。
相关主题
参考资料
- Google Filament, Lighting.
- Epic Games, Physical Lighting Units.
- Heitz 等:Real-Time Polygonal-Light Shading with Linearly Transformed Cosines,线性变换与余弦多边形积分;作者演示着色器 的矩阵布局、幅度和地平线处理。
- LearnOpenGL,
src/8.guest/2022/7.area_lights.