Glsl点光源实现· MD GLSL 反射照明续篇:点光源与衰减
GLSL 反射光照续篇:点光源与衰减
现在来填坑。
点光源和平行光的核心区别
平行光有个简化前提:所有光线方向一致,和光源的具体位置无关,只关心一个统一的传播方向(适合模拟太阳这种极远的光源)。
但点光源不一样,它有一个实际存在于场景中的位置,光是从这个点向四面八方发散出去的。这带来两个平行光没有的问题:
- 光照方向不再固定,而是要实时计算"从物体表面指向光源"的方向,每个片元算出来的方向都不一样。
- 距离光源越远,光应该越暗——这是平行光完全不需要考虑的衰减(decay)问题。
第一步:算出方向和距离
平行光里,光照方向是直接给定的:
vec3 lightDirection = normalize(lightPosition);
点光源不能这么算,因为 lightPosition 只是光源在世界空间里的坐标,不是方向。要得到"从表面指向光源"的方向,得用上之前聊过的那个规律——两个点的坐标相减,等于减数指向被减数的方向向量:
vec3 lightDelta = lightPosition - position; // position 指向光源的位移
float lightDistance = length(lightDelta); // 顺手把距离也算出来
vec3 lightDirection = normalize(lightDelta);
这里 position 是当前片元(表面点)的世界坐标,lightDelta 就是"从表面指向光源"的向量,取模长得到距离,归一化后得到方向——一步到位,漫反射和高光的计算方式跟平行光完全一样,不用改。
第二步:衰减(decay)
这是点光源真正的新内容。直觉上可能会觉得"直接拿距离当衰减系数不就行了",但仔细想想会发现方向反了:lightDistance 是离光源越远,值越大,而光照强度应该是离光源越远,越暗——两者是反比关系,不能直接拿距离本身去乘光照强度,否则离光源越远反而越亮,完全反直觉。
所以需要把"距离"转换成一个随距离增大而减小的系数。最简单的一种做法是线性衰减:
float decay = 1.0 - lightDistance * lightDecay;
decay = max(0.0, decay);
- 距离为 0(紧贴光源)时,
decay = 1.0,光照不打折扣 - 距离越大,减去的部分越多,
decay越小 lightDecay是"衰减速率"旋钮,值越大衰减越快,有效照射范围越小- 超过一定距离后
decay会变负,现实里没有"负光照"这种东西,所以用max(0.0, decay)截断为 0——相当于给这个光源定义了一个"最大照射半径",半径大小是1.0 / lightDecay
线性衰减 vs 真实的平方反比衰减
线性衰减不是物理精确的公式,真实世界的光遵循平方反比定律:
真实光强 ∝ 1 / distance²
写成 GLSL 大概是:
float decay = 1.0 / (1.0 + lightDistance * lightDistance);
(加个 1.0 是为了避免距离趋近 0 时分母也趋近 0,数值爆炸)
两种方案怎么选:
| 线性衰减 | 平方反比衰减 | |
|---|---|---|
| 公式 | 1 - d * k |
1 / (1 + d²) |
| 优点 | 简单、好控制,能明确定义照射边界 | 更符合真实光照观感,近处亮远处快速暗 |
| 缺点 | 衰减是匀速的,不够真实 | 没有明确的"照射边界",理论上无穷远处仍有极小的光 |
| 适用场景 | 想要精确控制光照范围(比如卡通渲染) | 追求真实感的场景(PBR 渲染常用) |
这次代码里用的是线性衰减,简单直接,先跑起来看效果,后面想要更真实的手感可以随时替换这一行。
完整代码
vec3 pointLight(
vec3 lightColor,
float lightIntensity,
vec3 normal,
vec3 lightPosition,
vec3 viewDirection,
float specularPower,
vec3 position,
float lightDecay
) {
vec3 lightDelta = lightPosition - position;
float lightDistance = length(lightDelta);
vec3 lightDirection = normalize(lightDelta);
vec3 lightReflection = reflect(-lightDirection, normal);
float shading = dot(normal, lightDirection);
shading = max(.0, shading);
float specular = -dot(lightReflection, viewDirection);
specular = max(0.0, specular);
specular = pow(specular, specularPower);
float decay = 1.0 - lightDistance * lightDecay;
decay = max(0.0, decay);
return lightColor * lightIntensity * decay * (shading + specular);
}

和 directionalLight 对比一下就能看出,点光源本质上是在平行光的基础上多做了两件事:实时计算方向(而不是用固定方向)、乘上一个随距离衰减的系数。漫反射和高光那部分的公式完全没变。
小结
到这里,环境光、平行光、点光源三种最常见的光源类型都齐了:
- 环境光:模拟场景整体的基础亮度,不涉及方向计算
- 平行光:方向固定,只有漫反射+高光,没有衰减
- 点光源:方向要实时算,多了一个距离衰减,漫反射+高光的算法复用平行光那套

浙公网安备 33010602011771号