【图形学】30 前向渲染多光照场景代码理解

来源:《UNITY SHADER入门精要》

文章目录

1、代码理解

  我们现在要注意光源的 5 个属性:位置、方向、颜色、强度、衰减
  在理解代码之前,我们依然需要熟悉我们的理论,主要我们要设置两个 Pass,注意它们的不同的特性,和要做的事情。

在这里插入图片描述

  注意,据书中所说,注意两个 Pass 中的 #pragma multi_complie_fwdbase 命令和 #pragma multi_complie_fwdadd 命令,在官方文档中没有说明,但是,实验表明,只有使用了这两个编译指令,我们才可以在相关的 Pass 访问到光照变量、、光照衰减值等等的变量。

Shader "Unity Shaders Book/Chapter 9/Forward Rendering" {
	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	

  第 17 句,我们使用了 #pragma 编译命令。#pragma multicomplie_fwdbase 确保我们在 Shader 中使用光照衰减等光照变量可以被正确赋值。这个 Pass 我们称之为 BasePass,正如我们之前概念里提到的那样。

			#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
		}

  所有的工作都在片元着色器中完成,顶点着色器只是在做了最简单的坐标转换而已。我们这里依然使用了 _LightColor0 来获取光源的强度和 _WorldSpaceLightPos0 来获取场景中的位置。平行光的强度不会衰减,所以,我们这里 atten 赋值为1。
  如果一个场景中包含了多个平行光,Unity 会选择最亮的平行光传递给 Base Pass 进行逐像素处理,其他的平行光会按照逐顶点活在 Additional Pass 中按照住像素的方式处理。

		Pass {
			// Pass for other pixel lights
			Tags { "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#pragma multi_compile_fwdadd

  我们第二个 Pass ,按照理论知识,第 3 行,我们定义为 Addtional Pass,为此,我们首先需要设置 Pass 的渲染路径标签:"LightMode" = "ForwardAdd"
  第 5 行,我们使用 Blend One One 命令来对结果进行混合,而亲测,选择更容易理解的 Blend SrcAlpha DstAlpha 也能得到正确的效果。
  第10 行,我们还要给给出宏指令 #prgma multi_complie_fwdadd 指令,这样才能保证我们在 Addtional Pass 中获得正确的光照变量。

			#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-13 行,我们进行第一个部分的处理:
  首先,我们仍然使用 _LightColor0 来得到光源的颜色和强度。我们使用 宏定义 #ifdef USING_DIRECTIONAL_LIGHT 来确定当前是否是平行光。因为,如果 Pass 处理的光源是萍乡光,那么 Unity 底层就会定义 USING_DIRECTIONAL_LIGHT。如果是平行光,那么可以直接使用 _WorldSpaceLightPos0.xyz 得到光源方向。如果是点光源或者聚光灯的话,那么 _WorldSpaceLightPos0.xyz表示的是世界空间下光源的位置。

  第二个部分,第 15-27 行,我们处理不同光源的衰减,如果是平行光的话,那 atten = 1 那就不衰减。如果是点光源或聚光灯,处理更加复杂,本来会涉及大量的开根号、除法等运算,但是为了节省效率,Unity 选择了使用一张纹理作为查找表(Lookup Table, LUT),对这个表取样,以获得光源的衰减值。


  例子中的场景有 5 个光源,其中 1 个是平行光,其他 4 个都是点光源。平行光会按照 Base Pass 逐像素的方式处理,其他四个点光源都会按照 Addtional Pass 中逐像素的方式处理,每一个光源都会调用一次 Additional Pass。
  但是如果我们手动把场景中的所有光源设置为 Not Important 那么,因为没有在 Bass Pass 中计算逐顶点 和 SH光源,因此场景中的 4 个点光源实际上不会对物体造成任何影响。

猜你喜欢

转载自blog.csdn.net/qq_40891541/article/details/127439571