CommandBuffer 를 이용한 Multipass Shader 기법





기본적으로 쉐이딩 프로그래밍은 1점에 대하여 컬러를 연산 하는것 이기 때문에, Blur 등과 같은 효과를 최종적으로 주기 위하서는 앞서 쉐이더 처리된 완성된 비트맵이 필요 하다. 하지만 안타깝게도 유니티에서 이러한 처리를 하기에는 별도의 카메라의 렌더택스쳐가 필요하거나, FPS 저하의 1등 공신 Texture2D.Apply() 을 써야 하는 상황이 벌어진다.


하지만 멀티 패스 를 이용하여 처리를 할 수 있다면, 쉐이더 처리결과를 비트맵으로 옮기고 다시 읽는 불필요한 과정이 없다면 가능하지 않을까 해서 조사를 시작, 

다행이도 이번 Unity3d 5 의 새로운 API 인 CommandBuffer 를 이용한다면 가능하다.



기본적인 아이디어는 아래와 같다.








커맨드 버퍼를 이용한다면 같은 Pass 를 여러번 처리하는것도 가능하여 특히 Blur 효과에 탁월 할 것이다. 
아래는 실제로 위의 아이디어를 이용한 예제 이다.




간단한 기능의 멀티패스 쉐이더 (MultipassSample.shader)

Shader "Custom/MultipassSample" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "" {}
    }

    
    
Subshader {

    //Pass 1 : 텍스처 컬러 반전
    pass  
    {  
        CGPROGRAM  
        
        #pragma fragment frag  
        #pragma vertex vert_img  
        #include "UnityCG.cginc"  
         
        sampler2D _BgTex;
        float4 frag(v2f_img input) : COLOR  
        {  
            float4 color = tex2D(_BgTex , input.uv);
            color = 1 - color;
            color.a = 1;
            return color;
        }  
        ENDCG  
    }  
    
    //Pass 2 : 블러 이펙트
    Pass {
        ZTest Always Cull Off ZWrite Off
        Fog { Mode off }

        CGPROGRAM
        #pragma fragmentoption ARB_precision_hint_fastest
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"

        struct v2f {
            float4 pos : POSITION;
            float2 uv : TEXCOORD0;
            
            float4 uv01 : TEXCOORD1;
            float4 uv23 : TEXCOORD2;
            float4 uv45 : TEXCOORD3;
        };
        
        float4 offsets;

        sampler2D _MainTex;

        v2f vert (appdata_img v) {
        v2f o;
        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

        o.uv.xy = v.texcoord.xy;

        o.uv01 =  v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1);
        o.uv23 =  v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1) * 2.0;
        o.uv45 =  v.texcoord.xyxy + offsets.xyxy * float4(1,1, -1,-1) * 3.0;

        return o;
        }
        
        half4 frag (v2f i) : COLOR {
            half4 color = float4 (0,0,0,0);

            color += 0.40 * tex2D (_MainTex, i.uv);
            color += 0.15 * tex2D (_MainTex, i.uv01.xy);
            color += 0.15 * tex2D (_MainTex, i.uv01.zw);
            color += 0.10 * tex2D (_MainTex, i.uv23.xy);
            color += 0.10 * tex2D (_MainTex, i.uv23.zw);
            color += 0.05 * tex2D (_MainTex, i.uv45.xy);
            color += 0.05 * tex2D (_MainTex, i.uv45.zw);

            return color;
        }
        ENDCG
    }
}

Fallback off

} // shader

dsadsadsad





(매우 간단한) 최종적 으로 렌더링할 쉐이더 (Final.shader)



Shader "Custom/Final" {
SubShader 
{
    Pass 
    {   
        CGPROGRAM
        #pragma vertex vert_img
        #pragma fragment frag
        #include "UnityCG.cginc"

        sampler2D _FinalTexture;
        
        half4 frag (v2f_img input) : SV_Target
        {
            return tex2D(_FinalTexture , input.uv);
        }
        ENDCG
    }
}
}


aaa





오브젝트에 사용할 C# 코드


using UnityEngine;
using System.Collections;
using UnityEngine.Rendering;


public class BlurTest1 : MonoBehaviour {

    public Shader MultiPassShader;
    public Texture2D _test_screen_texure;

    private Material _blurMaterial;
    private Camera _cam;

    private float blurAmount = 1;
    private Texture2D _main_texure;
    private bool _isReady = false;

    // Use this for initialization
    void Start () {
    }
    
