【Unity Shader学习笔记】Unity光照-光照衰减
1、Unity 光源类型
Unity —共支持 4 种光源类型:
- 平行光
- 点光源(Point Light)
- 聚光灯(Spot Light)
- 面光源(area light)
面光源仅在烘焙时才可发挥作用, 因此不在本节讨论范围内。
本节中, 我们学习如何处理点光源(pointlight) 和聚光灯(spotlight)。
1.1、光源类型
1.1.1、平行光
平行光的几何属性只有方向,不会衰减。
1.1.2、点光源
由空间的一个球体定义的光源。
点光源会存在光线衰减,中心点为1,边缘点为0;中间的衰减可以由一个函数定义。
1.1.3、聚光灯
这三种中最复杂的一种。照亮空间内的一个锥形区域。
中间的衰减同样可以通过函数定义,只是更加复杂。
2、Unity 光照衰减
Unity 使用一张纹理作为査找表(Lookup Table, LUT),在片元着色器中计算逐像素光照的衰减。
这样做减轻了计算压力,但是也有缺点:
- 需要预处理得到采样纹理, 而且纹理的大小也会影响衰减的精度。
- 不直观, 同时也不方便, 因此一旦把数据存储到査找表中, 我们就无法使用其他数学公式来计算衰减。
2.1、用于光照衰减的纹理
Unity 在内部使用一张名为 _LightTexture0 的纹理来计算光源衰减。
如果我们对该光源使用了 cookie,那么衰减査找纹理是 _LightTextureB0
我们通常只关心 _LightTexture0 对角线上的纹理颜色值。
例如:(0,0)点表明了与光源位置重合的点的衰减值, 而(1,1)点表明了在光源空间中所关心的距离最远的点的衰减值。
2.2、使用纹理采样
2.2.1、获得该点在光源空间上的位置
使用 unity_WorldToLight 矩阵可以把顶点从世界空间变换到光源空间。
将其与世界空间下的顶点坐标相乘即可。
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
2.2.2、纹理采样
使用这个坐标的模的平方对衰减纹理进行采样, 得到衰减值:
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
使用了光源空间中顶点距离的平方( 通过 dot 函数来得到)来对纹理采样, 之所以没有使用距离值来采样是因为这种方法可以避免开方操作。
然后, 我们使用宏 UNITY_ATTEN_CHANNEL 来得到衰减纹理中衰减值所在的分量, 以得到最终的衰减值。
2.3、使用公式采样
例如:
float distance = length(_WorldSpaceLightPosO.xyz - i.worldPosition.xyz); atten = 1.0 / distance;
Unity 没有在文档中给出内置衰减计算的相关说明,也没有开放相关接口(如聚光灯的朝向、 张开角度等)。
当然,我们可以利用脚本将相关信息传递给 Shader,但是效率、灵活性就很差了。
3、实践
3.1、在前向渲染中处理光源
首先定义第一个 Pass — BasePass 。
使用#pragma multi_compile_fwdbase
指令,保证光照衰减等光照变量可以被正确赋值。
在 BasePass 中计算场景中的环境光。
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
Base Pass 会在计算逐像素平行光时调用一次。
因为环境光、自发光只需要计算一次,因此放在 Base Pass 中计算。
如果场景中有多个平行光,Unity 会选择最亮的平行光传递给 Base Pass 进行逐像素处理。
其他平行光会按照逐顶点或在 Additional Pass 中按逐像素的方式处理。
如果场景中没有任何平行光, 那么 Base Pass 会当成全黑的光源处理。
使用 _WorldSpaceLightPos0
来得到这个平行光的方向。
使用 _LightColor0
来得到它的颜色和强度( _LightColor0
已经是颜色和强度相乘后的结果)。
随后定义 Addition Pass 。
使用#pragma multi_compile_fwdadd
指令,
使用 Blend 指令:Blend One One
声明混合模式(因为我们不希望覆盖)。
使用判断来获得光源方向、处理衰减。
最终代码如下:
Shader "Unity Shaders Book/Chapter 9/ForwardRendering" { Properties { _Diffuse ("Diffuse", Color) = (1, 1, 1, 1) _Specular ("Specular", Color) = (1, 1, 1, 1) _Gloss ("Gloss", Range(8.0, 256)) = 20 } SubShader { Tags { "RenderType"="Opaque" } Pass { // Pass for ambient light & first pixel light (directional light) Tags { "LightMode"="ForwardBase" } CGPROGRAM // Apparently need to add this declaration #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _Diffuse; fixed4 _Specular; float _Gloss; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; }; v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag(v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); fixed3 halfDir = normalize(worldLightDir + viewDir); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); fixed atten = 1.0;//光照衰减 return fixed4(ambient + (diffuse + specular) * atten, 1.0); } ENDCG } Pass { // Pass for other pixel lights Tags { "LightMode"="ForwardAdd" } Blend One One CGPROGRAM // Apparently need to add this declaration #pragma multi_compile_fwdadd #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" #include "AutoLight.cginc" fixed4 _Diffuse; fixed4 _Specular; float _Gloss; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; }; v2f vert(a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag(v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); //判断是否是平行光,获得世界坐标下光线方向 #ifdef USING_DIRECTIONAL_LIGHT fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); #else fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz); #endif fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); fixed3 halfDir = normalize(worldLightDir + viewDir); fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); //判断是否是平行光,处理衰减 #ifdef USING_DIRECTIONAL_LIGHT fixed atten = 1.0; #else #if defined (POINT) float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz; fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; #elif defined (SPOT) float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)); fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; #else fixed atten = 1.0; #endif #endif return fixed4((diffuse + specular) * atten, 1.0); } ENDCG } } FallBack "Specular" }
效果如下:能够实现非平行光源的光照衰减。

3.2、Base Pass 与 Additional Pass 的调用
Unity 中,我们可以使用帧调试器(Frame Debugger)工具来査看场景的绘制过程。
Window -> Analysis -> Frame Debugger
可以看到, Unity 首先清除颜色、深度和模板缓冲,为后面的渲染做准备;
随后 Unity 利用 Base Pass ,将平行光的光照渲染到帧缓存中;
最后调用 Additional Pass ,将点光源依次应用到物体上。
Unity 处理这些点光源的顺序是按照它们的重要度排序的。 重要度与颜色、强度、距离物体远近都有关系。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步