1. 程式人生 > >UnityShader例項12:螢幕特效之馬賽克(Mosaic)材質

UnityShader例項12:螢幕特效之馬賽克(Mosaic)材質

馬賽克(Mosaic)材質

概述

馬賽克(Mosaic),估計是大夥平時很常見最討厭的圖片處理手段,嘿嘿,沒錯我說的就是"打碼"。好了,正經點,馬賽克指現行廣為使用的一種影象(視訊)處理手段,此手段將影像特定區域的色階細節劣化並造成色塊打亂的效果,因為這種模糊看上去有一個個的小格子組成,便形象的稱這種畫面為馬賽克。其目的通常是讓影象大規模的降低影象()視訊解析度,而讓影象(視訊)的一些細節隱藏起來,使之無法辨認,一般用來保護隱私,或者隱藏某些不健康的畫面。

原理

要實現馬賽克的效果,需要把圖片一個相當大小的正方形區域用同一個點的顏色來表示,相當於將連續的顏色離散化,因此我們可以想到用取整的方法來離散顏色,但是在我們的圖片紋理座標取樣時在0到1的連續範圍,因此我們需要將紋理座標轉換成實際大小的整數座標,接下來要把影象這個座標量化離散,比如對於一個解析度為256X256畫素的圖片,馬賽克塊的大小為8X8畫素,我們先得將紋理座標乘以(256,256)使其對映到0到256的範圍,相當於一個整數代表一個畫素,再將紋理座標取除以8之後取整,最後再將座標乘以8,除以256.重新映射回0到1的範圍,但是紋理座標已經是離散的,而非連續的。

Shader程式碼實現


到程式碼實現部分了,如果被上面原理講述繞暈的同學還是直接看程式碼吧(原諒我,理科生,作文從來沒行過)。首先還是得在屬性裡宣告一個馬賽克大小的引數 
 
Properties {
	_MainTex ("Base (RGB)", 2D) = "white" {}
	_MosaicSize("MosaicSize", int)=5
}


然後宣告一個內建四元數變數_MainTex_TexelSize,這個變數的從字面意思是主貼圖_MainTex的畫素尺寸大小,是一個四元數,它的值為 Vector4(1 / width, 1 / height, width, height);這個屬於unity的黑魔法,不知道是在哪裡定義的,官方文件並沒有解釋,有知道的網友可以在回覆裡告訴我。

half4 _MainTex_TexelSize;

因為馬賽克是針對畫素操作,所以關鍵程式碼也在Frag函式裡面實現:
float2 uv = (i.texcoord*_MainTex_TexelSize.zw) ;//將紋理座標對映到解析度的大小
uv = floor(uv/_MosaicSize)*_MosaicSize;//根據馬賽克塊大小進行取整
i.texcoord =uv*_MainTex_TexelSize.xy;//把座標重新映射回0,1的範圍之內
fixed4 col = tex2D(_MainTex, i.texcoord);


Shader完整程式碼


