Web编程技术营地
研究、演示、创新

点光

撰写时间:2026-07-24

修订时间:2026-07-24

概述

点光是从一个点射向四面八方的光源。典型例子如台灯、壁灯、灯泡、街灯、广场灯等光源所射出的灯光。

方向光因其位置无限远而射向各个表面的方向都一样的特性不同,点光离各个网格物体有一定的距离,其照射各表面的方向均不一致。因此,在片断着色器中,每个表面上的各个顶点均需要单独计算它们与具体点光之间的方向。

此外,点光衰减的特性,其与特定网格物体的距离越长,光强衰减得越厉害,光照效果就越弱。

着色器

顶点着色器与上一章的内容完全一致。

而在片断着色器中,共有3个地方需改动。第一是删除原来的顶点属性uLightDir,改用:

第二,计算光源的方向及其与表面顶点的距离

第三,计算因距离不同而产生的衰减值。有两种方式。

第一种方式较简单:

为防止当distance值为0时而产生分母为0的错误溢出,上面的代码将分母值加上一个很小的值。

计算出attenuation后,第二行代码将其纳入直接光照部分 (direct lightings,在计算机图形学中包括漫反射光高光这两部分的光照) 的计算中。依此可看出,随着distance值增大,attenuation值变小,directLightings值也随之而变小。

这里需注意概念上的一个常见误区。我们经常想当然地认为attenuation指的是光照衰减值。但不是。正确的理解是,attenuation指的是因衰减而得出的光强系数。只有这样理解,directLightingsattenuation才有正比线性关系,上面第二行的代码才真正符合物理规律。

第二种方式,比较规范的公式为:

\[a = \frac {1.0} {K_c + K_l \times d + K_q \times d^2}\]

其中,\(K_c\), \(K_l\), \(K_q\)分别为常数、线性、二次衰减系数。\(d\)为距离

我们在客户端可以这样指定这些值:

3个参数的具体意义如下:

  1. Kc(常数项 / Constant)
    • 作用:防止因距离d趋近于0时分母为0,以及避免光强爆炸为无穷大。
    • 典型值:固定为1.0
    • 视觉效果:决定了光源最亮处(d = 0)的峰值强度。若该值小于1,靠近光源时亮度会异常刺眼;若该值大于1,整体亮度会被压暗。因而此项的值应直接设置为常数1.0
  2. Kl(线性项 / Linear)
    • 作用:使光强随距离呈线性递减(距离加倍,亮度减半)。
    • 物理意义:在真实物理世界中,点光严格遵循平方反比定律(1 / d2),因此线性衰减并不符合物理规律。
    • 实际用途:常用于渲染时视觉调优。若Kq导致光强消失得太快或太慢时,加入少量的线性项可以微调中间距离的过渡,让光照衔接更加平滑。但在PBR流程中,通常将其值设为0.0,以保持物理准确性。
  3. 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的百分比。

GLSLsmoothstep函数用于计算平滑(三次非线性)的中间插值。其原型为:

当\(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,代表位于光线最暗处。

attenuationsmoothFactor相乘,则attenuation在最亮时值为1,在最暗处值接近于0

最后,将attenuation与漫反射光与高光部分的颜色值相乘即可:

最后,回到客户端,如果我们将cutOffRadius的值指定为正好与光线的长度值相等时(且光源正好位于地板正上方,此时光线在地板上的投影高度正好等于光线长度值),由于在cutOffRadius处光强已非常弱,导致在地板上可能看不到光照在地板上的渲染效果。为避免此点,可先算出光线长度,然后再加上一个常量,让该结果穿透地板一定的距离,这样,地板上也将保证有光照渲染效果了。

相应完整代码

客户端:

点光的修改完全集中于片断着色器calcDirectLightings函数中。

运行应用

在客户端中简单地增大或减少cutOffRadius的值,可非常直观地控制地板上光照强度及范围。

下面是衰减半径cutOffRadius二次衰减系数Kq值所适用的一些常见场景列表。

场景规模cutOffRadius
(以建模所使用的刻度为单位)
算出的 Kq 值例子
超小范围(仅照亮物体自身)0.5 ~ 1.50.4 ~ 1.0物体自带自发光光晕,仅照亮自身表面
小范围(照亮附近几个物体)2 ~ 40.06 ~ 0.25桌上台灯照亮杯子,烛光效果
中等范围(照亮半个房间)5 ~ 80.015 ~ 0.04天花板吊灯照亮整个客厅
大范围(照亮整个场景)10 ~ 200.0025 ~ 0.01室外路灯、篝火
超大范围(几乎全场景通亮)30 ~ 50+0.0004 ~ 0.001类似阴天环境光补光,衰减极其微弱

应用程序中衰减半径cutOffRadius的值为5.42二次衰减系数Kq的值为0.034

参考资源

Specifications

  1. WebGL 1.0 Specification
  2. WebGL 2.0 Specification
  3. OpenGL ES 3.0 Reference Pages

JS Libraries

  1. glMatrix.net

Scientific Papers

  1. Sampling the GGX Distribution of Visible Normals
  2. HAL: Open Science