Shader Unity - Outline shader

Chào mọi người. Hôm nay mình xin trờ lại với bài Shader Unity - Outline shader.

Như các bạn đã biết, hiện ứng Outline là 1 trong những hiệu ứng được sử dụng phổ biến trong game. Mình sẽ giúp các bạn làm hiệu ứng này với Shader. Oke chúng ta bắt đầu nào.

I, Ý tưởng Như bình thường muốn tạo Outline cho 1 tấm hình. Ta cần 1 tấm hình A chứa ảnh và 1 tấm hình B chứa background cho line có tỷ lệ scale lớn hơn tấm hình rồi cho tấm hình A render đè lên tấm hình B.

Với tư tưởng ở trên ta sẽ áp dụng vào Shader như thế nào? May mắn là ở shader đã hỗ trợ cho vẽ 2 tấm hình A, B trong 1 shader và việc còn lại của chúng ta scale tấm B rồi set up tấm hình A render đè lên tấm hình B thui.

II, Thực hiện

Đầu tiên, ta có 1 shader đơn giản như hình

Shader code:

Shader "Custom/OutlineShader" {
	Properties {
		_Color("Main Color", Color) = (1,1,1,1)
		_MainTex("Main Texture",2D) = "white"{}
	}

	SubShader {
		Pass {
			CGPROGRAM
			 #pragma vertex vert
			 #pragma fragment frag

			 uniform sampler2D _MainTex;
			 uniform half4 _Color;

			 struct vertexInput {
			 	float4 position: POSITION;
			 	float4 texcoord: TEXCOORD0;
			 };

			 struct vertexOutput {
			 	float4 position: SV_POSITION;
			 	float4 texcoord: TEXCOORD0;
			 };

			 vertexOutput vert(vertexInput v) 
			 {
					vertexOutput o;
					o.position = UnityObjectToClipPos(v.position);
					o.texcoord = v.texcoord;
					return o;
			 }

			 half4 frag(vertexOutput i): COLOR 
			 {
			 	return tex2D(_MainTex,i.texcoord.xy) * _Color;
			 }
			ENDCG
		}
	}
}

Bây giờ, ta đã có tấm hình A được vẽ bởi cube và tiếp đến là vẽ tấm hình B. Thì shader đã hỗ trợ việc này bằng cách thêm Pass (Tham khảo thêm: https://docs.unity3d.com/Manual/SL-Pass.html)

Shader code:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/OutlineShader" {
	Properties {
		_Color("Main Color", Color) = (1,1,1,1)
		_MainTex("Main Texture",2D) = "white"{}
		_OutlineWidth("Outline Width", float) = 0.1
		_OutlineColor("Outline Color",Color) = (1,1,1,1)
	}

	SubShader {
    // hình A
		Pass {
			CGPROGRAM
			 #pragma vertex vert
			 #pragma fragment frag

			 uniform sampler2D _MainTex;
			 uniform half4 _Color;

			 struct vertexInput {
			 	float4 position: POSITION;
			 	float4 texcoord: TEXCOORD0;
			 };

			 struct vertexOutput {
			 	float4 position: SV_POSITION;
			 	float4 texcoord: TEXCOORD0;
			 };

			 vertexOutput vert(vertexInput v) 
			 {
					vertexOutput o;
					o.position = UnityObjectToClipPos(v.position);
					o.texcoord = v.texcoord;
					return o;
			 }

			 half4 frag(vertexOutput i): COLOR 
			 {
			 	return tex2D(_MainTex,i.texcoord.xy) * _Color;
			 }

			ENDCG
		}

    // hình B
		Pass {
	
			CGPROGRAM
			 #pragma vertex vert
			 #pragma fragment frag

			 uniform half4 _OutlineColor;
			 uniform float _OutlineWidth;

			 struct vertexInput {
			 	float4 position: POSITION;
			 	float4 texcoord: TEXCOORD0;
			 };

			 struct vertexOutput {
			 	float4 position: SV_POSITION;
			 	float4 texcoord: TEXCOORD0;
			 };
             
			 vertexOutput vert(vertexInput v) 
			 {
					vertexOutput o;
					o.position = UnityObjectToClipPos(v.position);
					return o;
			 }

			 half4 frag(vertexOutput i): COLOR 
			 {
			 	return _OutlineColor;
			 }

			ENDCG
		}
	}
}

