Depth and Normal Texture (Part 1)

반응형

이 글은 번역글 이다.

원본 : http://williamchyr.com/2013/11/unity-shaders-depth-and-normal-textures/


나는 지난 삼일을 유니티 셰이더를 배우는데 보냈다.

많은 양의 기본 문서들을 보는 것은 그다지 어려운 일이 아니었다.

하지만 깊이 버퍼(depth buffer)까지 왔을 때, 후처리 (post process)의

특별한 효과에 무척 효율적이지만, 정보가 절대적으로 부족하며, 

유니티의 문서가 크게 도움이 되지 않았다.

예를들자면, 만약 어떻게 깊이 텍스처와 노멀 텍스처가 사용되어지는지

알려고 해도 유니티 문서의 조언은 "대체 셰이더 예제 프로젝트의 외곽검출이나

SSAO 효과에 사용되어진다."라고만 되어 있다.

이는 셰이더를 잘 파악하고 있는 누군가에게는 충분한 내용이지만,

초보에게는 그다지 도움이 되지 않는 내용이다.


어쨋든, 많은 시간의 코딩으로 시행착오를 거치고, 여러 블로그를 찾아 다니고

포럼에서 토픽에 관해서 논의해서, 드디어 유니티에서 깊이 텍스처와 

노멀 텍스처가 어떻게 작동하는지 알게되었다.

학습과정은 실망스러웠지만, 나는 내가 기억하는 동안 내가 무었을 했는지

글을 쓰는것이 좋겠다고 생각했다.


- 몇달안에, 나는 내가 무었을 했는지 잊을 것이고 내가 작성한 코드를

  이해 못할지도 모른다.

- 누군가가 같은 문제에 직면했을 때, 이 정보가 도움이 되길 바란다.

  몇몇의 블로그 글들은 내가 깊이 텍스처를 알아내는데 엄청난

  도움을 주었고, 이 글을 써 내려가는 동안에도 그 블로거들에게 정말 

  감사하고 있다.


자 이제 시작해 보자.


영감

나는 육개월 전즘에 셰이더를 공부하기시작했다.

그래픽 파이프라인을 설명하는 많은 튜토리얼, 

다른 종류의 셰이더 들 등이 기억이 난다.

이 즈음해서 나는 그런 셰이더들이 이해가 되지 않았고

무척 어려워 보였다.

나는 기존의 셰이더로 시작하여 간신히 몇가지를 알아냈고 내가 원하는 것을

알아내기까지 그 주위의 것들을 파헤쳤다.


이번에, Quantum Conundrum 이라는 게임의 차원 이동 효과를 

다시 만들어 보기를 원했다.



Quantum Conundrum을 아직 해보지 않았다면, 위 효과가 어떻게 나오는지 설명할 것이다.

기본적으로,  케릭터는 차원을 넘나 다닐 수 있는 능력을 가지고 있다.

중력이 가벼운 차원, 중력이 무거운 차원, 움직임이 느린 차원, 그리고 역중력 차원이 있다.

각 차원에서, 환경과 오브젝트의 모양이 끊임없이 변하며 서로 다른 물리적 특성을 가진다.

예를들어, 가벼운 차원에서는 모든 것이 매우 가볍기 때문에 소파를 집어들거나 다른 무거운 것들을

쉽게 들어올릴 수 있고, 무거운 차원에서는 모든것이 실제로 무거워 져서 

일반적으로 버튼을 무겁게 누르지 않았을 종이상자가 무거운 차원에서는

무겁게 눌러야 할 만큼 무거워진다.


게다가 속성을 바꾸기 위해, 모이는 모든것들을 바꾼다.

가벼운 차원에서 모든것은 구름처럼 보이는 반면에 무거운 차원에

있는 동안은 모든것이 금속 텍스처를 가진것 처럼 보인다.

