1. 程式人生 > >Unity噴墨效果Shader實現

Unity噴墨效果Shader實現

筆者介紹:姜雪偉,IT公司技術合夥人,IT高階講師,CSDN社群專家,特邀編輯,暢銷書作者,已出版書籍:《手把手教你架構3D遊戲引擎》電子工業出版社和《Unity3D實戰核心技術詳解》電子工業出版社等。

對於遊戲中使用的類似噴墨效果,在射擊類遊戲中經常使用比如玩家射擊的子彈會在牆上出現類似噴墨效果,效果如下所示:


在預設情況下,螢幕的整個Alpha通道都是黑色的,直到玩家開始噴射油墨,才會使被油墨濺到區域的Alpha通道變為白色。然後影象效果就是其原有顏色與灰度進行混合。

    實現方式如下所示:


從上圖可以看出,使用Projector將噴漆繪製到物體表面並建立顏色遮罩。每個Projector都使用程式化動態生成,在子彈(空中飛行的白點)接觸到某個表面時進行初始化。Projector帶有一個盒式碰撞體,當子彈落在Projector上時,不會初始化新的Projector,而是讓原先的Projector變大。這樣漆量會變多,而場景中的Projector數量卻保持不變。

預設情況下,Unity標準著色器會為所有不透明物件的Alpha通道寫入1。所以下面使用自定義著色器來替換Unity標準著色器。新建一個標準表面著色器,將其表面函式替換為如下:

void surf(Input IN, inout SurfaceOutputStandard o)
{
     //Albedo 來自帶顏色的紋理
     fixed4 c=tex2D(_MainTex, IN.uv_MainTex) * _Color;
     o.Albedo = c.rgb;
     o.Metallic = _Metallic;
     o.Smoothness = _Glossiness;
     o.Alpha = 0;  //只新增此行
}

下面這行很重要,用於避免Unity更改自定義的Alpha值。將#pragma那行程式碼改為如下:
CGPROGRAM
#pragma surface surf Standard fullforwardshadows keepalphs
注意,該技巧不可用於Unity中的延遲渲染管線,因為它重寫了G-Buffer中的Alpha通道來儲存遮罩資料。

當子彈撞擊某個表面時就會在撞擊處動態生成Unity Projector。這些Projector帶有自定義材質與自定義著色器。材質紋理是一張帶有Alpha通道噴濺形狀圖,本文示例使用的紋理如下圖:


注意,紋理匯入設定中要將Wrap Mode設為“Clamp”而非“Repeat”。用於Projector材質的著色器從Unity提供的ProjectorLight修改而來,程式碼如下:


Shader "Projector/ProjectAlpha"
{
	Properties
	{
		_ShadowTex("Cookie", 2D) = "gray"{}
	}
	Subshader{
		Tags{"Queue" = "Transparent"}
		Pass
		{
			ZWrite Off
			Blend Zero One,One One
			Offset -1, -1
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_fog
			#include "UnityCG.cginc"

			struct Input
			{
				float4 vertex : LagPosition;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 uvShadow : TEXCOORD0;
				UNITY_FOG_COORDS(2)
				float4 pos : SV_POSITION;
				fixed nv : COLOR0;
			};

			float4x4 unity_Projector;
			float4x4 unity_ProjectorClip;

			v2f vert(Input v)
			{
				v2f o;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uvShadow = mul(unity_Projector, v.vertex);
				UNITY_TRANSFER_FOG(o, o.pos);
				float3 normView = normalize(float3(unity_Projector[2][0], unity_Projector[2][1], unity_Projector[2][2]));
				float nv = dot(v.normal, normView);
				o.nv = nv < 0 ? 1: 0;
				return o;
			}

			sampler2D _ShadowTex;
			sampler2D _FalloffTex;

			fixed4 frag(v2f i) : COLOR
			{
				fixed4 texS = tex2DProj(_ShadowTex, UNITY_PROJ_COORD(i.uvShadow));
				fixed4 res = fixed4(1,1,1,texS.a);

				res.a = i.nv;
				UNITY_APPLY_FOG_COLOR(i.fogCoord, res, fixed4(1,1,1,1));
				return res;
			}
			EDNCG
		}
	}
}


再介紹一下,關於混合的部分:

當著色器計算某個畫素的顏色時,該顏色必須作用於螢幕上該點已經存在的畫素顏色之上。預設情況下,新的畫素會完全覆蓋原有畫素,但新畫素也可以與原有畫素進行混合。混合通常用於讓物件呈透明或半透明效果,當然也可以實現很多其它的炫酷特效。

關鍵字Blend可以包含在Subshader或Pass標籤中,甚至對同一個著色器的不同Pass進行混合。新增Blend關鍵字後,必須寫入混合因子。混合因子如下:

 

Src指向著色器用於計算的顏色。Dst指向螢幕上已有的畫素顏色。著色器用於計算的顏色會與第一個因子相乘,而螢幕上已有顏色會與第二個因子相乘。將兩個結果相加,就是最終寫到螢幕的顏色。

所以"Blend SrcAlpha One"會將自身Alpha值與當前著色器計算的顏色相乘,此時螢幕上的顏色暫未改動。然後再將螢幕顏色計算後的結果與前者相加。還可以使用逗號分隔兩組因子,逗號前的混合選項用於計算顏色,逗號後的混合選項僅計算Alpha通道。可以查閱Unity文件瞭解更多關於混合的內容。

用於Projector的著色器就是“Blend Zero One, One One”,“Zero One”移除了飛濺紋理的顏色,使用子彈所飛濺到的表面顏色。“One One”將飛濺物的Alpha值與表面Alpha值相加。

現在使用上面的著色器與材質來生成Projector,應該將場景檢視的Alpha通道設為白色。

現在可以隨意修改Alpha通道,但還未達到最終效果。下面利用Alpha遮罩來建立遊戲所需的影象特效。

首先,建立要使用影象特效的著色器。在Unity中新建預設的Image Effect Shader,然後將片段程式碼替換為如下:

fixed4 frag(v2f i) : SV_Target
{
   fixed4 col = tex2D(_MainTex, i.uv);
   fixed3 bnw = dot(col.rgb, float3(0.3,0.59,0.11));
   col.rgb = lerp(bnw, col.rgb, col.a);
   
   return col;
}

可以隨意更改bnw(Black&White)變數以達到理想的混合效果。最後還需要新建指令碼來執行該影象特效。指令碼非常簡單,程式碼如下:
using System.Collections;
using System.Collections.Generic;
using UnityStandardAssets.ImageEffectBase;

[ExecuteInEditMode]
[ImageEffectAllowedInSceneView]
public class ALphaColorSwitch : ImageEffectBase{
	void OnRenderImage(RenderTexture source, RenderTexture destination)
	{
		Graphics.Blit (source, destination, material);
	}
}


注意,這裡用到了ImageEffectBase,該資源在Unity標準資源庫中(Unity 5.5及以上版本推薦使用Post Processing Stack資源庫代替ImageEffects)。匯入標準資源庫後,將指令碼繫結到相機(確保將相機的渲染模式設為Forward)上,並將公共的著色器變數設為前面提到的著色器。

到此就可以向場景中噴射油墨啦!