이것저것

[UNITY] 리플렉션 Reflection 본문

[유니티] Unity3D/Unity Shader

[UNITY] 리플렉션 Reflection

Patch_JA 2019. 6. 8. 23:17
728x90

 

 

Reflection

반사를 구현하기 위해서는 여러 방법들이 있다. 큐브 맵을 쓴다거나 포스트 프로세싱을 쓴다거나

리플렉션 프로브를 쓴다거나 등등 이 방식들은 모두 실시간 반사가 아닌 그럴듯하게 보이게

흉내 내는 것에 불과하다.

 

최근 실시간 레이 트레이싱(Ray Tracing)이 가능해지고 현실에 가깝게 흉내 낼 수 있게 되었다.

이미지 픽셀하나하나를 통과하는 광선(Ray)들을 역추적(Tracing)하는 방식이라고 하는데

쉽게 말해서 '계속해서 반사하는 빛을 최대한 현실처럼 똑같이 흉내 내는 것'이라고 생각하면 편하다. 그래도 아직까지는 하드웨어적 한계 때문에 조금 더 시간이 지나야 많이 사용될 듯하다.

 

어쨌든 이번 포스팅에서는 큐브 맵 리플렉션을 만들어볼 예정이다.

 

 

CubeMap 소개

큐브 맵은 오브젝트의 반사나 주변 환경을 캡처하는 데 사용되는데, 예를 들어

스카이박스(Skyboxes)와 환경 반사(Environment Reflections)등은 주로 큐브 맵을 사용한다.

 

 

CubeMap 이미지 변환

큐브맵을 생성할 때 가장 빠른 방법은 특별히 레이아웃 된 텍스쳐를 임포트 하는 것이다.

 

큐브 맵으로 변환할 텍스쳐를 선택 후 인스펙터 창에서 Texture Type

Default나 Normal Map 또는 Single Channel로 설정 후에 

Texture Shape에서 Cube로 설정해주면 Unity가 큐브 맵을 자동으로 설정해 준다.

 

 

CubeMap 추가

 

