10.聚光灯光照衰减计算

10.多种光源-聚光灯光照衰减计算


10.1 知识点

  • 在 Unity 中,灯光组件的 Cookie 参数用于关联光照遮罩图片。

  • 对于平行光和点光源,默认不会提供光照遮罩信息。

  • 对于聚光灯,Unity 会默认提供一个 Cookie 光照遮罩,模拟聚光灯的区域性。

Unity默认提供一个 Cookie 光照遮罩

当使用聚光灯时:

  1. 光照纹理中 _LightTexture0 存储的是 Cookie 纹理信息
  2. _LightTextureB0 存储的是 光照纹理信息,其中包含衰减值。

因此:

  • 要获取聚光灯的衰减值,需要从 _LightTextureB0 中采样。
  • 要获取遮罩范围相关数据,需要从 _LightTexture0 中采样。

聚光灯衰减计算

将顶点从世界空间转换到光源空间

float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
  • 与点光源只使用 xyz不同,聚光灯在转换后的光源空间坐标中需要使用 xyzw
  • 原因是 w 在聚光灯光源空间中有特殊含义,参与后续计算。

获取聚光灯衰减信息的三步流程

//注意:以下是三步计算出来结果乘起来
fixed atten = 
(lightCoord.z > 0) * // 判断是否受聚光灯影响
tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * // 遮罩范围计算
tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; // 距离衰减计算
第一步:判断是否受聚光灯影响
fixed atten = (lightCoord.z > 0);
  • CG 中没有显式的布尔类型,0 表示 false1 表示 true。也就是说lightCoord.z > 0的返回值,条件满足时为1,条件不满足为0。
  • lightCoord.z 表示目标点相对于聚光灯光源所在的平面的距离。
  • lightCoord.z <= 0,目标点位于聚光灯照射方向的背面,不受光照影响。
  • 例如图中,如果目标点在光源点下就受光照影响。
第二步:遮罩范围计算
tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w;
  • 缩放和平移: lightCoord.xy / lightCoord.w + 0.5

    • 有点类似之前在进行纹理采样时都会进行一个 先缩放 后 平移 的操作。
    • 比如:uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    • 这样做的主要目的是因为需要把uv坐标映射到0~1的范围内再从纹理中采样
    • lightCoord.xy / lightCoord.w:将 xy 的范围缩放至 -0.5 ~ 0.5。因为聚光灯有很多横截面,需要把各横截面映射到最大的面上进行采样。.w 表示衰减系数。
    • + 0.5:平移至 0 ~ 1 范围,便于纹理采样。

  • 从Cookie 纹理信息中取值: tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w

    • tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5)从 _LightTexture0 中采样纹理,返回一个 四维向量 (RGBA)。
    • .w取该四维向量的Alpha 值,表示衰减或遮罩强度,决定光在这个位置上的衰减效果。如果是黑色取出来是0,白色取出来是1,灰色取出来的值介于0-1之间。参与下一步计算。
第三步:距离衰减计算
tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
  • 点光源和聚光灯的距离衰减计算规则一致。
  • dot(lightCoord, lightCoord) 是点乘操作,计算得到的结果是 distance²(离光源的距离的平方),即x² + y² + z² = 离光源距离 distance²
  • .rr.xx一样,是一种特殊的写法,用于构建一个 float2 类型的UV坐标。这里的UV坐标相当于 (distance², distance²)
  • 根据距离光源原点的平方,通过 _LightTextureB0 获取衰减值。
  • UNITY_ATTEN_CHANNEL 宏用于获取衰减值所在的分量。这就像在颜色中选取一个分量(如rg)。使用宏的原因是,不同平台的分量对应关系不同,使用宏可以确保在各个平台上都能得到正确的分量。
  • 注意:
    • 聚光灯的光照衰减纹理为 _LightTextureB0。而点光源是_LightTexture0
    • dot 函数只计算 xyz,忽略 w

总结

首先要将顶点从世界空间转换到光源空间:

float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));

