이전에 Diffuse(난반사), Specular(정반사), Rim Light 등 현실에 존재하는 효과를 통해 더 퀄리티 있고 현실적인 그래픽을 구현 할 수 있었습니다.  

Reflection(반사) 효과도 그래픽을 더욱더 퀄리티 있게 표현 할 수 있게 되며 무엇 보다 유리, 금속 등 반사율이 높은 재질을 표현 할 수 있습니다.

 

주변 오브젝트, 빛을 반사하는 시스템은 매우 무겁습니다, 그래서 1년 전 E3 2018에서 실시간 레이트레이싱이 등장하기 이전에는 Real Time Reflection(반사)는 불가능한 일이 였으며 이를 가능하게 했던 것은 실시간 처럼 보이게 구현된 Baked Reflection(반사)였습니다, 

 

이번 포스팅에서는 Unity에서 Baked Reflection을 구현을 해보겠습니다.

 

 

 

 

CubeMap Reflection

첫 번째 Baked Reflection 기법은 CubeMap Reflection입니다.

CubeMap이란 다른 말로 Skybox라고도 하며 3D 세상의 배경을 의미합니다, 

Reflection(반사)의 핵심은 눈으로 보는 오브젝트 표면이 반사되어 배경 어딘가를 보는 것입니다.

 

그래서 CubeMap Reflection은 가장 단순하게 CubeMap텍스처의 UV에 카메라가 물체를 보는 반사 각도를 계산해서 물체 표면에 CubeMap을 출력하는 것입니다.

 

 

1.  CubeMap Texture 만들기

엔진상에서 텍스처 설정의 Texture Shape 속성을 Cube로 바꾸면 CubeMap전용 텍스처로 변환 됩니다.

이 텍스처를 이용해서 실제 월드의 SkyBox를 변경 할 수 있고 이번에 제작할 쉐이더의 CubeMap텍스처롤 활용 할 수 있습니다.

 

 

2 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
Shader "Custom/hgfedsh"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert 
 
        sampler2D _MainTex;
 
        struct Input
        {
            float2 uv_MainTex; 
        };
 
        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;        
            o.Alpha = c.a; 
        }
 
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs

 

우선 기본 Lambert 쉐이더를 세팅합니다.

 

Properties 인터페이스에 CubeMap 텍스처를 받을 수 있는 변수를 작성합니다.

 

인터페이스와 연동할 변수의 자료형은 samplerCUBE입니다.

 

Input 구조체에 작성된 float3 worldRefl은 Surface Shader에서 자동으로 View -> Normal의 월드 반사 벡터를 계산한 데이터입니다,

 

마지막으로 surf함수 작성 부분입니다, 지금 만드는 쉐이더는 순전히 CubeMap을 반사하는 쉐이더이므로 Albedo를 사용하지 않아 주석처리 했습니다.

tex2D와 마찬가지로 texCUBE함수를 통해 _CubeMap텍스처 변수의 값을 float4로 가져오는 과정입니다, 두 번째 인자에서는 uv값을 집어넣듯이 World Reflection Vector데이터인 IN.worldRefl을 넣었습니다.

 

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
Shader "Custom/hgfedsh"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _CubeMap("CubeMap", cube) = "" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert 
 
        sampler2D _MainTex;
        samplerCUBE _CubeMap;
 
        struct Input
        {
            float2 uv_MainTex; 
            float3 worldRefl;
        };
 
        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            //o.Albedo = c.rgb;            //! 반사에는 물리적으로 그림자 영향받지 않으므로 Albedo 주석
            o.Alpha = c.a; 
 
            float4 fCube = texCUBE(_CubeMap, IN.worldRefl);
            o.Emission = fCube.rgb;
        }
 
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs

 

3 Normal Map 영향 받는 반사 처리

 

-INTERNAL_DATA

Normal Map의 영향 받는 반사처리를 하기 위해서는 탄젠트 좌표계 기반으로 얻은 월드 좌표계를 사용해서 반사 벡터를 얻어야 합니다, 기존 Surface Shader에서 노멀맵을 처리할 때는 자동으로 로컬 -> 월드 좌표까지 변환이 되서 사용되었습니다, 그래서 Surface Shader에서는 따로 탄젠트 좌표계 기반 월드 좌표를 얻을 방법이 없습니다, 이럴때 INTERNAL_DATA라는 메크로를 추가해야 합니다.

 

 

 

Normal Map추가를 위해 인터페이스 변수를 추가하고 SubShader안에 sampler2D변수를 작성합니다.

 

Input struct에 INTERNAL_DATA 메크로를 추가합니다.

 

texCUBE함수의 두 번째 인자에는 worldRefl대신 WorldReflectionVector함수를 넣습니다, 이 함수는 INTERNAL_DATA 메크로에 포함된 함수이며 o.Normal의 탄젠트 좌표계를 월드로 변환해 줍니다.

 

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/hgfedsh"
{
    Properties
    {
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _CubeMap("CubeMap", cube) = "" {}
        _BumpMap("BumpMap", 2D) = "bump"
    }
        SubShader
    {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert 
 
        sampler2D _MainTex;
        sampler2D _BumpMap;
        samplerCUBE _CubeMap;
 
        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float3 worldRefl;
            INTERNAL_DATA
        };
 
        void surf(Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
            //o.Albedo = c.rgb;            //! 반사에는 물리적으로 그림자 영향받지 않으므로 Albedo 주석
            o.Alpha = c.a;
 
            float3 fNormal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            o.Normal = fNormal;
            
            float4 fCube = texCUBE(_CubeMap, WorldReflectionVector(IN, o.Normal));
            o.Emission = fCube.rgb;
        }
 
        ENDCG
    }
        FallBack "Diffuse"
}
 
cs

 

 

Reflection Probe 반사

Reflection Probe란?

Unity 엔진상에서 Prob를 배치한 기준으로 주변의 Static오브젝트들을 캡쳐하여 CubeMap데이터로 저장하는 기능입니다, 이전 CubeMap Reflection은 따로 CubeMap 텍스처를 넣어서 반사텍스처에 활용 했다면 이번엔 Reflection Probe로 주변 환경, Static오브젝트를 캡처한 CubeMap 텍스처를 반사텍스처로 활용 할 수 있습니다.

 

 

Hierarchy->Create->Light->Reflection Probe를 통해 월드에 오브젝트를 생성할 수 있으며 캡처를 원하는 곳에 배치 후, 해상도, 그림자, Mask등 설정 후 Bake버튼을 누르면 주변이 캡처됩니다.

 

 

-Reflection Probe 반사 구현

술집 내부에 이 권총 오브젝트에 주변 환경을 반사하는 쉐이더를 입혀 총기의 메탈느낌을 살려보겠습니다.

 

우선 필요한 텍스처 변수는 위 3가지 입니다.

 

Input 구조체에는 위 3개의 텍스처 uv 변수와 CubeMap반사를 위한 worldRefl(월드 반사벡터)와 INTERNAL_DATA 메크로를 추가합니다.

 

Emission에 넣는 데이터가 Reflection Probe데이터 입니다.

unity_SpecCube0, unity_SpecCube0_HDR은 빌트인 쉐이더이고 Reflection Probe 데이터를 가지고 있습니다, 이 데이터를 쉐이더에서 사용 할 수 있게 float 변수로 바꾸는 건 UNITY_SAMPLE_TEXCUBE입니다.

(이 부분의 정보는 유니티 문서에서도 자세하게 나와 있지 않아 이 정도 정보밖에 없네요)

 

마지막으로 커스텀 라이트 함수를 통해 Lambert Diffuse와 Blinn Phong Specular를 직접 구현했습니다.

 

 

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
Shader "Custom/ReflProbShader"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}        //! Diffuse Texture
        _BumpMap("Bumpmap", 2D) = "bump" {}                //! Normal Texture
        _SpecMap("Specular Map", 2D) = "black" {}        //! Specular Map Texture
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        CGPROGRAM
        #pragma surface surf _CatDark 
 
        sampler2D _MainTex;
        sampler2D _BumpMap;
        sampler2D _SpecMap;
 
        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float2 uv_SpecMap;
 
            float3 worldRefl;
            INTERNAL_DATA
        };
 
        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = c.a;
 
            float3 n = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            o.Normal = n;
 
            float3 fWRV = WorldReflectionVector(IN, o.Normal).xyz;
            o.Emission = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, fWRV).rgb * unity_SpecCube0_HDR.r;    //! Reflection Probe CubeMap 받는 함수
 
            float4 spec = tex2D(_SpecMap, IN.uv_SpecMap);
            o.Specular = spec.g;
        }
 
        float4 Lighting_CatDark(SurfaceOutput s, float3 lightDir, float3 viewDir, float atten)
        {
            float fNDotL = saturate(dot(s.Normal, lightDir));
 
            float3 fDiffuse = s.Albedo * _LightColor0.rgb * (fNDotL * atten);        //! Lambert DIffuse
 
            float3 fHalfVector = 0.0f;                                                //! Blinn Phong Sepcular
            float fBPSpecular = 0.0f;
            if (fNDotL > 0)
            {
                fHalfVector = normalize(lightDir + viewDir);
                fBPSpecular = saturate(dot(s.Normal, fHalfVector));
                fBPSpecular = pow(fBPSpecular, 5.0f);
            }
 
            float4 fFinalColor;
            fFinalColor.rgb = (fDiffuse * (1 - s.Specular)) +(fBPSpecular * s.Emission);
            fFinalColor.a = s.Alpha;
 
            return fFinalColor;
        }
 
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs

 


WRITTEN BY
CatDarkGame
Technical Artist dhwlgn12@gmail.com

,