Shader笔记九 渲染路径&复杂光照

渲染路径&复杂光照
渲染路径(Rendering Path)决定光照如何应用到UnityShader中。为了获取场景中的光源数据,需要为每个Pass指定其渲染路径。5.0以后的版本中,Unity支持前向渲染(Forward Renddering Path)和 延迟渲染(Deferred Rendering Path)。系统默认情况下选择的是前向渲染路径,可以更改,同时若想使用多个渲染路径,可以在不同的摄像机中调节 Rendering Path选项。
通过在每个Pass中使用标签来指定Pass使用的渲染路径,即使用 LightMode标签实现。不同类型的渲染路径可能包含多种标签设置。

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

前向渲染路径除了有ForwardBase,还有 ForwardAdd。Pass中的 LightMode支持的渲染路径设置选项:
Shader笔记九 渲染路径&复杂光照_第1张图片
当我们为一个Pass设置了渲染路径的标签,就可以通过Unity提供的内置光照变量来访问这些属性。

前向渲染路径
每进行一次完整的前向渲染,都需要渲染该对象的渲染图元,并计算颜色和深度缓冲区的值。**对于每一个逐像素光源,都需要进行一次该过程。**也就是说,如果一个物体在多个逐像素光源的影响区域内,那么就需要执行对应数量的Pass,每个Pass对应一个逐像素的光源,然后在帧缓冲内将这些 光照结果混合起来得到最终的颜色值。

Unity中的前向渲染
Unity中,前向渲染路径有3种处理光照的方式:逐顶点处理,逐像素处理,球谐函数 光源类型和渲染重要度决定了使用哪一种处理方式。
光源类型指该光源是平行光还是其他类型的光源。
渲染重要度是指该光源是否为重要的(IMPORTANT),如果将一个光源设置为重要的,那么Unity将其作为逐像素光源来处理。
在前向渲染中,Unity根据场景中的光源对物体的影响程度对光源进行重要度排序。
一定数目光源按照 逐像素处理
最多 4个光源按照 逐顶点方式处理
剩下的光源按照 球谐函数方式处理
Unity使用的判定规则:

  • 最亮的平行光总是按照逐像素处理
  • 渲染模式设置为 Not Important的光源,按照逐顶点或球谐函数的方式处理
  • 渲染模式被设置成Important的光源,按逐像素处理
  • 根据以上规则得到的逐像素光源数量小于QualitySetting中的逐像素光源数量,将有更多的光源以逐像素方式进行渲染

光照计算在Pass中,前向渲染有两种:ForwardBase和 ForwardAdd,使用如下:
Shader笔记九 渲染路径&复杂光照_第2张图片

  • 在对应的Pass中使用 #pragma multi-compile-fwdbase和 #pragma multi-compile-fwdadd编译命令才能正确的访问光照衰减等光照变量。
  • Base Pass中渲染的平行光默认支持阴影,Additional Pass中渲染的光源默认情况没有阴影效果,需要使用 #pragma multi-compile-fwdadd-fullshadows代替 #pragma multi-compile-fwdadd编译指令。
  • 环境光和自发光只需计算一次,因此放到BasePass中
  • Additional Pass的渲染设置中,开启和设置设置混合模式,因为希望每个Additional Pass可以与上一次的光照结果在帧缓存中进行叠加。如果没有设置,那么就会将上一次的计算结果进行覆盖。通常设置的混合模式为Blend One One
  • 针对前向渲染,一个Shader通常会定义一个Base Pass(Base Pass也可以定义多个,像之前的双面渲染)和一个Additional Pass。一个Base Pass仅执行一次,而Additional Pass会根据影响该物体的其他逐像素光源的数目被多次调用。

内置的光照变量&函数
前向渲染中可以使用的内置光照变量
Shader笔记九 渲染路径&复杂光照_第3张图片
前向渲染中可以使用的内置光照函数
Shader笔记九 渲染路径&复杂光照_第4张图片

