Unity Shader 学习笔记(13) 混合光源、光的衰减

Unity Shader 学习笔记(13) 混合光源、光的衰减

参考书籍:《Unity Shader 入门精要》
3D数学 学习笔记(8) 光照
【常见问题】dot(lightCoord, lightCoord).rr和光源衰减的相关问题git


混合光源的前向渲染

一个BasePass处理平行光、四个点光源调用四次Additional Pass,四个点光源的顺序是依靠重要度排序的(光源强度、颜色、距离远近)。注意:只处理了逐像素光照,即光源渲染模式若是为Not Imoportant,则不会对物体产生影响。
在这里插入图片描述
在这里插入图片描述

github

使用Phong光照模型。定义了Base Pass 和 Additional Pass来处理多个光源。web

Base Pass 代码以下。Unity会把最亮的平行光给BasePass处理,其余平行光交给AdditionalPass处理。缓存

Pass {
	Tags { "LightMode"="ForwardBase" }

	CGPROGRAM
	
	#pragma multi_compile_fwdbase		// 保证使用光照衰减等光照变量能够正确被赋值

	...

	fixed4 frag(v2f i) : SV_Target {
		...
		
		// 环境光,只在BasePass处理一次够了。
		fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;	
		
		// 漫反射,_LightColor0是平行光颜色和强度相乘后的结果。
	 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

		...
	 	// 镜面高光。
	 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

		// 衰减值,平行光为1.0。
		fixed atten = 1.0;
		
		return fixed4(ambient + (diffuse + specular) * atten, 1.0);
	}
	...
}

Additional Pass代码以下。处理其余光源。svg

Pass {
	Tags { "LightMode"="ForwardAdd" }

	// 开启光照混合,与在帧缓存中的光照叠加
	Blend One One					

	CGPROGRAM

	// 获取正确光照变量
	#pragma multi_compile_fwdadd	
	
	...
	
	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
		
		...
		// 漫反射和高光反射
		
		// 计算衰减值。平行光为1。其余光照较复杂。
		#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
}

光照衰减

UNITY_ATTEN_CHANNEL:能够获得衰减纹理中衰减值所在份量。学习

光照衰减纹理

须要预处理获得采样纹理,纹理的大小会影响衰减精度。相似渐变纹理,(0, 0)表示与光源重合时的衰减值,(1, 1)为离光源空间最大距离的衰减值。
  Unity内部使用_LightTexture0的纹理来计算光源衰减。利用_LightMatrix0变换矩阵来计算点在光源空间的位置。.net

// 计算衰减纹理坐标值
float3 lightCoord = mul(_LightMatrix0, float4(i.worldPosition, 1)).xyz;

// 根据坐标对纹理采样,用dot点乘就是直接作距离的平方,`.rr`的意思就是取获得rgb值的r做为一个新的二维坐标即(r, r)。
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

数学公式计算衰减

使用的是线性衰减:code

flaot distance = length(_WorldSpaceLightPos0.xyz) - i.worldPosition.xyz);
atten = 1.0 / distance;