Unity3D學習(八):《Unity Shader入門精要》——透明效果

前言
在實時渲染中要實現透明效果,一般會在渲染模型時控制它的透明通道。
Unity中一般使用兩種方法來實現透明 :(1)透明度測試(AlphaTest)(2)透明度混合(AlphaBlend)。前者每每沒法實現真正的半透明效果。

深度緩衝(Depth Buffer)html

深度緩衝是用於解決可見性問題的,它能夠決定物體的哪些部分渲染在前面,哪些部分被其餘物體遮擋。其基本思想是:根據深度緩存中的值來判斷該片元距離攝像機的距離,當渲染一個片元時,須要把它的深度值和已經存在深度緩存中的值進行比較(前提是開啓了深度測試),若是它的值距離攝像機更遠,那麼說明它不該該被渲染到屏幕上(被擋住了);不然,這個片元應該覆蓋掉此時顏色緩衝中的像素值,並把它的深度更新到深度緩衝中(前提是開啓了深度寫入,Unity中爲ZWrite On)。

透明度混合時應關閉深度寫入(ZWrite Off)緩存

若是不關閉深度寫入,一個半透明表面背後的表面本就是透過它被咱們看到的,但因爲深度測試時判斷結果是該半透明表面)距離攝像機更近,致使後面的表面會被剔除掉,也就沒法經過半透明面觀察到後面的物體。app

另外注意關閉深度寫入後要考慮物體的渲染順序。以下圖,一個半透明物體A和一個不透明物體B,B在A後方,若是先渲染A再渲染B會出現A被B遮擋的狀況,這是錯誤的。所以渲染順序在關閉深度寫入的狀況下極爲重要。函數

 

 爲了保證渲染順序正確,渲染引擎通常會對物體進行排序,再渲染,經常使用的方法是測試

  • 1)先渲染全部不透明物體,並開啓它們的深度測試和深度寫入。
  • 2)把半透明物體按他們離攝像機的遠近進行排序,而後按照從後往前的順序渲染透明物體,並開啓它們的深度測試,但關閉深度寫入。

但這種方法沒法解決物體重疊的狀況,所以須要額外的解決方案,好比分割網格等。可是也能夠試着讓透明通道更柔和,是穿插重疊看起來不那麼明顯。spa

Unity的渲染順序3d

Unity經過一組Queue標籤來決定模型歸於哪一個渲染隊列,隊列由整數索引表示,索引號越小越先被渲染。orm

渲染隊列 渲染隊列描述 渲染隊列值
Background 這個隊列被最早渲染。它被用於skyboxes等。 1000
Geometry 這是默認的渲染隊列。它被用於絕大多數對象。不透明幾何體使用該隊列。 2000
AlphaTest 通道檢查的幾何體使用該隊列。它和Geometry隊列不一樣,對於在全部立體物體繪製後渲染的通道檢查的對象,它更有效。 2450
Transparent 該渲染隊列在Geometry和AlphaTest隊列後被渲染。任何通道混合的(也就是說,那些不寫入深度緩存的Shaders)對象使用該隊列,例如玻璃和粒子效果。 3000
Overlay 該渲染隊列是爲疊加效果服務的。任何最後被渲染的對象使用該隊列,例如鏡頭光暈。 4000

透明度測試htm

只要有一個片元的透明度不知足條件(一般是小於某個閾值),那麼它對應的片元便會被捨棄,不作任何處理。對象

在Unity中是用以下函數來進行透明度測試:

clip(texColor.a - _Cutoff);

texColor.a爲紋理的alpha值,_Cutoff爲閾值,該函數等價於

if(texColor.a - _Cutoff < 0)
    discard;

完整的代碼 