延迟渲染路径
Pass中的渲染路径的设置是根据场景中的光源数据来进行选择。当场景中包含大量实时光源时,前向渲染的性能会下降,因为需要多次Pass来计算不同光源对该物体的光照结果,在颜色缓冲区将结果混合起来得到最终光照效果。实际上每执行一个Pass都需要重新再渲染一遍物体,很多计算实际上是重复的。延迟渲染除了使用颜色和深度缓冲区,还会使用额外的缓冲区,称为G-缓冲,存储表面(通常离摄像机最近的表面)的法线,位置,用于光照计算的材质属性等。
延迟渲染原理
延迟渲染主要包含两个Pass。第一个Pass中不做任何光照计算,只计算片元的可见性,通过深度缓冲来实现,当片元可见,将该片元相关信息存储到G缓冲中。第二个Pass中,利用G缓冲的各个片元信息,进行光照计算。延迟渲染使用的数目通常就2个,和场景中的光源数量并没有关系。
Unity中的延迟渲染
延迟渲染路径适合场景中光源数量较多、使用前向渲染路径会造成性能瓶颈的情况下使用,延迟渲染中的每个光源都可以按逐像素的方式进行处理。不过,延迟渲染也存在缺点

  • 不支持真正的抗锯齿(anti-aliasing)功能
  • 不能处理半透明物体(延迟渲染需要深度写入)
  • 对显卡有一定要求

Unity中使用延迟渲染路径,需要提供两个Pass。
第一个Pass用于渲染G缓冲,在该Pass中将物体漫反射、高光反射、颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区,因此,延迟渲染路径的效率不依赖与场景是否复杂,而是屏幕的分辨率高低。对于每个物体来说,这个Pass仅会执行一次
第二个Pass用于计算真正的光照模型。这个Pass使用上一个Pass中渲染的数据来计算最终的光照颜色,再存储到帧缓冲中。默认的G缓冲区包括以下渲染纹理(Render Texture):

  • RT0:ARGB,RGB通道存储漫反射颜色,A通道未使用
  • RT1:ARGB,RGB通道存储高光反射颜色,A通道存储高光反射的指数部分(Gloss)
  • RT2:ARGB,RGB通道存储法线,A通道未使用
  • RT3:ARGB,存储自发光+lightmap+反射探针
  • 深度缓冲和模板缓冲

延迟渲染路径中可以使用的内置变量

Unity光源类型
Unity支持4种光源类型:平行光、点光源、聚光灯和面光源。面光源只有在烘焙后才能起作用,不属于实时光源。Shader中常用的光源属性包括:光源的位置、方向、颜色、强度和衰减。

  • 平行光
    平行光对照亮的范围没有限制,平行光的几何属性只有方向。平行光没有衰减的概念。
  • 点光源
    点光源的照亮空间是有限的,由空间中的一个球体定义。点光源可以表示由一个点发出、向所有方向延伸的光。点光源具有位置、方向、颜色、强度和衰减的属性。
  • 聚光灯
    聚光灯的照亮空间是有限的,由空间中的一个锥形区域定义。聚光灯同样具有位置、方向、颜色、强度和衰减的属性。聚光灯的衰减比点光源的衰减计算更为复杂,因为要判断是否在一个锥形区域内。

前向渲染中处理不同的光源类型
使用一个平行光和一个点光源共同照亮物体
实例代码:

