본문 바로가기
Unity - Surface Shader

4. Cubemap을 사용하여 반사매질을 표현해보자.

by SimonLee 2023. 11. 19.

큐브 맵은 Sky box에서 사용하기도 하지만,

큐브 맵 텍스처를 일반 오브젝트에 반사재질로 사용하면, 오브젝트에서 하늘에 반사되는 효과를 볼 수 있습니다.

Cube map 텍스처를 사용하여 건물을 렌더링 해봅시다.

 

먼저 렌더링 패스를 이해해 봅니다.

=======================================================

SurfaceShader 에서 Light를 표현할때,  #pragma 에 Lambert를 추가해도 되지만,

Custom Lighting을 하기 위해서는 Lighting + Custom API name 으로 #pragma 에 추가 한뒤,

함수 이름으로 정의를 하면 됩니다. 

 

아래 예제에서는 함수 명이 LightingTest 이기 때문에 , #pragma Test를 추가 해줬습니다.

추가를 하게 되면,

surf 함수가 먼저 실행이 되고, SurfaceOutput o 함수 인자가 다시 LighitngTest의 첫번째 인자 SurfaceOutput s 로 

들어가게 됩니다.

즉 파이프라인은 surf --> LighitngTest 순서로 진행이 됩니다.

 

=======================================================

 Reflection을 적용해봅니다.

 

준비 과정은 다음과 같습니다.

1) 에셋스토어에서 Skybox를 몇개 다운을 받구요

2) 하늘에 Skybox를 넣어봅니다.

 

===================================================

큐브맵은 

큐브맵의 구조는 정육면체에 사진 6개 붙인 형태의 타입니다.

 

정육면체의 전개도를 보시면

Z : 앞뒤, X : 왼쪽, 오른쪽, Y : 위 아래 입니다.

 

텍스처 샘플링과 큐브맵 샘플링은 텍스처에서 샘플링하는 방식이 동일하기 떄문에 

API도 비슷하게 사용이 됩니다.

- 큐브맵은 Property Type은 "Cube" 이며, CG Type은 "samplerCUBE" , 샘플링 함수는 texCUBE

- 텍스처 Property Type은 "2D" 이며, CG Type은 "sampler2D", 샘플링 함수는 tex2D

 

아래 1. 그림은 커다란 정육면체를 감싸고 있는 6개의 텍스처가 있으며,

정육면체 안에 있는 오브젝트의 하나의 픽셀 P를 나타낸 것 입니다.

 

큐브맵의 텍스처를 샘플링하는 방식은 specular 에서 refelection vector를 구하는 방식과 비슷한데요

 

카메라 방향벡터가 I라고 한다면, I와 지면의 P점에 반사된 방향벡터가 R 입니다.

방향벡터 R을 연장시켰을때 정육면체 큐브맵 텍스처와 만나는 부분을 Sampling Point라고 나타내었습니다.

해당 지점에 대응하는 큐브맵 텍스처 칼러값을 가져옵니다.

 

그렇기 때문에 맥락상 방향 벡터 p를 3차원 uv로 볼 수 있습니다.

I : View Vector

N : Normal Vector

R : 반사 Vector

R 벡터는 다음과 같이 표현 될 수 있다.

 

유도 과정은 다음과 같습니다.

큐브맵 reflection Vector 유도 과정

 

 

공식에서 필요한 값은 노멀벡터와 뷰 벡터 입니다. 

서피스 함수에서 뷰벡터를 가져오기 위해서는 Input Struct에 viewDir을 선언해서 사용하면 됩니다.

 

위 공식으로 사용해서 구현을 해도 되고, 이미 유니티에서 구현된 변수를 사용해도 됩니다.

구현된 변수이름은 worldRefl 입니다.

 

그리고 샘플링 된 컬러값은 빛에 영향받지 말아야 합니다.

그래서 Albedo 값에 넣지 말고 emissive에 컬러값을 넣어야 합니다.

Shader "Custom/Reflection"
{
    Properties
    {
        _Reflect("Reflect", Range(0, 1)) = 0
        _Color("_Color", Color) = (1, 1, 1, 1)
        _MainTex("Main Tex", 2D) = "white" {}
        _BumpMap("NormalMap", 2D) = "bump" {}
        _Cube("Cubemap", Cube) = "" {}
    }
        SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert noambient
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _BumpMap;
        samplerCUBE _Cube;
        float4 _Color;
        float _Reflect;
        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float3 worldRefl;
            float3 viewDir;
            INTERNAL_DATA
        };

        void surf(Input IN, inout SurfaceOutput o)
        {
            float3 refl = 2.0 * o.Normal * dot(o.Normal, IN.viewDir) - IN.viewDir;
            //float3 refl = IN.worldRefl;
            float4 re = texCUBE(_Cube, refl);
            o.Albedo = 0;
            o.Emission = re.rgb;
        }
        ENDCG
    }
        FallBack "Diffuse"
}

 

