10.多种光源-聚光灯光照衰减计算
10.1 知识点
聚光灯默认 Cookie
在 Unity 中,灯光组件的
Cookie
参数用于关联光照遮罩图片。对于平行光和点光源,默认不会提供光照遮罩信息。
对于聚光灯,Unity 会默认提供一个 Cookie 光照遮罩,模拟聚光灯的区域性。
当使用聚光灯时:
- 光照纹理中
_LightTexture0
存储的是 Cookie 纹理信息。 _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
表示false
,1
表示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
宏用于获取衰减值所在的分量。这就像在颜色中选取一个分量(如r
、g
)。使用宏的原因是,不同平台的分量对应关系不同,使用宏可以确保在各个平台上都能得到正确的分量。- 注意:
- 聚光灯的光照衰减纹理为
_LightTextureB0
。而点光源是_LightTexture0
。 dot
函数只计算xyz
,忽略w
。
- 聚光灯的光照衰减纹理为
总结
首先要将顶点从世界空间转换到光源空间:
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
聚光灯光照衰减的计算由 遮罩衰减 和 距离衰减 决定:
- 判断目标点是否能被聚光灯照到:
fixed atten = (lightCoord.z > 0);
- 根据遮罩纹理信息,进行缩放和平移后采样,叠加遮罩衰减值:
tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w;
- 从光照衰减纹理
_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