iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 25
0
自我挑戰組

初見Unity Shader系列 第 25

Unity中存取相機視角的方法

文章內使用Unity 2019 LTS

一個場景有著各種形狀的物體,假如今天我們不只是要對單一物體進行特效處理,而是要對整個場景所看到「視角」進行處理,例如:模糊效果、傳送門...,勢必不可能在特定時間一個一個對照射到的物體進行處理,所以,有幾種方法可以把整個看到的視角,不論3D或2D,存在紋理貼圖(Texture)裡面,再進行加工。

Render Texture

利用相機把照射到的視角,儲存在Render Texture裡面,首先,先從Create -> Render Texture新增出一張Render Texture,之後,在要捕捉的相機上找到Target Texture的屬性,把剛剛新增Render Texture附加上去,這樣這張貼圖就可以獲得相機的視角。

我們就可以像利用一般的紋理貼圖一樣,對它進行處理,會發現貼圖的結果是上下顛倒的,需要把紋理座標做一下修正。

Shader "Learning/RenderTexture" {
	Properties {
		_MainTex ("Main Tex", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}
		
		Pass {
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"
			
			sampler2D _MainTex;
			
			struct v2f {
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;
			};
			
			v2f vert(appdata_base v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.uv = v.texcoord;
                // 反轉紋理座標
				o.uv.x = 1 - o.uv.x;
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				return tex2D(_MainTex, i.uv);
			}
			
			ENDCG
		}
	} 
 	FallBack Off
}

有一點很大的不同是,Render Texture是直接吃相機的視角的,如果視角內繪製的物體有變化,貼圖也會跟著變化,可以把它延伸做出影片撥放的效果。

GrabPass

GrabPass則是直接利用Shader的語法來直接獲取螢幕影像,用法為:

Shader "Learning/GrabPassTest"
{
    Properties{
        
    }
    SubShader{
        Tags{"Queue"="Transparent" "RenderType"="Opaque"}
        
        // GrabPass!!
        GrabPass{"_GrabPassTexture"}

        pass{

            CGPROGRAM
            #pragma vertex vertex
            #pragma fragment frag
            #include "UnityCG.cginc"
            
            // 記得要像使用變數一樣,再一次宣告他們
            sampler2D _GrabPassTexture;
            float4 _GrabPassTexture_TexelSize;

            struct v2f{
                
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            v2f vert(appdata_base v){

                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                return o;
            }

            fixed4 frag(v2f i):SV_TARGET{

                return tex2D(_GrabPassTexture,i.uv);
            }
            ENDCG
        }
    }
}

直接在大括號中填上的字串就是產生出來的「螢幕紋理貼圖」的變數名稱,在變數後面加上_TexelSize可以獲得GrabPass的紋理大小

OnRenderImage

OnRenderImage則是一個在MonoBehaviour底下的一個函數,常用在「螢幕後製處理(screen post-processing)」上。

以下就拿一個調節螢幕亮度的功能當作範例:

  1. 新增一個Shader:
Shader "Learning/Brightness" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_Brightness ("Brightness", Float) = 1
	}
	SubShader {
		Pass {  
			ZTest Always Cull Off ZWrite Off
			
			CGPROGRAM  
			#pragma vertex vert  
			#pragma fragment frag  
			  
			#include "UnityCG.cginc"  
			  
			sampler2D _MainTex;  
			half _Brightness;
			  
			struct v2f {
				float4 pos : SV_POSITION;
				half2 uv: TEXCOORD0;
			};
			  
			v2f vert(appdata_img v) {
				v2f o;
				
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.uv = v.texcoord;
						 
				return o;
			}
		
			fixed4 frag(v2f i) : SV_Target {
				fixed4 texColor = tex2D(_MainTex, i.uv);  
				
				
				return fixed4(texColor.rgb * _Brightness, texColor.a);  
			}  
			  
			ENDCG
		}  
	}
	
}

  1. 再新增一個Script控制這個Shader
using UnityEngine;

[RequireComponent(typeof(Camera))]
public class PostBrightness : MonoBehaviour
{
    public Shader BrightnessShader;
    private Material mat;

    [Range(0.0f, 3.0f)]
    public float Brightness = 1.0f;

    private void Start() {
        if (BrightnessShader == null) {
		return;
	}

        if (!BrightnessShader.isSupported) {
	    return;
	}
	else {
		mat = new Material(BrightnessShader);
		mat.hideFlags = HideFlags.DontSave;
	}
    }
    
    
    void OnRenderImage(RenderTexture src, RenderTexture dest) {
        if (mat != null) {
            mat.SetFloat("_Brightness", Brightness);

            Graphics.Blit(src, dest, mat);
        } else {
            Graphics.Blit(src, dest);
        }
    }
}

Reference


上一篇
折射...好像漏了很多東西
下一篇
畫面模糊效果
系列文
初見Unity Shader30

尚未有邦友留言

立即登入留言