Unity/Unity Graphics

Depth and Normal Textures (Part 3)

붕대마음 2016. 7. 24. 18:22
반응형

이전 글 : Depth and Normal Texture (Part 2)

원문 : Depth and Normal Texture (Part 3)


이 글은 세편의 시리즈로 이루어 져 있고 이 글이 마지막 편이다.

이전 두 글에서, 유니티에서 깊이 텍스처를 사용하는 법에 대해 이야기 했었다.

이제, DepthTextureMode.DepthNormals를 통해 기본적으로 depth와 뷰 공간 normal을

하나로 압축해서 depth + normal 텍스처를 사용하는 법을 알아볼 것이다.


아래 이미지가 우리가 만들 효과다.

아래 이미지로보는 장면이 뷰 공간 노멀을 색상으로 표현한 것이고,

그 다음 이미지가 깊이값을 표현한 것이다.




Depth + Normal Texture

만약  Part 1에서 다룬 내용이 기억 난다면, 유니티에서 깊이 텍스처를 만들기 위해

Camera.depthTextureMode를 사용하는 거에 대해 알 것이다.

유니티 문서에 따라, 이 모드를 사용하기 위해 두가지 모드 값이 있다.

1. DepthTextureMode.Depth : 깊이 텍스처 .

2. DepthTextureMode.DepthNormals : 깊이와 뷰 공간 노멀을 하나의 텍스처에 압축.


우리느 이미 DepthTextureMode.Depth는 익숙한데, DepthTextureMode.DepthNormals 을 통해

어떻게 depth와 normal을 얻어올 수 있을까?


DecodeDepthNormal을 사용하면 된다.

이 함수는 UnityCG.cginc include 파일에 포함되어 있다.

UnityCG.cginc



define된 내용은 아래와 같다.


inline void DecodeDepthNormal( float4 enc, out float depth, out float3 normal )
{
   depth = DecodeFloatRG (enc.zw);
   normal = DecodeViewNormalStereo (enc);
}

이  함수에서 어떤 일이 벌어지는가?

함수의 인자가 세개인 것을 알 수 있다. (float4 enc, out float depth, out float3 normal)

기본적으로, enc로부터 DecodeFloatRG 함수를 실행하여 depth 값을 얻고 DecodeViewNormalStereo 함수를

실행하여 normal값을 얻는 것을 알수 있다.


코드상에서 아래와 같이사용하면 된다.


DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.scrPos.xy), depthValue, normalValues);

depthValue는 화면의 깊이값을 가지고 있는 float 값이며 normalValue는 뷰 공간의 노말을 가지는 float3 값이다.

첫번째 인자값인 tex2D(_CameraDepthNormalsTexture, i.scrPos.xy), 이는 무슨 일을 하는 것인가?

글쎄..일반적으로, _CameraDepthNormalsTexture의 변수 타입은 Sampler2D이다.

DecodeDepthNormal은 어떤 float4 타입의 값을 요구한다.

그래서 우리가 해야 할 일은, 주어진 샘플러로 texture look up을 수행하는 함수에 tex2d를 제공하는 것이다.


tex2d의 첫번째 인자로 sampler을 넣어주면 되는데 위에서는 _CameraDepthNormalsTexture를 넣어주었고,

두번째 인자는 해당 픽셀 값을 찾을 좌표값을 넣어주면 되는데 위 경우에는 화면의 좌표값을 넣어주었다.

하지만 i.scrPos가 float4라서 형식에 맞추기 위해 xy만 넣어주었다.


The 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
45
46
47
48
49
50
51
52
53
54
55
56
57
Shader "Custom/DepthNormals" {
Properties {
   _MainTex ("", 2D) = "white" {}
   _HighlightDirection ("Highlight Direction", Vector) = (1, 0,0)
}

SubShader {
Tags { "RenderType"="Opaque" }

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

sampler2D _CameraDepthNormalsTexture;
float _StartingTime;
float _showNormalColors = 1; //when this is 1, show normal values as colors. when 0, show depth values as colors.

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

//Our Vertex Shader
v2f vert (appdata_base v){
   v2f o;
   o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
   o.scrPos=ComputeScreenPos(o.pos);
   o.scrPos.y = 1 - o.scrPos.y;
   return o;
}

sampler2D _MainTex; 
float4 _HighlightDirection;

//Our Fragment Shader
half4 frag (v2f i) : COLOR{

float3 normalValues;
float depthValue;
//extract depth value and normal values

DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, i.scrPos.xy), depthValue, normalValues);
if (_showNormalColors == 1){
   float4 normalColor = float4(normalValues, 1);
   return normalColor;
} else {
   float4 depth = float4(depthValue);
   return depth;
}
}
ENDCG
}
}
FallBack "Diffuse"
}

노멀값이 뷰 공간으로부터의 값이기 때문에 카메라를 움직이면 노멀값, 그에 따른 컬러값이 바뀐다는것을 명심해라.



카메라에 스크립트 붙이기

이 스크립트를 "DepthNormals.cs"라고 부르자. 

스크립트의 내용은 아래에 있고 "E"버튼을 누르면 셰이더의 값을 바꾸게해 뒀다.

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
using UnityEngine;
using System.Collections;

public class DepthNormals : MonoBehaviour {

public Material mat;
bool showNormalColors = true;

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

// Update is called once per frame
void Update () {
   if (Input.GetKeyDown (KeyCode.E)){
      showNormalColors = !showNormalColors;
   }

   if (showNormalColors){
      mat.SetFloat("_showNormalColors", 1.0f);
   } else {
      mat.SetFloat("_showNormalColors", 0.0f);
   }
}

// Called by the camera to apply the image effect
void OnRenderImage (RenderTexture source, RenderTexture destination){
   //mat is the material containing your shader
   Graphics.Blit(source,destination,mat);
}
}

결론

이제 Depth + normal texture로 부텅 depth 와 normal을 어떻게 얻어오는지 알게되었다.

이 내용은 셰이더를 어떻게 작업해야 하는지에 대한 완벽한 지침서가 아니라는 것을 알아줬으면 좋겠다.

내가 몇일동안 유니티에서 vertex/fragment shader와 깊이 텍스처를 통한 작업에 대한

경험을 간단하게 축약해 놓은 것이다.

네가 개발하고 있는 프로젝트에 유용한 정보가 되었길 바란다.


추가내용.

Script에서 사용하는 DepthTextureMode.DepthNormals는 

화면크기의 32비트(8비트/채널) 텍스처를, 뷰 공간 법선이 RG, 깊이가 BA 채널에 인코딩된 형태로 빌드한다.

법선은 Stereographic Projection(스테레오 투영)을 사용하여 인코딩 되며 깊이는 두 8비트 채널로 압축된 16비트 값이다.


// 0~1 사이의 값을 채널당 8비트 RG채널로 인코딩/디코딩.

inline float2 EncodeFloatRG( float v )

{

float2 kEncodeMul = float2(1.0, 255.0);

float kEncodeBit = 1.0/255.0;

float2 enc = kEncodeMul * v;   // float2(depth, depth*255)

enc = frac (enc);                 // enc의 소수점만 구한다.

enc.x -= enc.y * kEncodeBit;

return enc;

}

inline float DecodeFloatRG( float2 enc )

{

float2 kDecodeDot = float2(1.0, 1/255.0);

return dot( enc, kDecodeDot );

}


// http://docs.unity3d.com/kr/current/Manual/SL-BuiltinIncludes.html

// http://www.gamedev.net/topic/630239-encode-float-to-rg-or-rgb/


DepthNormals.cs

DepthNormals.shader



Reference Link

- cg wiki frac

-