Glsl点光源实现· MD GLSL 反射照明续篇:点光源与衰减

GLSL 反射光照续篇:点光源与衰减

现在来填坑。

点光源和平行光的核心区别

平行光有个简化前提:所有光线方向一致,和光源的具体位置无关,只关心一个统一的传播方向(适合模拟太阳这种极远的光源)。

但点光源不一样,它有一个实际存在于场景中的位置,光是从这个点向四面八方发散出去的。这带来两个平行光没有的问题:

  1. 光照方向不再固定,而是要实时计算"从物体表面指向光源"的方向,每个片元算出来的方向都不一样。
  2. 距离光源越远,光应该越暗——这是平行光完全不需要考虑的衰减(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);
}

image

和 directionalLight 对比一下就能看出,点光源本质上是在平行光的基础上多做了两件事:实时计算方向(而不是用固定方向)、乘上一个随距离衰减的系数。漫反射和高光那部分的公式完全没变。

小结

到这里,环境光、平行光、点光源三种最常见的光源类型都齐了:

  • 环境光:模拟场景整体的基础亮度,不涉及方向计算
  • 平行光:方向固定,只有漫反射+高光,没有衰减
  • 点光源:方向要实时算,多了一个距离衰减,漫反射+高光的算法复用平行光那套
posted @ 2026-09-12 11:13  OvOGhostFace  阅读(26)  评论(0)    收藏  举报