点光
撰写时间:2026-07-24
修订时间:2026-07-24
概述
点光是从一个点射向四面八方的光源。典型例子如台灯、壁灯、灯泡、街灯、广场灯等光源所射出的灯光。
与方向光因其位置无限远而射向各个表面的方向都一样的特性不同,点光离各个网格物体有一定的距离,其照射各表面的方向均不一致。因此,在片断着色器中,每个表面上的各个顶点均需要单独计算它们与具体点光之间的方向。
此外,点光有衰减的特性,其与特定网格物体的距离越长,光强衰减得越厉害,光照效果就越弱。
着色器
顶点着色器与上一章的内容完全一致。
而在片断着色器中,共有3个地方需改动。第一是删除原来的顶点属性uLightDir,改用:
第二,计算光源的方向及其与表面顶点的距离。
第三,计算因距离不同而产生的衰减值。有两种方式。
第一种方式较简单:
为防止当distance值为0时而产生分母为0的错误溢出,上面的代码将分母值加上一个很小的值。
计算出attenuation后,第二行代码将其纳入直接光照部分 (direct lightings,在计算机图形学中包括漫反射光及高光这两部分的光照) 的计算中。依此可看出,随着distance值增大,attenuation值变小,directLightings值也随之而变小。
这里需注意概念上的一个常见误区。我们经常想当然地认为attenuation指的是光照衰减值。但不是。正确的理解是,attenuation指的是因衰减而得出的光强系数。只有这样理解,directLightings与attenuation才有正比线性关系,上面第二行的代码才真正符合物理规律。
第二种方式,比较规范的公式为:
\[a = \frac {1.0} {K_c + K_l \times d + K_q \times d^2}\]
其中,\(K_c\), \(K_l\), \(K_q\)分别为常数、线性、二次衰减系数。\(d\)为距离。
我们在客户端可以这样指定这些值:
这3个参数的具体意义如下:
- Kc(常数项 / Constant)
- 作用:防止因距离d趋近于0时分母为0,以及避免光强爆炸为无穷大。
- 典型值:固定为1.0。
- 视觉效果:决定了光源最亮处(d = 0)的峰值强度。若该值小于1,靠近光源时亮度会异常刺眼;若该值大于1,整体亮度会被压暗。因而此项的值应直接设置为常数1.0。
- Kl(线性项 / Linear)
- 作用:使光强随距离呈线性递减(距离加倍,亮度减半)。
- 物理意义:在真实物理世界中,点光严格遵循平方反比定律(1 / d2),因此线性衰减并不符合物理规律。
- 实际用途:常用于渲染时视觉调优。若Kq导致光强消失得太快或太慢时,加入少量的线性项可以微调中间距离的过渡,让光照衔接更加平滑。但在PBR流程中,通常将其值设为0.0,以保持物理准确性。
- Kq(二次项 / Quadratic)
- 作用:使光强随距离呈平方反比递减(距离加倍,亮度变为原来的1/4)。
- 物理意义:这是真实物理世界中点光的衰减规律(能量按球面面积收缩)。
- 实际用途:PBR渲染中的核心衰减项。灯光看起来是否真实,将主要由此系数决定。
因我们使用PBR渲染模型,因此上面代码中将Kc值设置为常量1.0,将Kl值设置为常量0.0。
问题是如何直观地设置Kq的值?上面Kq的值为何设置为0.09?这真是一个虚幻的魔术数,我们很难把握其确切值。
但我们可以引入其他的变量来表示Kq。
第一步,在客户端中:
cutOffRadius意为,光强完全衰减至0时的半径,然后Kq的值根据此变量值求出。这样,在客户端直接调节cutOffRadius的值将非常直观。
第二步,在片断着色器中根据此值计算衰减因子。
若distance的值也正好等于3时,则attenuation的值为\(1.0 \; / \; (1 + 0 + 1) = 0.5\)。
第三步,根据Kq的值反推出在客户端所指定的cutOffRadius的值。
反推的好处是,我们无需再次额外地向着色器手工传递cutOffRadius的值,从而消除了数据冗余。
第四步,计算distance相对于cutOffRadius的百分比。
GLSL的smoothstep函数用于计算平滑(三次非线性)的中间插值。其原型为:
当\(x \leq min\)时,返回0;当\(x \geq max\)时,返回1。否则,根据\(x\)位于区间内的相对位置,在\((0, 1)\)区间内进行平滑单调递增。
因此,代码:
将distance放在\((0.0, 1.0)\)的区间内取值,则tempValue的值被钳制在\((0.0, 1.0)\)的区间内。
当tempValue的值为0,则smoothFactor的值为1,代表从光线最亮处;当tempValue的值为接近于1时,则smoothFactor的值接近于0,代表位于光线最暗处。
将attenuation与smoothFactor相乘,则attenuation在最亮时值为1,在最暗处值接近于0。
最后,将attenuation与漫反射光与高光部分的颜色值相乘即可:
最后,回到客户端,如果我们将cutOffRadius的值指定为正好与光线的长度值相等时(且光源正好位于地板正上方,此时光线在地板上的投影高度正好等于光线长度值),由于在cutOffRadius处光强已非常弱,导致在地板上可能看不到光照在地板上的渲染效果。为避免此点,可先算出光线长度,然后再加上一个常量,让该结果穿透地板一定的距离,这样,地板上也将保证有光照渲染效果了。
相应完整代码
客户端:
点光的修改完全集中于片断着色器的calcDirectLightings函数中。
运行应用。
在客户端中简单地增大或减少cutOffRadius的值,可非常直观地控制地板上光照强度及范围。
下面是衰减半径cutOffRadius与二次衰减系数Kq值所适用的一些常见场景列表。
| 场景规模 | cutOffRadius (以建模所使用的刻度为单位) | 算出的 Kq 值 | 例子 |
|---|---|---|---|
| 超小范围(仅照亮物体自身) | 0.5 ~ 1.5 | 0.4 ~ 1.0 | 物体自带自发光光晕,仅照亮自身表面 |
| 小范围(照亮附近几个物体) | 2 ~ 4 | 0.06 ~ 0.25 | 桌上台灯照亮杯子,烛光效果 |
| 中等范围(照亮半个房间) | 5 ~ 8 | 0.015 ~ 0.04 | 天花板吊灯照亮整个客厅 |
| 大范围(照亮整个场景) | 10 ~ 20 | 0.0025 ~ 0.01 | 室外路灯、篝火 |
| 超大范围(几乎全场景通亮) | 30 ~ 50+ | 0.0004 ~ 0.001 | 类似阴天环境光补光,衰减极其微弱 |
应用程序中衰减半径cutOffRadius的值为5.42,二次衰减系数Kq的值为0.034。