Và ta được kết quả như hình trên. Ta thấy hình cube đã được vẽ lên màn hình được tô lên toàn màu trắng nghĩa là hình A đã bị hình B đè lên. Bước cuối cùng chúng ta cần thực hiện là ẩn hình B sau hình A và scale B lớn lên.

Bây giờ update code. Full code:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/OutlineShader" {
	Properties {
		_Color("Main Color", Color) = (1,1,1,1)
		_MainTex("Main Texture",2D) = "white"{}
		_OutlineWidth("Outline Width", float) = 0.1
		_OutlineColor("Outline Color",Color) = (1,1,1,1)
	}

	SubShader {

		Pass {

			CGPROGRAM
			 #pragma vertex vert
			 #pragma fragment frag

			 uniform sampler2D _MainTex;
			 uniform half4 _Color;

			 struct vertexInput {
			 	float4 position: POSITION;
			 	float4 texcoord: TEXCOORD0;
			 };

			 struct vertexOutput {
			 	float4 position: SV_POSITION;
			 	float4 texcoord: TEXCOORD0;
			 };

			 vertexOutput vert(vertexInput v) 
			 {
					vertexOutput o;
					o.position = UnityObjectToClipPos(v.position);
					o.texcoord = v.texcoord;
					return o;
			 }

			 half4 frag(vertexOutput i): COLOR 
			 {
			 	return tex2D(_MainTex,i.texcoord.xy) * _Color;
			 }

			ENDCG
		}

		Pass {
			Cull Front
			CGPROGRAM
			 #pragma vertex vert
			 #pragma fragment frag

			 uniform half4 _OutlineColor;
			 uniform float _OutlineWidth;

			 struct vertexInput {
			 	float4 position: POSITION;
			 	float4 texcoord: TEXCOORD0;
			 };

			 struct vertexOutput {
			 	float4 position: SV_POSITION;
			 	float4 texcoord: TEXCOORD0;
			 };

			 float4 Outline(float4 vertPos, float width)
			{
				float4x4 scaleMat;
				scaleMat[0][0] = 1.0 + width;
				scaleMat[0][1] = 0.0;
				scaleMat[0][2] = 0.0;
				scaleMat[0][3] = 0.0;
				scaleMat[1][0] = 0.0;
				scaleMat[1][1] = 1.0 + width;
				scaleMat[1][2] = 0.0;
				scaleMat[1][3] = 0.0;
				scaleMat[2][0] = 0.0;
				scaleMat[2][1] = 0.0;
				scaleMat[2][2] = 1.0 + width;
				scaleMat[2][3] = 0.0;
				scaleMat[3][0] = 0.0;
				scaleMat[3][1] = 0.0;
				scaleMat[3][2] = 0.0;
				scaleMat[3][3] = 1.0;	

				return mul(scaleMat, vertPos);
			}

			 vertexOutput vert(vertexInput v) 
			 {
					vertexOutput o;
					o.position = UnityObjectToClipPos(Outline(v.position,_OutlineWidth));
					return o;
			 }

			 half4 frag(vertexOutput i): COLOR 
			 {
			 	return _OutlineColor;
			 }

			ENDCG
		}
	}
}

Hàm float4 Outline(float4 vertPos, float width) sẽ giúp ta scale hình B lên bởi scale cube. Ta lấy tọa độ các đỉnh nhân với 1 matrix scaleMat ( scale matrix). (Cái này các bạn tự tìm hiểu thêm về kiến thức toán https://docs.unity3d.com/ScriptReference/Matrix4x4.Scale.html)

Và ta set Cull Front giúp hình B ẩn sau A. Kết quả cuối cùng ta được outline như hình.

Cảm ơn mọi người đã xem tới đây. Hẹn gặp lại vào bài viết sau. Full code: http://www.mediafire.com/file/sopt5rrm9cvuh3c/