잘 나옵니다.

여기에 원래 빌딩 건물의 색상 Albedo + Normal을 적용해 봅니다.

Shader "Custom/Reflection"
{
    Properties
    {
        _Reflect("Reflect", Range(0, 1)) = 0
        _Color("_Color", Color) = (1, 1, 1, 1)
        _MainTex("Main Tex", 2D) = "white" {}
        _BumpMap("NormalMap", 2D) = "bump" {}
        _Cube("Cubemap", Cube) = "" {}
    }
        SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert noambient
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _BumpMap;
        samplerCUBE _Cube;
        float4 _Color;
        float _Reflect;
        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float3 worldRefl;            
        };

        void surf(Input IN, inout SurfaceOutput o)
        {
            float4 c = tex2D(_MainTex, IN.uv_MainTex);
            float4 re = texCUBE(_Cube, IN.worldRefl);
            float3 n = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            
            o.Albedo = c;
            o.Normal = n;
            o.Emission = re.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

 

 

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

worldRefl 벡터와  UnPackNormal을 같이 surf 함수 내에서 호출하면, 내부 API 에러가 발생한다고 하네요.

 

struct Input
{
    float2 uv_MainTex;
    float2 uv_BumpMap;
    float3 worldRefl;
    INTERNAL_DATA
};


o.Normal = UnpackNormal(tex2D (_BumpMap, IN.uv_BumpMap));        
float4 re = texCUBE(_Cube, WorldReflectionVector(IN, o.Normal));

 

WorldReflectionVector 함수를 사용하여 Normal 값을 사용하여 월드공간 반사 벡터를 구해옵니다.

Input 구조체에 있는 worldRefl 사용을 안하는 듯 보이지만 WorldReflectionVector 함수 내부에서 사용이 되기 때문에,

float3 worldRefl 을 반드시 선언해야 한다.

선언할때 INTERNAL_DATA 키워드를 같이 추가해야 한다.

 

worldRefl을 사용하지 않고 직접 구한 공식을 사용하면,  하늘이 제대로 표현되지 못하는 부분이 나온다.

그 이유는 WorldReflectionVector 함수내부를 까봐야 알 것 같은데.....

원인을 좀 파악은 나중에 하고..... WorldReflectionVector 함수를 일단 사용하기로 하자...

 

 

전체 코드입니다.

Shader "Custom/Reflection"
{
    Properties
    {
        _Reflect("Reflect", Range(0, 1)) = 0
        _Color("_Color", Color) = (1, 1, 1, 1)
        _MainTex("Main Tex", 2D) = "white" {}
        _BumpMap("NormalMap", 2D) = "bump" {}
        _Cube("Cubemap", Cube) = "" {}
    }
        SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Test noambient
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _BumpMap;
        samplerCUBE _Cube;
        float4 _Color;
        float _Reflect;
        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float3 worldRefl;
            float3 viewDir;
            INTERNAL_DATA
        };


        void surf(Input IN, inout SurfaceOutput o)
        {
            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
            float3 refl = WorldReflectionVector(IN, o.Normal); 
            //float3 refl = 2.0 * o.Normal * dot(o.Normal, IN.viewDir) - IN.viewDir;
            float4 re = texCUBE(_Cube, refl);

            o.Albedo = c.rgb * _Color * (1 - _Reflect);
            o.Emission = re.rgb * _Reflect;
            o.Alpha = c.a;
        }

        float4 LightingTest(SurfaceOutput s, float3 lightDir, float atten)
        {
            float ndotl = dot(s.Normal, lightDir) * 0.5 + 0.5;
            float4 final;
            final.rgb = s.Albedo * ndotl * _LightColor0.rgb;
            final.a = s.Alpha;
            return final;
        }
        ENDCG
    }
        FallBack "Diffuse"
}

 

'Unity - Surface Shader' 카테고리의 다른 글

5. 알파블렌딩과 컷아웃  (3) 2023.11.20
3. Vertex Shader를 사용하여 Toon Shading 해보기  (0) 2023.11.14
2. Blin - phong Specular + Rim Lighiting  (0) 2023.11.11
1. Hologram  (0) 2023.11.10
시작하면서...  (0) 2023.11.10