이제는 너무나도 익숙한 기본 Lambert 셰이더를 준비해준다.

 

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
Shader "Custom/Post_Work1"
{
    Properties
    {
        _MainTex ("Albedo (RGB)"2D= "white" {}
    }
    SubShader
    {
 
        Tags { "RenderType"="Opaque" }
 
        CGPROGRAM
        #pragma surface surf Lambert 
        #pragma target 3.0
 
        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"
}
 

 

CubeMap을 추가하기 위해서는 몇 가지 새로운 것을 사용한다.

Properties에서 텍스쳐를 추가할 때 2D가 아닌 cube를 사용하며,

SubShader에서 sampler2D가 아닌 samplerCUBE를 사용한다.

 

그리고 Input 구조체에서는 float2 uv정보가 아닌 유니티 내장 변수인

float3 worldRefl 변수를 가져온 후 Emission에 texCUBE에 추가시켜준다.

 

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
Shader "Custom/Post_Work1"
{
    Properties
    {
        _MainTex("Albedo (RGB)"2D= "white" {}
        _CubeMap("CubeMap"cube= "" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
 
        CGPROGRAM
        #pragma surface surf Lambert 
        #pragma target 3.0
 
        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;
            o.Emission = texCUBE(_CubeMap, IN.worldRefl);
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 

 

Input구조체에서 float3 worldRefl; 를 가져온 이유는 월드 반사 벡터를 가져와야 하기 때문인데,

texCUBE()에는 결국 큐브 맵 텍스쳐와 월드 좌표를 가진 반사 벡터를 넣은 것이다.

 

큐브 맵을 Albedo가 아닌 Emission에 추가한 이유는

Albedo는 빛의 영향을 받게 되어 반사를 하게 되는데, 거울에 비추는 것은 그림자가 지지 않아서 때문이다.

 

 

좌측 UnpackNormal을 이용한 탄젠트 좌표계는 각 픽셀에 고유 좌표를 가지고 있어서 어느 방향을

돌려도 노말 맵 텍스처 그대로 모습을 보여주는 반면에

우측 worldRefl을 이용한 월드 좌표계는 월드 기준 방향으로 있기 때문에 같이 움직이지 않는다.

 

 

CubeMap 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
Shader "Custom/Post_Work1"
{
    Properties
    {
        _MainTex("Albedo (RGB)"2D= "white" {}
        _NormalMap("NormalMap"2D= "bump" {}
        _CubeMap("CubeMap"cube= "" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
 
        CGPROGRAM
        #pragma surface surf Lambert 
        #pragma target 3.0
 
        sampler2D _MainTex, _NormalMap;
        samplerCUBE _CubeMap;
 
        struct Input
        {
            float2 uv_MainTex, uv_NormalMap;
            float3 worldRefl;
        };
 
        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            
            o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
 
            o.Albedo = c.rgb;
            o.Emission = texCUBE(_CubeMap, IN.worldRefl);
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 

노말맵을 추가시켰더니 에러가 난다. 에러 내용을 대충 훑어보면

 

Shader error in 'Custom/Post_Work1': Surface shader Input structure needs 
INTERNAL_DATA for this WorldNormalVector or WorldReflectionVector usage at
line 200 (on d3d11)

 

INTERNAL_DATA , WorldNormalVector, WorldReflectionVector 관련해서

키워드를 얻을 수 있어서 유니티 홈페이지에서 찾아보았다.

 

정확히는 무슨 말인지는 모르겠지만 worldRefl을 사용할 때 노말 맵을 쓸 경우

WorldReflectionVector를 사용하는데, 픽셀당 반사 벡터를 사용하기 위해

 Input구조체에서 INTERNAL_DATA를 선언해서 뭔가 컴파일해주는 듯..?

 

좀 더 쉽게 설명하면 탄젠트 좌표계를 월드 좌표계로 변환시켜주어야 하는데

그 과정에서 변환이 되지 않아 에러가 났던 것이고 INTERNAL_DATA를 통해서

변환해주는 듯하다. (자세한 것은 모르겠다..)

 

유니티가 PBR로 바뀌면서 이런 식으로 큐브 맵 사용이 변경되었다 한다.

그러면 일단 한번 수정해보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Input
{
    float2 uv_MainTex, uv_NormalMap;
    float3 worldRefl;
    INTERNAL_DATA
};
 
void surf (Input IN, inout SurfaceOutput o)
{
    fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    
    o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
    o.Emission = texCUBE(_CubeMap, WorldReflectionVector(IN, o.Normal));
    o.Albedo = c.rgb;
    o.Alpha = c.a;
}
 

<우측이 노말 사용상태>

 

헬멧 MaskMap 추가

만들었던 큐브맵 머티리얼을 헬멧 모델에 적용하여

금속 재질인 것처럼 표현하고 마스크 맵을 적용하여 부분적으로 적용되도록 해보겠다.

 

 

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
Shader "Custom/Post_Work1"
{
    Properties
    {
        _MainTex("Albedo (RGB)"2D= "white" {}
        _NormalMap("NormalMap"2D= "bump" {}
        _MaskMap("MaskMap"2D= "bump" {}    
        _CubeMap("CubeMap"cube= "" {}
 
        [HDR]_Color("Color", color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
 
        CGPROGRAM
        #pragma surface surf Lambert 
        #pragma target 3.0
 
        sampler2D _MainTex, _NormalMap, _MaskMap;
        samplerCUBE _CubeMap;
 
        struct Input
        {
            float2 uv_MainTex, uv_NormalMap, uv_MaskMap;
            float3 worldRefl;
            INTERNAL_DATA
        };
 
        float4 _Color;
 
        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            fixed4 m = tex2D(_MaskMap, IN.uv_MaskMap);
 
            o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
            o.Emission = texCUBE(_CubeMap, WorldReflectionVector(IN, o.Normal)) * m.a * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 

 

저번 포스팅에서 해봤던 것처럼

간단하게 마스크 맵과 컬러도 수정할 수 있도록 하였다.

 

 

Reflection Probe 사용하기

리플렉션 프로브란 해당 오브젝트를 배치하여 구역 내에 주변 static오브젝트들을

캡처하여 큐브 맵 데이터로 저장하는 기능이다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void surf (Input IN, inout SurfaceOutput o)
{
    fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    fixed4 m = tex2D(_MaskMap, IN.uv_MaskMap);
 
    o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
 
    fixed3 wr = WorldReflectionVector(IN, o.Normal);
    //fixed3 p = texCUBE(_CubeMap, wr);
    fixed3 r = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, wr) * unity_SpecCube0_HDR.r;
    
    o.Emission = r * m.a;
    o.Albedo = c.rgb;
    o.Alpha = c.a;
}
 

유니티에서 리플렉션 프로브로 큐브 맵을 받기 위해서는 UNITY_SAMPLE_TEXCUBE()를 사용해야 한다.

unity_SpecCube0, unity_SpecCube0_HDR은 리플렉션 프로브 데이터를 가지고 있으며,

unity_SpecCube가 아니라 unity_SpecCube0이 붙은걸 보면 뒤에 1도 존재한다는 것도 알 수 있고

HDR도 1이 존재한다. 

 

이를 설명하기 위해서 오브젝트 Mesh Renderer 인스펙터 부분을 보면 Light Probes에

Blend Probes라는 항목이 존재하는데, 리플렉션 프로브 영역으로 이동할 때 리플렉션 이미지가 갑자기 바뀌는 것을 방지하기 위해 블렌딩 해주는 것이라고 한다.

 

 

 

 

 

Comments