Shader "Unity Shader Book/Chapter 8/AlphaTest"
{
	Properties
	{
	    _Color("Color Tint",Color) = (1,1,1,1)
		_MainTex ("Texture", 2D) = "white" {}
		_Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
	}
	SubShader
	{
		Tags { "RenderType"="TransparentCutout" "Queue"="AlphaTest" "IgnoreProjector"="True" "LightMode"="ForwardBase"}
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			struct appdata
			{
				float4 vertex : POSITION;
				float3 normal:NORMAL;
				float4 texcoord : TEXCOORD0;
			};

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

			fixed4 _Color;
			fixed _Cutoff;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				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 = UnityWorldSpaceLightDir(i.worldPos);

				fixed4 texColor = tex2D(_MainTex,i.uv);

				//Alpha Test
				clip(texColor.a - _Cutoff);
				//equal to 
				//if(texColor.a - _Cutoff < 0) discard;
				fixed3 albedo = texColor.rgb * _Color.rgb;
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
				return fixed4(diffuse + ambient,1.0);
			}
			ENDCG
		}
	}
}

效果以下,能夠看到只是單純的顏色剔除,並不算正常的透明效果。

爲了獲得正確的透明效果,能夠使用透明度混合。

透明度混合

透明度混合即將透明物體的源顏色與其表面後方的目標顏色混合,其基本公式爲:

DstColor-new(混合後的顏色) = SrcAlpha * SrcColor + (1- SrcAlpha) * DstColor。(SrcAlpha爲混合因子)

該公式能夠在Unity的ShaderLab語義中表示爲

Blend SrcAlpha OneMinusSrcAlpha

更多語義能夠參考官方文檔

完整代碼實現

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Unity/Chapter 8/AlphaBlend"
{
	Properties
	{
	    _Color("Color Tint",Color) = (1,1,1,1)
		_MainTex ("Texture", 2D) = "white" {}
		_AlphaScale("Alpha Scale",Range(0,1)) = 0.5
	}
	SubShader
	{
		Tags { "RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent" }
		LOD 100
		Blend SrcAlpha OneMinusSrcAlpha
		ZWrite On
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			struct appdata
			{
			    float3 normal:NORMAL;
				float4 vertex : POSITION;
				float4 texcoord : TEXCOORD0;
			};

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

			fixed4 _Color;
			fixed _AlphaScale;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
			    fixed3 worldNormal = normalize(i.worldNormal);
			    fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed4 texColor = tex2D(_MainTex,i.uv);
				fixed3 albedo = texColor.rgb * _Color.rgb;
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
				return fixed4(ambient + diffuse,texColor.a * _AlphaScale);
			}
			ENDCG
		}
	}
	Fallback "Transparent/VertexLit"
}

該代碼獲得的效果以下

嗯,這纔是正常的透明效果

開啓深度寫入的半透明效果

上述的透明混合並無開啓深度寫入(ZWrite On),所以若是物體有重疊,那麼會產生錯誤的效果,以下

當模型網格之間有互相交叉的結構時,每每會獲得錯誤的半透明效果

這時候須要在上文的透明度混合代碼中的Pass代碼塊以前,插入一個新的Pass代碼塊

Pass
{
    ZWrite On
    ColorMask 0  //用於設置顏色通道的寫掩碼,0表示不寫入任何通道
}

這樣就會獲得正確的效果

 開啓了深度寫入的半透明效果

雙面渲染的透明效果

仔細觀察上文的透明效果能夠發現,咱們並不能透過半透明物體觀察它們的內部狀況,這是不合乎常理的,所以咱們須要進行雙面渲染。

在Unity中,咱們能夠經過Cull指令來控制剔除哪一面的渲染圖元

Cull Back  //剔除背面,只渲染前面,引擎的默認設置
Cull Front //剔除前面,僅渲染背面
Cull Off    //關閉剔除

透明度測試的雙面渲染

僅須要在Tags標籤後插入一行

Cull Off

效果對比

透明度混合的雙面渲染

將Pass代碼塊複製一份,一個Pass負責渲染前面,一個Pass負責渲染後面就行,格式以下

Shader "........."{
propeties
{
     /*.....*/
}
SubShader{
     Pass
   {
         Tags{.......}
         Cull Front  //剔除前面
         /*其他代碼保持不變
          ....
         */
    }

     Pass
    {
         Tags{.......}
         Cull Back  //剔除後面
         /*其他代碼保持不變
           ....
         */
     }

   }  
}

實現的效果以下

相關文章
相關標籤/搜索