最近学习冯乐乐的unity shader入门精要中有关SPOT光源的光照衰减计算问题!
P196 9-3-1节
首先贴上乐乐书上的代码Additional Pass部分的代码
Pass{
Tags{"LightMode"="ForwardAdd"}
blend one one
LOD 200
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
float _Gloss;
fixed4 _Specular;
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 uv:TEXCOORD0;
};
struct v2f
{
float4 pos:SV_POSITION;
float3 worldpos:TEXCOORD2;
float3 worldnormal:TEXCOORD1;
float2 uv:TEXCOORD3;
};
v2f vert(a2v v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.worldnormal=UnityObjectToWorldNormal(v.normal);
o.worldpos=mul(unity_ObjectToWorld,v.vertex).xyz;
o.uv.xy=v.uv.xy;
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);
#endif
fixed3 worldViewDir=normalize(_WorldSpaceCameraPos-i.worldpos);
fixed3 halfdir=normalize(worldLightDir+worldViewDir);
fixed3 albedo=_Color.rgb;
fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(worldLightDir,worldnormal));
fixed3 specular=_LightColor0.rgb*_Specular*pow(max(0,dot(worldnormal,halfdir)),_Gloss);
#ifdef USING_DIRECTIONAL_LIGHT
float 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);
}
ENDCG
}
主要计算的是下列代码,这些代码在#include "AutoLight.cginc"里面都有定义
#ifdef USING_DIRECTIONAL_LIGHT
float 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
USING_DIRECTIONAL_LIGHT当该Pass使用的是平行光时unity会定义它。POINT、SPOT分别问点光源和聚光灯。
这里讨论聚光灯情况:unity_WorldToLight会将此像素的世界坐标变换到光源空间下的坐标!
tex2D(_LightTexture0,lightCoord.xy/lightCoord.w+0.5).w这句代码很关键,lightCoord.xy/lightCoord.w这一句的含义是像素点在聚光灯空间的方位与聚光灯Z轴的夹角(分X、Y),这个值只有在[-0.5,-0.5]到[0.5,0.5]之间才有效(后面加了0.5),我们知道对纹理采样时UV坐标的范围在[0,0]到[1,1]之间。也就是说在[-0.5,-0.5]到[0.5,0.5]之外的部分是没有光的。然后通过上面unity_WorldToLight矩阵进行变换后只有在聚光灯角度范围之内的才有光照亮,出了这个范围的点就没有颜色了。具体可以参考https://github.com/candycat1992/Unity_Shaders_Book/issues/47
而且光的强度信息是存在纹理的W分量中的,所以我们只取W分量来计算光强的衰减。
然后我将tex2D(_LightTexture0,lightCoord.xy/lightCoord.w+0.5).w这句改成了tex2D(_LightTexture0,lightCoord.xy/(lightCoord.w*0.5)+0.5).w。计算的结果如下
注意到这个光照范围缩小了,本来我们对_LightTexture0纹理采样时控制的角度范围在[-0.5,0.5]之间。我们将他扩大了两倍。本来可以在角度范围内的点,现在被我们排除掉了。所以光照范围变小了为[-0.25,0.25]范围。
然后我又将然后我将tex2D(_LightTexture0,lightCoord.xy/lightCoord.w+0.5).w这句改成了tex2D(_LightTexture0,lightCoord.xy/(lightCoord.w*2)+0.5).w!此时预想的应该是光照的圈应该扩大一倍才对,实际上是
并没有成为想象中扩大一倍的圆 而好像是在某一处被截取掉了,而且从不同角度去观察,效果还不一样!
想在我又有了更夸张的想法:
将tex2D(_LightTexture0,lightCoord.xy/lightCoord.w+0.5).w这句改成了tex2D(_LightTexture0,float2(0.5,0.5)+0.5).w! 这时光的照射范围就不应该受到像素位置的约束,我们压根就没有用到像素的位置!而是用定值对光照进行采样,也就是所以的像素应该是一样的值。
出现了这么一个效果。
后来索性我就直接在片元着色器中返回return fixed4(1,0,0,1);!按理说整个物体都应该呈现红色才对,事实是:
可见,在这些条件的限制下,渲染会受到灯光类型的影响。