Shader "Custom/Chapter9_ForwardRendering" {
Properties{
	_Color("Color",Color)=(1,1,1,1)
	_SpecularColor("SpecularColor",Color)=(1,1,1,1)
	_Gloss("Gloss",Range(8.0,256))=20
}
SubShader{
	Pass{
		Tags{"LightMode"="ForwardBase"}
		CGPROGRAM
			#pragma multi_compile_fwdbase
			//#pragma multi_compile_fwdbase指令保证Shader中使用光照衰减变量可以被正确赋值
			#pragma vertex vert
			#pragma fragment frag 
			
			#include "Lighting.cginc"
			
			fixed4 _Color;
			fixed4 _SpecularColor;
			float   _Gloss;

			struct a2v{
				float4 vertex:POSITION;
				float3 normal:NORMAL;
			};
			struct v2f{
				float4 pos:SV_POSITION;
				float3 worldPos:TEXCOORD0;
				float3 worldNormal:TEXCOORD1;
			};

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

				return o;
			}

			fixed4 frag(v2f i):SV_Target{
				fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed3 worldNormal=normalize(i.worldNormal);
				fixed3 worldViewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));

				fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
				fixed3 diffuse=_LightColor0.rgb*_Color.rgb*max(0,dot(worldNormal,worldLightDir));
				fixed3 halfDir=normalize(worldViewDir+worldLightDir);
				fixed3 specularColor=_LightColor0.rgb*_SpecularColor.rgb*pow(saturate(dot(worldNormal,halfDir)),_Gloss);
				
				fixed atten=1.0;
				//Unity选择最亮的平行光在BasePass中处理,平行光的衰减值设为1,认为没有衰减
				return fixed4(ambient+(diffuse+specularColor)*atten,1.0);
			}
		ENDCG
	}

	Pass{
		Tags{"LightMode"="ForwardAdd"}
		Blend One One
		CGPROGRAM
			#pragma multi_compile_fwdadd
			//#pragma multi_compile_fwdadd指令保证Shader中访问正确的光照变量
			#pragma vertex vert
			#pragma fragment frag 
			
			#include "Lighting.cginc"
			//需要引入改文件,才能正确的访问到_LightMatrix0光照矩阵,对坐标进行转换
			//以便对光照衰减纹理进行采样
			#include "AutoLight.cginc"
			
			fixed4 _Color;
			fixed4 _SpecularColor;
			float   _Gloss;

			struct a2v{
				float4 vertex:POSITION;
				float3 normal:NORMAL;
			};
			struct v2f{
				float4 pos:SV_POSITION;
				float3 worldPos:TEXCOORD0;
				float3 worldNormal:TEXCOORD1;
			};

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

				return o;
			}

			fixed4 frag(v2f i):SV_Target{
				//Additional中去掉BasePass中的环境光、自发光、逐顶点光照和SH光照部分
				//AddPass中处理的光源可能是非最亮平行光、点光源、聚光灯,因此计算光源的
				//位置、方向、颜色、强度和衰减时,需要根据光源类型分别进行计算
				#ifdef USING_DIRECTIONAL_LIGHT
					fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
				#else
					fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz-i.worldPos.xyz);
				#endif
				//上述过程先判断处理的光源是否为平行光,如果是平行光,渲染引擎会定义USING_DIRECTIONAL_LIGHT
				//若没有定义则说明不是平行光,光源位置通过运算得到
				fixed3 worldNormal=normalize(i.worldNormal);
				fixed3 worldViewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));
				
				fixed3 diffuse=_LightColor0.rgb*_Color.rgb*max(0,dot(worldNormal,worldLightDir));
				fixed3 halfDir=normalize(worldViewDir+worldLightDir);
				fixed3 specularColor=_LightColor0.rgb*_SpecularColor.rgb*pow(saturate(dot(worldNormal,halfDir)),_Gloss);
				
				//处理衰减过程
				//针对其他光源类型,Unity使用一张纹理作为查找表,来得到片元着色器中的光照衰减值
				//先得到光源空间下的坐标,使用该坐标进行采样得到衰减值
				#ifdef USING_DIRECTIONAL_LIGHT
					fixed  atten=1.0;
				#else 
					float3 lightCoord=mul(_LightMatrix0,float4(i.worldPos,1)).xyz;
					fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				#endif		
				return fixed4((diffuse+specularColor)*atten,1.0);
			}
		ENDCG
	}
}
FallBack "Specular"
}  

实例效果:
Shader笔记九 渲染路径&复杂光照_第5张图片
左侧没有AddPass的BlinnPong光照效果,右侧为增加上述AddPass的BlinnPhong光照效果,可以看出右侧可以接收场景中的点光源的影响。

你可能感兴趣的:(UnityShader)