Unity Shader笔记之 Unity的光源类型

Unity一共支持4种光源类型:平行光、点光源、聚光灯和面光源 (area light) 。

面光源仅在烘焙时才可发挥作用。

光源类型有什么影响

最常使用的光源属性有光源的位置 、方向 (更具体说就是,到某点的方向)、颜色 、强度以及衰减 强度(更具体说就是,到某点的衰减,与该点到光源的距离有关)这5个属性。而这些属性和它们的几何定义息息相关。

1.平行光

平行光可以照亮的范围是没有限制的,它通常是作为太阳这样的角色在场景中出现的。

平行光之所以简单,是因为它没有一个唯一的位置,它可以放在场景中的任意位置。

它的几何属性只有方向,我们可以调整平行光的Transform组件中的Rotation属性来改变它的光源方向, 而且平行光到场景中所有点的方向都是一样的,这也是平行光名字的由来。除此之外,由于平行光没有一个具体的位置,因此也没有衰减的概念,也就是说,光照强度不会随着距离而发生改变。

2.点光源

点光源的照亮空间则是有限的,它是由空间中的一个球体定义的。点光源可以表示由一个点发出的、向所有方向延伸的光。

球体的半径可以由面板中的Range属性来调整,也可以在Scene视图中直接拖拉点光源的线框(如球体上的黄色控制点)来修改它的属性。

点光源是有位置属性的,它是由点光源的Transform组件中的Position属性定义的。

对于方向属性,我们需要用点光源的位置减去某点的位置来得到它到该点的方向。

而点光源的颜色和强度可以在Light组件面板中调整。同时,点光源也是会衰减的,随着物体逐渐远离点光源,它接收到的光照强度也会逐渐减小。点光源球心处的光照强度最强,球体边界处的最弱,值为0。其中间的衰减值可以由一个函数定义。

我们需要在Scene视图中开启光照才能看到预览光源是如何影响场景中的物体的。图9.7给出了开启Scene 视图光照的按钮。

3.聚光灯

聚光灯是这3种光源类型中最复杂的一种。它的照亮空间同样是有限的,但不再是简单的球体,而是由空间中的一块锥形区域定义的。聚光灯可以用于表示由一个特定位置出发、向特定方向延伸的光。

这块锥形区域的半径由面板中的Range属性决定,而锥体的张开角度由Spot Angle属性决定。我们同样也可以在Scene视图中直接拖拉聚光灯的线框(如中间的黄色控制点以及四周的黄色控制点)来修改它的属性。聚光灯的位置同样是由Transform组件中的Position属性定义的。对于方向属性,我们需要用聚光灯的位置减去某点的位置来得到它到该点的方向。聚光灯的衰减也是随着物体逐渐远离点光源而逐渐减小,在锥形的顶点处光照强度最强,在锥形的边界处强度为0。其中间的衰减值可以由一个函数定义,这个函数相对于点光源衰减计算公式要更加复杂,因为我们需要判断一个点是否在锥体的范围内。

在前向渲染中处理不同的光源类型

如何在Unity Shader中访问它们的5个属性:位置 位置 、方向 方向 、颜色 颜色 、强度以及衰减。

我们来实现一个前向渲染的 shader。

首先做如下准备工作。

(1)在Unity中新建一个场景。该场景名为Scene_9_Light,默认情况下场景将包含一个摄像机和 一个平行光,并且使用了内置的天空盒子。在Window → Lighting → Skybox中去掉场景中的天空盒子。

(2)新建一个材质。材质名为ForwardRenderingMat。

(3)新建一个Unity Shader。Shader名为Chapter9-ForwardRendering。把新的Unity Shader赋给第2步中创建的材质。

(4)在场景中创建一个胶囊体,并把第2步中的材质赋给该胶囊体。

(5)为了让物体受多个光源的影响,我们再新建一个点光源,把其颜色设为红色,以和平行光进行区分。

前向渲染说明 
先说明一下前向渲染,unity支持多种渲染路径,前向渲染是其中一种

Tags { "LightMode"="ForwardBase" }

一般使用以上规定前向渲染。 
对前向渲染而言,一个shader通常会规定2个pass,一个是Bass Pass,仅执行一次,一个是Additional Pass,为每个光源会执行一次。

代码实例 

定义 Base Pass

I. 定义 Properties 块,因为只计算光照,所以比较简单

Properties
	{
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}

II. 先定义第一个 Pass : Base Pass

Pass
		{
		 Tags { "LightMode"="ForwardBase" }
		 
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_fwdbase			
			#include "Lighting.cginc"

把 LightMode 定义为 ForwardBase,并添加编译指令 #pragma multi_compile_fwdbase

III. 输入输出结构体

struct appdata
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
				float3 worldNormal:TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};

IV. 顶点着色器,进行简单的空间转换 

v2f vert (appdata v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				return o;
			}

V. 片元着色器,对最亮的平行光,环境光,自发光等做计算。但此处我们不考虑自发光

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);
				
			}

 在这里 atten 表示光照衰减值,因为是平行光,所以固定为 1

 至此 Base Pass 块基本定义完成。下面我们来定义 Additional Pass

定义 Additional Pass

I. 定义标签,同时注意一定要开启混合

Pass{
			Tags { "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			#pragma multi_compile_fwdadd
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"

II. Additional Pass 中对光源的处理方式与 Base Pass 中基本一致,去掉了环境光,自发光,逐顶点光照,SH 光照部分,然后添加对不同光源类型的支持。因为在这里处理的光源可能是平行光,点光源和聚光灯中任意一种。所以我们需要对其进行判断,然后计算五个影响要素:位置,方向,颜色,强度和衰减
 

III. 对光源进行判断,得到光源方向,记得归一化

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

IV. 常规的光照计算

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);

 V. 最后,处理不同光源的衰减

#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);

可以看到的是,点光源和聚光灯求衰减比较复杂,要通过复杂的数学表达式计算得到。因为这种计算操作较大,所以 Unity 选择使用一张纹理作为查找表(LUT),我们首先得到光源空间下的坐标,然后用这个坐标对衰减纹理进行采样得到衰减值。

最后,保存两部分的代码,查看效果,可以看到,平行光和点光源都照亮了物体

完整代码整理如下:

Shader "Unlit/ForwardRenderingMat"
{
	Properties
	{
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader
	{
		

		Pass
		{
		 Tags { "LightMode"="ForwardBase" }
		 
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_fwdbase			
			#include "Lighting.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;

			struct appdata
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
				float3 worldNormal:TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};


			
			v2f vert (appdata 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{
			Tags { "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			#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"
}