2014年6月19日 星期四

Deferred Rendering 下的特殊需求

這是針對Unity4的Deferred Rendering,因為5的做法會大改,而且也還沒發佈所以不確定。

最近有個特殊需求是,在做PostEffect時,希望能把一些東西排除掉,列如背景去飽和了,但人物想不被受影響。

但這個在Deferred rendering下會有問題,因為Unity的Deferred Rendering的PostEffect只能作用在最後一隻畫的Camera上,因此想了幾個方法。

1.畫人物時同時畫Stencil,PostEffect判斷Stencil,把人物排除。這個缺點是邊緣會有些鋸齒,Stencil應該沒AA。而且半透明物件就沒辦法。

2.人物在PostEffect之後用Forward畫,。缺點是,Camera深度沒共用,人不會被場景遮蔽。

3.人物用Shader tag,用一隻Camera RenderwithShader,輸出mask。但RenderWithShader同上,只能在Forward下使用,Mask也沒深度。而且另外做mask,drawcall也會增加。

4.把場景、特效、人物,用不用的RenderTexture輸出,最後在另一個PostEffect合併。這是個可行的方法,會增加一些Blit的drawcall,但是固定的。只是會用掉很多Vram。一張Full HD的rendertarget還滿大的。

上述方法好像都有缺點。

如果不同render path的Camera能Share Depth buffer就好了。

所以,如果拿Deferred Rendering的Camera深度,寫到Forward那隻Camera不就解決了?
於是2的Modify選項就出現了。

Camera結構如下:
MainCamera <-- Deferred Rendering
   -PlayerCamera  <-  Forward Rendering (clear flag -> don't clear)

在PlayerCamera掛上一個DepthWrite的Script
Cull Mask設定一下

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class DetphWrite : MonoBehaviour {

 private Material DepthWriteMaterial = null;
 // Use this for initialization
 void Start () {
  DepthWriteMaterial = new Material(Shader.Find("Hidden/DepthWrite"));
 }
 

 void OnPreRender()
 {

  DrawQuad();
 }

 void DrawQuad()
 {
  GL.PushMatrix(); 
  GL.LoadOrtho();
  
  DepthWriteMaterial.SetPass(0);     
  
  //Render the full screen quad manually.  
  GL.Begin(GL.QUADS); 
  GL.TexCoord2(0.0f, 0.0f); GL.Vertex3(0.0f, 0.0f, 0.1f);  
  GL.TexCoord2(1.0f, 0.0f); GL.Vertex3(1.0f, 0.0f, 0.1f);  
  GL.TexCoord2(1.0f, 1.0f); GL.Vertex3(1.0f, 1.0f, 0.1f);  
  GL.TexCoord2(0.0f, 1.0f); GL.Vertex3(0.0f, 1.0f, 0.1f);  
  GL.End();
  
  GL.PopMatrix();
 }
}

Shader如下:
Shader "Hidden/DepthWrite" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "black" {}
    }
SubShader {
    Pass {
        ZTest Always Cull Off ZWrite On
      Fog { Mode off }
        
        CGPROGRAM        
            #pragma exclude_renderers gles flash
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 4.0
             #include "UnityCG.cginc" 
            // vertex input: position, UV
            struct appdata {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
            };
           
            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
           
            v2f vert (appdata v) {
                v2f o;
                o.pos = mul( UNITY_MATRIX_MVP, v.vertex );
                o.uv = v.texcoord.xy;
                return o;
            }
           

            
            sampler2D _MainTex;     
            sampler2D _CameraDepthTexture;
            
            
            struct fragOut
           {
               // half4 color : COLOR;   don't need
               float depth : DEPTH;
            };
          
            fragOut frag( v2f i ) {
           
                fragOut o;
               
                float depth =UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture,i.uv));
              
              // o.color= 0; // 
                o.depth= depth;
                return o;
           }
            
            

        ENDCG
        }
    }
}
本來以為_CameraDepthTexture要自己copy,但發現竟然Get到的是對的,值沒被Clear掉。如果Forward那支Camera,有下Clear flag Depth,就會被清掉。所以如果要自己畫Mask,前面3的方式,應該也可以傳進來自己判斷。

這個方法的缺點是用到Shader Model 4.0,要把Dx11的flag打開。
另外,在PlayerCamera畫的Soft Particle會失效(明明 _CameraDepthTexture抓得到),我把Particle Shader中#ifdef SOFTPARTICLES_ON 註解掉就可以。應該是Unity判斷Camera不同Render Path時,就 define off了。

如果要在d3d9下可以用,另一個想到的方法是在vertex shader讀入depth map,然後事前產生一個plane,它的點數和螢幕pixel一樣多,再寫入depth。可能對點的位置會有點麻煩,但理論上可行。

沒有留言 :

張貼留言