聚光灯光照衰减的计算由 遮罩衰减距离衰减 决定:

  1. 判断目标点是否能被聚光灯照到:
    fixed atten = (lightCoord.z > 0);
    
  2. 根据遮罩纹理信息,进行缩放和平移后采样,叠加遮罩衰减值:
    tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w;
    
  3. 从光照衰减纹理 _LightTextureB0 中获取按距离计算的衰减值:
    tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
    

10.2 知识点代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson10_多种光源_聚光灯衰减计算 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 聚光灯默认Cookie

        //我们知道灯光组件中有一个Cookie参数,是用来关联光照遮罩图片的
        //对于平行光和点光源,默认是不会提供任何光照遮罩信息的
        //但是对于聚光灯来说
        //Unity会默认为它提供一个Cookie光照遮罩
        //主要是用于模拟聚光灯的区域性
        //而此时 光照纹理中
        //_LightTexture0 存储的是Cookie纹理信息
        //_LightTextureB0 存储的是光照纹理信息,里面包含衰减值

        //因此
        //1.获取聚光灯衰减值时
        //  需要从_LightTextureB0中进行采样
        //2.获取遮罩范围相关数据时
        //  需要从_LightTexture0中进行采样

        #endregion

        #region 知识点二 聚光灯衰减计算

        //1.将顶点从世界空间转换到光源空间
        // float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
        // 注意:
        // 这里我们转换后和点光源不同的是
        // 点光源只会获取其中的xyz
        // 而聚光灯会获取其中的xyzw
        // 这是因为在聚光灯光源空间下的w值有特殊含义
        // 会参与后续的计算

        //2.利用光源空间下的坐标信息
        //  我们会通过3个步骤去获取聚光灯的衰减信息
        //  fixed atten = (lightCoord.z > 0) * //第一步
        //  tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //第二步
        //  tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; //第三步

        //  我们首先分析和点光源相同的部分——第三步
        //  tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; 
        //  这一步的规则和点光源规则一致,直接根据距离光源原点距离的平方从光照纹理中获取衰减值
        //  需要注意的是
        //  1.聚光灯的光照衰减纹理为_LightTextureB0
        //  2.dot函数只会计算xyz,w不会计算

        //  接着我们来分析用于进行范围判断的部分——第一、二步
        //  第一步:(lightCoord.z > 0) 
        //      CG语法中没有显示的bool类型,一般情况下 0 表示false,1表示true
        //      也就是说lightCoord.z > 0的返回值,条件满足时为1,条件不满足为0
        //      这里的z代表的其实是 目标点 相对于 聚光灯照射面 距离
        //      如果 lightCoord.z <= 0 证明在聚光灯照射方向的背面,就不应该受到聚光灯的影响
        //      也就是说这一步的主要作用,是用来决定顶点是否受到聚光灯光照的影响

        //  第二步:tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w
        //      我们以前在进行纹理采样时都会进行一个 先缩放 后 平移 的操作
        //      比如:uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
        //      而第二步中的 lightCoord.xy / lightCoord.w + 0.5 其实也是在做这样的一个操作
        //      这样做的主要目的是因为:
        //      我们需要把uv坐标映射到0~1的范围内再从纹理中采样
        //      lightCoord.xy / lightCoord.w 进行缩放后 x,y的取值范围是-0.5~0.5之间
        //      再加上0.5后,x,y的取值范围就是0~1之间,便可以进行正确的纹理采样了
        //      而lightCoord.xy / lightCoord.w 是因为聚光灯有很多横截面
        //      我们需要把各横截面映射到最大的面上进行采样
        //      最后的.w代表衰减系数 这是规则

        //  因此我们总结一下
        //  看似复杂的聚光灯光照衰减计算方式
        //  其实就是由 “遮罩衰减” 和 距离衰减 共同决定的
        //  第一步:判断是否能有机会照到光 看得到为1,看不到为0 
        //  fixed atten = (lightCoord.z > 0) * 
        //  第二步:缩放平移,映射到遮罩纹理采样 根据遮罩纹理的信息决定衰减叠加 最后的.w代表衰减系数
        //  tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * 
        //  第三步:从光照衰减纹理中取出按距离得到的衰减值
        //  tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; 

        #endregion
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