光照衰减
一、定义与类型
1.定义
光照衰减通常是指在渲染过程中考虑光照在空间中传播时的减弱效应 例如: 任何光源的光照亮度会随着物体离光源的距离增加而迅速衰减
2.常见的光线衰减的计算方式
- 线性衰减 光强度和距离成线性关系。即:光照衰减与光源到被照射表面的距离成正比。
- 平方衰减 光强度和距离的平方成反比。这种模型更加符合现实世界中光照的特性,因为光在空间中的传播通常会遵循平方衰减规律
二、Unity 中的光照衰减
Unity 中为了提升性能,我们一般不会直接通过数学公式计算光照衰减 而是使用一张纹理作为查找表(LUT,lookup table) 在片元着色器中计算逐像素光照的衰减
Unity Shader 中有一个内置的纹理类型变量 \_LightTexture0 该纹理中存储了衰减值相关数据 Unity 内部预先计算好了相关数据 并存了改纹理中,避免重复计算,提升性能
其中:
- 对角线 表明了光源空间的中不同位置的点对应的衰减值
- 起点 表示和光源重合的点的衰减值
- 终点 表示光源空间中离光源最远的点的衰减值
一般我们直接从\_LightTexture0 中进行纹理采样后,利用其中的 UNITY_ATTEN_CHANNEL 宏来得到衰减值
1 | tex2D(_LightTexture,对应纹理的UV坐标).UNITY_ATTEN_CHANNEL |
注意: 如果光源存在 cookie,也就是灯光遮罩 那么衰减值纹理就是 _LightTextureB0
三、光源空间变化矩阵
Unity Shader 中 内置的光源空间变换矩阵 将世界空间下的位置转化到光源空间下
- 老版本:_LightMatrix0
- 新版本:unity_WorldToLight
由于我们需要从 _LightTexture0 光照纹理中取出对应的衰减数据 因此我们需要将顶点位置从世界空间转化到光源空间中 然后再来从中取出衰减数据
通过矩阵变换的形式来 将顶点从世界空间中转化到光源中
1 | mul(unity_WorldToLight,float4(worldPos,1)) |
三、点光源光照衰减的实现
1.重要知识
- 从纹理中取出衰减数据
- 使用灯光遮罩,从 _LightTexture0 纹理中取
- 不使用灯光遮罩,从 _LightTextureB0 纹理中取
- 采样之前,需要把顶点坐标转化到光源空间中 变换矩阵:unity_WorldToLight
2.实现步骤
注意: 一般点光源我们不会为其添加 cookie 光照遮罩 一般想要使用光照遮罩都会在聚光灯中使用 因此我们点光灯不会考虑 cookie 纹理问题
- 将顶点从世界空间转化到光源空间中 float3 lightCoord = mul(unity_WorldToLight,float4(worldPos,1)).xyz; lightCoord 是计算出来的 光源坐标系下的顶点根据光源范围 range 规范化后的坐标 相当于是一个模长为 0~1 之间的向量
- 利用该光源空间下的坐标来计算离光源的距离 并利用距离参数,从纹理中取样 fixed atten = tex2D(_LightTexture,dot(lightCoord,lightCoord).xx).UNITYATTEN_CHANNEL;
解释:- dot(lightCoord,lightCoord) 是为了通过点乘得到 结果 x² + y² + z² = distance² 离光源的距离
- xx 是一种特殊写法,目的是为了构建一个 float2 代表 uv 坐标 这里的 uv 坐标相当于是(distance²,distance²) 这里利用 distance² 作为 UV 坐标,而不是 distance 有以下两个原因
- 避免开平方带来的消耗
- 采用平方衰减更加符合现实生活中的光照特性 人眼对亮部不敏感,而对暗部更敏感,这样我们就可以将 衰减值的精度 集中在较远的地方 即:distance 为 0.5 时 distance² 为 0.25,这样在 LUT 查找表中大部分的值都会留给比较远的部分
四、聚光灯光照衰减的实现
1.聚光灯默认的 cookie
在灯光组件中有一个 Cookie 参数,是用来关联光照遮罩图片的 对于平行光和点光源来说,默认不会提供一个任何光照遮罩信息的 但是对于聚光灯来说,Unity 会默认给他提供一个 Cookie 光照遮罩 主要用于模拟聚光灯的区域性 而此时 在光照纹理中 _LihgtTexture0 存储的是 Cookie 纹理信息 _LightTextureB0 存储的是光照纹理信息,里面包含衰减值
2.聚光灯衰减计算
- 将顶点从世界空间转化到光源空间中 float3 lightCoord = mul(unity_WorldToLight,float4(worldPos,1)); 注意:这里与点光源不同的是这里要保留 w 的值,这里的 w 有特殊意义,会参与后续计算
- 利用光源空间下的坐标信息 获取聚光灯的衰减信息 fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0,lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL; 这里我们一般会通过三个步骤去获取
- (lightCoord.z > 0)
- CG 语法中没有显示的 bool 类型,一般情况下:0 表示 flase,1 表示 ture 也就是说,这里的 (lightCoord.z > 0) 满足条件为 1,不满足就为 0
- 这里的 z 代表的是 目标点相对于聚光灯位置 如果 z<=0,代表 该点在聚光灯的统一平面或者背面,属于照射不到的区域
- 主要作用就是来决定顶点是否受聚光灯光照的影响
- tex2D(_LightTexture0,lightCoord.xy / lightCoord.w + 0.5).w
- 类似于进行纹理采样时计算 uv 坐标(先缩放后平移的操作)
- _LightTexture0,lightCoord.xy / lightCoord.w 是因为聚光灯有很多横截面,我们需要把各个横截面映射到最大的面上进行采样
- _LightTexture0,lightCoord.xy / lightCoord.w 进行缩放后,xy 的取值范围是[-0.5,0.5],+0.5 后 xy 的取值范围就是[0,1]之间了,就可以正确采样了
- tex2D(_LightTextureB0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL 这里理解和点光源一致,需要注意的是
- 聚光灯的采样纹理为_LightTextureB0
- dot 函数只会计算 xyz,w 不进行计算
- (lightCoord.z > 0)