위의 gif 이미지 에서 보면 플레이어는 기본 차원에서 무거운 차원으로 이동하고 나서,

가벼운 차원으로 갔다가 무거운 차원으로 돌아온 후 다시 기본차원으로 돌아온다.


가벼운 차원


무거운 차원


기본 차원



변환하는 장면


이 효과에 대해 몇가지 알아차린 것이 있다.

1. 방을 통과하는 빛의 고리는 항상 내가 쳐다보고 있는 오브젝트에서 시작해서

   바깥으로 퍼져나간다.

   나의 추측으로는 창문뒤로 링의 일부가 보이는걸로 봐서 모든 방향으로 팽창하는 

   구가 있는것 같다.

2. 빛의 링은 환경 뿐 아니라 오브젝트들과도 겹쳐진다.

3. 링은 차원의 텍스처를 분할하며, 새 차원의 텍스처는 링이 지나가기 전까지

   실제로 넣지 않는다. 이는 특정 시점에서 오브젝트들이 실제로 두개의 텍스처를 

   가진다는 것을 의미한다. (그림을 자세히 보면 정 가운데에 있는 의자와 의자의 오른쪽

   바래 바닥은 무거운 차원의 텍스처로 바뀌었지만 다른 부분은 기본 차원으로 남아있다.)


첫번째 단계 - 깊이 텍스쳐 구축

이 효과를 만들기 위한 아이디가 없고 어디서부터 시작해야 할지도 모르겠다.

몇몇 포럼과 트위터에 질문을 달고나서, 이 효과가 공간적 인식(spatially aware) 느낌을 주기 위해

깊이 버퍼를 사용하는 후처리 효과를 사용하는 셰이더라는 것을 알았다.


이 시점에 나는 내가 배웠던 셰이더를 대부분 다 까먹었기에 기본부터 다시 하기로 했다.

이쪽을 너무 파고싶지는 않기에 surface shader와 vertex/fragment shader의 차이점,

그리고 내가 찾은 실제로 도움을 주는 참고 링크들에 대해서 설명하는것은 하지 않기로 한다.

이 내용들이 처음에는 혼란스럽고 어려워 보이지만 몇번 읽어보고 셰이더를 작성하는 것을

연습 해 보면 내가 약속하건데 모든 것을 이해할 수 있을 것이다.

아래 글을 읽기 전에 적어도 위 링크들을 한번 보기를 권하며, 특히 셰이더에 신입이라면

더욱 위 링크들을 한번 보는것을 추천한다.


유니티에서 깊이 버퍼를 얻기 위해 렌더 텍스처를 사용해야만 하는데

이는 실시간으로 생성되어 지고 갱신되는 특수한 텍스처이다.

게임의 한 지역에서 어떤 일이 벌어지도록 보이게 하는 tv화면같은 것을

만드는데 이 텍스처를 사용할 수 있다.

깊이버퍼 또는 깊이 텍스처는 사실 단순히 화면에서

물체가 카메라로부터 얼마나 멀리 떨어져 있는지를 나타내는 값을 

기록하고 있는 렌더 텍스처이다.


그래서 어떻게 깊이 텍스처를 얻을 것인가?

첫번째로 깊이 텍스처를 만들기 위해 카메라에 대해 이야기 해야 하는데

Camera.depthTextureMode를 알아야 한다.

그리고나서 셰이더에 전달하기 위해 OnRenderImage 함수를 사용해야 한다.


PostProcessDepthGrayscale.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using UnityEngine;
using System.Collections;

//so that we can see changes we make without having to run the game

[ExecuteInEditMode]
public class PostProcessDepthGrayscale : MonoBehaviour {

   public Material mat;

   void Start () {
      camera.depthTextureMode = DepthTextureMode.Depth;
   }

   void OnRenderImage (RenderTexture source, RenderTexture destination){
      Graphics.Blit(source,destination,mat);
      //mat is the material which contains the shader
      //we are passing the destination RenderTexture to
   }
}