    void CleanUp()
    {
        _isReady = false;
        Object.DestroyImmediate(_blurMaterial);
    }

    
    //OnWillRenderObject 에 넣어야만 동작 한다
    //http://docs.unity3d.com/ScriptReference/MonoBehaviour.OnWillRenderObject.html
    void OnWillRenderObject()
    {


        //--------------------------------------------------------------------------반복적인 처리가 되지 않도록 함.
        #region
        

        if ((gameObject.activeInHierarchy && enabled) == false)
        {
            CleanUp();
            return;
        }

        _cam = Camera.current;
        if (_cam == null)
        {
            _isReady = false;
            return;
        }

        if (_isReady == true) return;

        #endregion
    






        if (_blurMaterial == null)
        {
            _blurMaterial = new Material(MultiPassShader);
            _blurMaterial.SetTexture("_BgTex", _test_screen_texure);
            _blurMaterial.hideFlags = HideFlags.HideAndDontSave;
        }

        //빈 텍스쳐를 생성하여 텍스쳐 버퍼를 채워준다.
        if (_main_texure != null)
        {
            _main_texure = new Texture2D(1024, 1024);
        }




        //커맨드 버퍼 생성
        CommandBuffer buf = new CommandBuffer();




        //--------------------------------------------------------------------------첫번째 Pass
        #region


        //CommandBuffer의 GetTemporaryRT 를 이용하여 임시 공간을 확보 한다.
        //http://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.GetTemporaryRT.html
        int screenCopyID = Shader.PropertyToID("_ScreenCpyTexture");
        buf.GetTemporaryRT(screenCopyID, -1, -1, 0, FilterMode.Bilinear);

        //BuiltinRenderTextureType.CameraTarget 으로 현재 카메라가 바라보고 있는 화면을 텍스쳐로 가져 올 수 있다
        //기존의 [GrabPass{}] 와 유사하다.
        //buf.Blit(BuiltinRenderTextureType.CameraTarget, screenCopyID);



        //_main_texure 의 데이터를 screenCopyID 에 할당된 공간으로 _blurMaterial 의 0번 Pass 를 통하여 저장한다.
        buf.Blit(_main_texure, screenCopyID, _blurMaterial, 0);


        #endregion








        //--------------------------------------------------------------------------두번째 Pass
        #region


        // 2개의 GetTemporaryRT 를 선언 한것은 , 연산 결과를 서로 주고 받아 블러 효과를 극대화 하기 위함이다.
        int blurredID1 = Shader.PropertyToID("_Tmp1");
        buf.GetTemporaryRT(blurredID1, -2, -2, 0, FilterMode.Bilinear);

        int blurredID2 = Shader.PropertyToID("_Tmp2");
        buf.GetTemporaryRT(blurredID2, -2, -2, 0, FilterMode.Bilinear);


        //우선 blurredID1 에 첫번째 Pass 의 결과 데이터를 옮긴다.
        buf.Blit(screenCopyID, blurredID1);


        //더이상 사용되지 않음으로 제거한다.
        buf.ReleaseTemporaryRT(screenCopyID);



        //Blur 효과 , blurredID1 과 blurredID2 가 가로, 세로를 반복적으로 주고 받아 풍성한 블러 효과를 만든다.
        if (true)
        {
            //// 가로 흐림
            buf.SetGlobalVector("offsets", new Vector4(2.0f * blurAmount / Screen.width, 0, 0, 0));
            buf.Blit(blurredID1, blurredID2, _blurMaterial, 1);
            // 세로 흐림
            buf.SetGlobalVector("offsets", new Vector4(0, 2.0f * blurAmount / Screen.height, 0, 0));
            buf.Blit(blurredID2, blurredID1, _blurMaterial, 1);
            //// 가로 흐림
            buf.SetGlobalVector("offsets", new Vector4(4.0f * blurAmount / Screen.width, 0, 0, 0));
            buf.Blit(blurredID1, blurredID2, _blurMaterial, 1);
            //// 세로 흐림
            buf.SetGlobalVector("offsets", new Vector4(0, 4.0f * blurAmount / Screen.height, 0, 0));
            buf.Blit(blurredID2, blurredID1, _blurMaterial, 1);
        }

        //최종적으로 계산된 버퍼를 현재 GameObject 의 Material Shader 의 _FinalTexture 에 넣어준다.
        buf.SetGlobalTexture("_FinalTexture", blurredID1);

        #endregion


        //buf 를 현재 카메라 렌더링에 반영한다. 
        //(순서를 정할수 있다. 하지만 아직 렌더링 순서에 대하여 명확하게 이해하지 못했다.)
        _cam.AddCommandBuffer(CameraEvent.AfterSkybox, buf);


        Debug.Log("Process complete");
        buf.Dispose();
        _isReady = true;
    }
}

ㅁㅁㅁ

유니티 프로젝트 

결과는 아래와 같다.




비로서 Pass 간의 결과를 주고 받을 수 있게 되었다.



Yamecoder 야매코더_
unity3d 2015.04.23 20:42
Powerd by Tistory, designed by criuce
rss