VF版本程式碼01
Shader "PengLu/Unlit/MosaicVF" {
Properties {
	_MainTex ("Base (RGB)", 2D) = "white" {}
	_MosaicSize("MosaicSize", int)=5
}

SubShader {
	Tags { "RenderType"="Opaque" }
	LOD 100
	
	Pass {  
		CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
		
			
			#include "UnityCG.cginc"

			struct appdata_t {
				float4 vertex : POSITION;
				float2 texcoord : TEXCOORD0;
				
			};

			struct v2f {
				float4 vertex : SV_POSITION;
				half2 texcoord : TEXCOORD0;
			
			
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			half4 _MainTex_TexelSize;
			int _MosaicSize;
			v2f vert (appdata_t v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
				
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				float2 uv = (i.texcoord*_MainTex_TexelSize.zw) ;
				uv = floor(uv/_MosaicSize)*_MosaicSize;
				i.texcoord =uv*_MainTex_TexelSize.xy;
				fixed4 col = tex2D(_MainTex, i.texcoord);
			
				UNITY_OPAQUE_ALPHA(col.a);
			
				return col;
			}
		ENDCG
	}
}

}

VF版本程式碼01效果:

C#指令碼程式碼


要做成螢幕特效,還需要指令碼配合,這裡不做過多解釋,腳本里重要部分有註釋,需要注意的是幾個函式,具體用處可以看官方文件(這裡這裡
OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture)

Graphics.Blit(sourceTexture, destTexture,material)

Graphics.Blit(sourceTexture, destTexture);


完整C#指令碼如下:
using UnityEngine;
using System.Collections;
using System;

[ExecuteInEditMode]
[AddComponentMenu ("PengLu/ImageEffect/Mosaic")]
public class ImageEffect_Mosaic : MonoBehaviour {
	#region Variables
	public Shader MosaicShader = null;
	private Material MosaicMaterial = null;	
	public int MosaicSize = 8;

	#endregion


//建立材質和shader
	Material material
	{
		get
		{
			if(MosaicMaterial == null)
			{
				MosaicMaterial = new Material(MosaicShader);
				MosaicMaterial.hideFlags = HideFlags.HideAndDontSave;	
			}
			return MosaicMaterial;
		}
	}

	// Use this for initialization
	void Start () {

		MosaicShader = Shader.Find("PengLu/Unlit/MosaicVF");
		
		// Disable if we don't support image effects
		if (!SystemInfo.supportsImageEffects)
        {
            enabled = false;
            return;
        }

        // Disable the image effect if the shader can't
        // run on the users graphics card
        if (!MosaicShader || !MosaicShader.isSupported)
            enabled = false;
            return;
	
	}



	void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture)
	{	
		if(MosaicSize > 0 && MosaicShader != null){

	        material.SetInt("_MosaicSize", MosaicSize);//將馬賽克尺寸傳遞給shader
		    
                Graphics.Blit(sourceTexture, destTexture,material);//將抓取的影象傳遞給gpu並用shader處理後,傳回來

  
		}

		else{
			Graphics.Blit(sourceTexture, destTexture);
			
		}
		
		
	}
	
	// Update is called once per frame
	void Update () {
		#if UNITY_EDITOR
		if (Application.isPlaying!=true)
		{
			MosaicShader = Shader.Find("PengLu/Unlit/MosaicVF");

		}
		#endif
	
	}

	 public void OnDisable () {
        if (MosaicMaterial)
            DestroyImmediate (MosaicMaterial);
    }
}


馬賽克效果變種


在網上查資料的過程中,也看到一些很有意思的馬賽克演算法變種,這裡我只將它們的關鍵程式碼以及實現效果放上來,大家可以自己理解制作。

圓形馬賽克


關鍵程式碼:
float2 intUV = (i.texcoord*_MainTex_TexelSize.zw) ;
float2 xyUV = floor(intUV/_MosaicSize)*_MosaicSize+0.5*_MosaicSize;
float disSample = length(xyUV-intUV);
float2 mosaicUV = xyUV*_MainTex_TexelSize.xy;
fixed4 col = tex2D(_MainTex, i.texcoord);
if(disSample<0.5*_MosaicSize)
	col = tex2D(_MainTex,mosaicUV);


程式碼效果:

正六邊形(蜂巢)馬賽克

演算法原理參考這裡這裡,我稍微做了些簡化,減少了些數學運算使之能支援tanget 2.0,演算法不難理解,但是自己想出來卻還是蠻難的,所以很佩服原作者,由此可見數學在shader程式設計中還是相當重要的;
關鍵程式碼:
const float TR = 0.866025f;//TR=√3
float2 xyUV = (i.texcoord*_MainTex_TexelSize.zw);
int wx = int (xyUV.x/1.5f/_MosaicSize);
int wy = int (xyUV.y/TR/_MosaicSize);

float2 v1,v2;
float2 wxy =float2(wx,wy);
if(wx/2*2==wx){
	if(wy/2*2==wy){
		v1 = wxy;
		v2 = wxy+1;
	}
	else{
		v1 = wxy+float2(0,1);
		v2 = wxy+float2(1,0);
	}	
}
else{
	if(wy/2*2 == wy){
		v1 = wxy+float2(0,1);
		v2 = wxy+float2(1,0);
	}
	else{
		v1 = wxy;
		v2 = wxy+1;
	}
}
v1 *= float2(_MosaicSize*1.5f,_MosaicSize*TR);
v2 *= float2(_MosaicSize*1.5f,_MosaicSize*TR);

float s1 = length(v1.xy-xyUV.xy);
float s2 = length(v2.xy-xyUV.xy);
fixed4 col = tex2D(_MainTex,v2*_MainTex_TexelSize.xy);
if(s1 < s2)  
	col = tex2D(_MainTex,v1*_MainTex_TexelSize.xy);  


程式碼效果: