必须用 vec3 output = vec3(0.0) 初始化,否则未定义行为导致噪点或全黑;点光源需三段式衰减;聚光灯用 smoothstep 避免硬边;uniform 数组须配计数器防止性能崩坏。

多光源叠加必须用 vec3 output = vec3(0.0) 初始化
不初始化或写成 vec3 output(未赋初值)会导致输出颜色随机,画面出现大片噪点或全黑。GLSL 中未初始化的局部变量内容不可预测,尤其在循环叠加多个光源时,第一步加法就可能崩掉整个颜色计算链。
常见错误现象:场景中物体忽明忽暗、边缘闪烁、部分区域完全无光照响应。
- 务必写成
vec3 output = vec3(0.0),不能省略= vec3(0.0) - 如果后续要叠加环境光,建议在最开头加上:
output += u_ambient_color * v_albedo; - 所有光源函数返回的都应是
vec3,不能混用vec4或直接乘 alpha
点光源必须做距离衰减,否则近处过曝远处失效
点光源物理特性是能量随距离平方衰减,但直接用 1.0 / (distance * distance) 容易导致除零或数值爆炸。实际工程中普遍采用三段式衰减公式:1.0 / (k0 + k1 × d + k2 × d²),其中 k0、k1、k2 是预设常量。
使用场景:火把、吊灯、角色手持光源等需要明显“照亮范围”的效果。
立即学习“C++免费学习笔记(深入)”;
-
constant(常数项)防止近距离除零,通常设为 1.0 -
linear(线性项)控制衰减坡度,常用 0.09 -
quadratic(二次项)决定远端截止,常用 0.032 - 计算前必须先算
float distance = length(light_pos - v_frag_pos),且确保坐标空间统一(都在视图空间或世界空间)
聚光灯的 cos(θ) 截断要用 smoothstep 避免硬边
直接用 if (cos_theta > cos_cutoff) 会产生明显的明暗分界线,像贴了张圆形贴纸。真实手电筒光有柔和过渡,需要用 smoothstep(cos_outer, cos_inner, cos_theta) 做平滑插值。
参数差异:outer cutoff 角度比 inner cutoff 大 5°–10°,例如 inner=12.5° → cos_inner = cos(radians(12.5)) ≈ 0.976,outer=17.5° → cos_outer = cos(radians(17.5)) ≈ 0.953。
- 不要手写
pow或clamp模拟过渡,smoothstep硬件优化好、插值连续 -
light_dir和spot_dir必须同为空间(推荐视图空间),否则dot结果无意义 - 聚光灯函数里别漏掉衰减项——它既是方向限制,也受距离影响
Uniform 数组大小和实际数量必须严格分离
GLSL 不支持动态长度数组,所以得用固定上限(如 #define POINT_LIGHT_MAX_NUM 16),再传一个 int u_point_light_count 告诉着色器“这次真用了几个”。否则遍历全部 128 个点光源会严重拖慢帧率,甚至触发驱动降频。
性能影响:每多一个点光源,片段着色器就要多跑一次完整光照计算(含归一化、点积、衰减、高光),16 个点光源 ≈ 单光源 16 倍开销。
- CPU 端更新 uniform 时,只填前
u_point_light_count个元素,其余保持默认值(避免脏数据) - 结构体字段顺序必须和 C++ 端内存布局一致,否则
glUniformBlockBinding读出会错位 - 如果某帧没用聚光灯,
u_spot_light_count应设为 0,循环for(int i=0; i<u_spot_light_count i> 就自动跳过</u_spot_light_count>
实际最难调的不是公式,而是三个光源的坐标空间是否真正对齐——平行光方向、点光源位置、聚光灯方向,如果混用世界空间和视图空间,叠加结果会漂移、翻转或局部失光,这种问题往往要花半小时以上查矩阵传递逻辑。


















