유니티에서 depth를 이용한 fullscreen shader effect를 구현하려면

보통 Standard Assets의 SSAO등과 같이 PostEffectBase를 상속받아서 다음과 같이 구현한다.


[ImageEffectOpaque]

void OnRenderImage (RenderTexture source, RenderTexture destination)

{

...

}


이때 [ImageEffectOpaque]는 SSAO의 음영같이 transparent한 것들보다 먼저 처리되기 위해서 쓰이는 키워드

이게 없으면 이펙트 위에 그림자가 그려짐..


근데 이런 방식은 굳이 필요없는 source까지 사용되고 (shader에는 _MainTex)

이렇게 된다면 분명 current buffer를 copy하는 불필요한 작업을 할것만 같아 뭔가 부담스럽게 느껴진다. (그냥 추측임;)


따라서 screen size만한 quad를 직접 그려서 사용하면 뭔가 심플해질 듯 하다

scene에 3D Object인 quad를 추가하고 다음과 같은 script를 작성해주자


void OnEnable()

{

Camera.main.depthTextureMode |= DepthTextureMode.Depth;

}


void OnWillRenderObject()

{

Camera cam = Camera.main;


float dist = cam.farClipPlane - 0.01f; // farClipPlane과 정확히 일치하면 아예 안그려짐..

Vector3 campos = cam.transform.position;

Vector3 quadpos = campos + cam.transform.forward * dist;


transform.position = quadpos;

 // uv방향을 depthtexture와 맞춰주기 위해 뒤쪽을 향하자

transform.rotation = Quaternion.LookRotation(quadpos - campos);


float h = Mathf.Tan(cam.fieldOfView * Mathf.Deg2Rad * 0.5f) * dist * 2f;

Vector3 scale = transform.parent ? transform.parent.localScale : Vector3.one;

transform.localScale = new Vector3(h * cam.aspect / scale.x, h / scale.y, 0f);

}


이렇게 하면 정확히 현재 카메라의 FarPlane에 screen size로 quad가 배치된다.

OnWillRenderObject()로 한 이유는 LateUpdate에서 카메라의 위치가 변한 이후에 처리하기 위함.

나중에 pixel shader에서 _CameraDepthTexture를 사용하기 위해서

OnEnable()에서 DepthTextureMode를 설정해주자


아름답게 screen quad가 배치되었다.


이제 shader를 작성해주자

Queue를 Transparent로 해야 Opaque한 것들을 그린뒤 screen effect가 그려진다

Tags

{

"Queue"="Transparent"

...

}


Fog, ZTest, ZWrite는 다 꺼주자

Pass

{

Fog { Mode Off }

ZWrite Off ZTest Always

 ...

}


UnityCG.cginc를 include하고 vertex shader를 작성하자

#include "UnityCG.cginc"


struct v2f

{

float4 pos : SV_POSITION;

float2 uv  : TEXCOORD0;

float3 ray : TEXCOORD1;

};


v2f vert (appdata_base v)

{

v2f o;

o.pos = mul (UNITY_MATRIX_MVP, v.vertex);

o.uv = v.texcoord.xy;

// 현재 farplane의 quad를 그리고 있기때문에 v자체가 farplane의 꼭지점

o.ray = mul (_Object2World, v.vertex) - _WorldSpaceCameraPos;

return o;

}

v.vertex는 object의 local좌표이고 _Object2World로 변환하면 farplane quad의 world position을 얻게된다

여기서 _WorldSpaceCameraPos를 빼주면 카메라 위치에서 quad꼭지점까지의 ray이고

이 ray는 depth로 world position을 구하는데 사용된다.


Standard Assets의 ScreenSpaceAmbientObscurance를 보면

depth로 camera space position을 구하는 것이 있는데

보통 world position이 더 유용한 경우가 많기 때문에 여기서는 world ray를 구한다.


이제 pixel shader작성.

sampler2D _CameraDepthTexture;


fixed4 frag(v2f i) : SV_Target

{

// depth to world position

float  depth = Linear01Depth( SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv) );

float3 world = i.ray * depth + _WorldSpaceCameraPos;


 // do anything

 ...

 ...

}


이 방식으로 

current buffer가 필요없는 cloud shadow, height fog, ssao 등을 구현할 수 있다.


하지만 _CameraDepthTexture를 사용하는 것이라

모바일에서 device spec에 따른 성능 하락을 고려해야한다.

(iPhone5 이상은 큰 무리가 없음..)


특수한 경우는 오히려 성능향상에 도움을 줄 수도 있다

예를들어 cloud shadow를 projector로 하는 경우가 있는데

이때 projector가 전체 scene에 드리우기 때문에 전체 draw call이 두배가 되어버린다.

projector로 하느니 그냥 screen effect로 한번에 구름 그림자를 그려주는 것이 디바이스에 따라 더 빠른 경우도 있다.

저작자 표시 비영리 변경 금지
신고
Posted by ttmayrin


티스토리 툴바