카메라 오브젝트에 위 스크립트를 붙인다.


셰이더

이제 깊이텍스처를 처리하고 디스플레이 하기 위해 셰이더를 만들것이다.

그냥 단순한 vertex, fragment 셰이더가 될 것이다.

기본적으로 카메라로부터 깊이 텍스처를 읽어서 screen 좌표에 깊이값을 출력한다.

DepthGrayscale.shader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Shader "Custom/DepthGrayscale" {
SubShader {
Tags { "RenderType"="Opaque" }

Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

sampler2D _CameraDepthTexture;

struct v2f {
   float4 pos : SV_POSITION;
   float4 scrPos:TEXCOORD1;
};

//Vertex Shader
v2f vert (appdata_base v){
   v2f o;
   o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
   o.scrPos=ComputeScreenPos(o.pos);
   //for some reason, the y position of the depth texture comes out inverted
   o.scrPos.y = 1 - o.scrPos.y;
   return o;
}

//Fragment Shader
half4 frag (v2f i) : COLOR{
   float depthValue = Linear01Depth (tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.scrPos)).r);
   half4 depth;

   depth.r = depthValue;
   depth.g = depthValue;
   depth.b = depthValue;

   depth.a = 1;
   return depth;
}
ENDCG
}
}
FallBack "Diffuse"
}

위 코드의 vertex shader에서 한가지 언급할 부분이 있다.

24번째 줄을 보면 o.srcPos.y = 1 - o.srcPos.y; 라고 있다.

몇가지 이유로 나의 깊이 텍스처는 y축이 반전되어 나오고 나는 이 부분을 

겪는 다른 사람을 찾을 수 없어서 결국 y값을 반전시켰다.

만약 결과값이 y값 반전이 되어 나온다면 24번째 줄을 주석처리 하면 된다.


이제 새 머티리얼을 만들어 위에서 만든 셰이더를 적용하고 PostProcessDepthGrayscale.cs를

붙이 카메라 오브젝트에 머티리얼을 설정 해 준다.


결과물




위와 같은 결과물을 볼 수 있다.

같은 박스를 여러개 만들어 위치를 다르게 두었다.

만약 색생값이 다르게 나오지 않고 같게 나온다면 카메라의 far clipping값을 수정해야 한다.


추가사항

이 글은 단순한 번역글이다.

다만 추가하고 싶은 내용이 있어서 글을 적는다.

shader의 24번째 줄의 의미에 관한 것이다.

uv의 좌표값은 시스템마다 조금씩 다르다.

나에게 익숙한 directx는 왼쪽위가 0이었고 오른쪽으로 갈수록 u값이 커지며 아래쪽으로 갈수록

v의 값이 커지는데 즉, 왼쪾위가 0,0으로 시작한다.

하지만 unity는 다중 플랫폼을 지원하며 opengl은 왼쪽위가 0,1로 시작한다.

그러므로 오른쪽 아래가 1,0이 된다.

그래서 나는 셰이더를 짤 때 이런 방식을 사용한다.

#if UNITY_UV_STARTS_AT_TOP

if( o.scrPos.y < 0.0 )

{

o.scrPos.y = 1.0 - o.scrPos.y;

}

#endif


관련 파일

DepthGrayscale.shader

PostProcessDepthGrayscale.cs



참고 링크

- unity doc, render texture

- game dev forver - unity uv

- unity doc, camera depth texture

- unity doc, camera depth texture 의 사용

-

'Unity > Unity Graphics' 카테고리의 다른 글

Depth and Normal Texture (Part 2)  (0) 2016.06.13
Unity에서 Depth Texture의 사용  (0) 2016.04.29
#pragma multi_compile  (0) 2016.04.23
Performance Tips when Writing Shaders  (0) 2016.04.15
TRANSFORM_TEX  (0) 2016.04.15
TAGS.

Comments