最近有個特殊需求是,在做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。可能對點的位置會有點麻煩,但理論上可行。
沒有留言 :
張貼留言