Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
Archives
Today
Total
관리 메뉴

nomad-programmer

[CG/Unity] Matcap (메터리얼 캡쳐) 본문

CG/Unity

[CG/Unity] Matcap (메터리얼 캡쳐)

scii 2022. 2. 6. 18:21

Matcap이란 Material Capture의 준말로, Ramp Texture와 비슷하게 이미지로 조명을 대신하는 방식 중 하나이다.

Matcap은 라이트의 영향을 최소화해야 하는 모바일 게임에서 최적화를 위해 자주 사용되는 '가짜' 라이팅 방식이며, ViewDir을 기반으로 하므로 카메라를 회전하지 않는 상황에서 좋은 효과를 볼 수 있는 기능이다. 이 방식을 사용하면 라이트를 '전혀 사용하지 않고'도 조명을 받은 것 같은 효과를 낼 수 있다.

Shader "Custom/matcap"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Matcap ("Matcap", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Nolight noambient

        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _Matcap;

        struct Input
        {
            float2 uv_MainTex;
            float3 worldNormal;
        };

        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Alpha = c.a;
        }

        float4 LightingNolight(SurfaceOutput s, float3 lightDir, float atten){
            return float4(0, 0, 0, s.Alpha);
        }

        ENDCG
    }
    FallBack "Diffuse"
}

아주 기본적인 코드 구성이다. 이제 여기에서부터 맷캡을 적용하자.

맷캡 텍스처 입력

저 셀 셰이딩같은 느낌의 MatCap 텍스처가 조명이 될 것이다. 저 텍스처를 적용하기 위한 UV를 연산하는 것이 핵심이다.

IN.worldNormal은 world 좌표계이기 때문에, 이 normal은 카메라를 돌려도 카메라와 상관없이 고정되어 있다. 그래서 이것은 view 좌표계로 변환해야 한다.

worldNormal에다가 view 좌표계 즉, view Matrix를 구해서 view 좌표계의 Normal로 만들어 보자.

float3 viewNormal = mul((float3x3)UNITY_MATRIX_V, IN.worldNormal.rgb);

UNITY_MATRIX_V는 유니티 안에 내장된 'view 좌표계' 매트릭스이다. mul 함수는 매트릭스 곱셈을 위한 함수이다.

이렇게 하면, normal이 바뀐다. 카메라를 돌리는 것에 따라 그 방향이 따라온다. 즉, view 좌표계의 normal이 된 것이다. 아무리 카메라를 돌려도 왼쪽이 파랗다는 것을 볼 수 있다.

view normal을 구했으니 UV를 구하면되는데, view normal의 x, y를 u, v로 사용하면 된다. 그런데 normal 값 그대로를 사용하면 안된다. 그 이유는 Normal은 180도를 표현해야 하는데 0~1 로는 90도밖에 표현하지 못하기 때문이다.

-1~1 이어야 180도가 표현 가능하다. 즉, NormalMap은 일반적인 텍스쳐이기 때문에 0~1로 되어 있으니, NormalMap에 *2-1을 연산해서 0~1을 -1~1로 만들어 주는 것이다. 이것이 NormalMap이 어정쩡하게 파란색인 이유이다. NormalMap에 *2-1 연산을 해주면 확실히 새파랗게 변하게 된다.

float3 viewNormal = mul((float3x3)UNITY_MATRIX_V, IN.worldNormal.rgb);
float2 matCapUV = viewNormal.xy * 0.5 + 0.5;

이제 이 UV를 이용하여 Matcap 텍스쳐를 연산해주자.

Shader "Custom/matcap"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Matcap ("Matcap", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Nolight noambient

        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _Matcap;

        struct Input
        {
            float2 uv_MainTex;
            float3 worldNormal;
        };

        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            float3 viewNormal = mul((float3x3)UNITY_MATRIX_V, IN.worldNormal.rgb);
            float2 MatcapUV = viewNormal.xy * 0.5 + 0.5;
            o.Emission = tex2D(_Matcap, MatcapUV) * c.rgb;
            o.Alpha = c.a;
        }

        float4 LightingNolight(SurfaceOutput s, float3 lightDir, float atten){
            return float4(0, 0, 0, s.Alpha);
        }

        ENDCG
    }
    FallBack "Diffuse"
}

viewNormal을 이용한 텍스쳐링


Normal을 사용하는 Matcap을 만들기 위해서는 NormalMap이 사용된 worldNormal을 만들어야 한다. worldNormal과 UnpackNormal을 함께 사용하는 상황이 만들어지는데, 이런 경우 서피스 셰이더에서 사용할 수 있는 함수는 worldNormalVector 이다.

Shader "Custom/matcap"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _BumpMap ("Normalmap", 2D) = "bump" {}
        _Matcap ("Matcap", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Nolight noambient

        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _BumpMap;
        sampler2D _Matcap;

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

        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            float3 worldNorm = WorldNormalVector(IN, o.Normal);
            float3 viewNormal = mul((float3x3)UNITY_MATRIX_V, worldNorm);
            float2 MatcapUV = viewNormal.xy * 0.5 + 0.5;
            o.Emission = tex2D(_Matcap, MatcapUV) * c.rgb;
            o.Alpha = c.a;
        }

        float4 LightingNolight(SurfaceOutput s, float3 lightDir, float atten){
            return float4(0, 0, 0, s.Alpha);
        }

        ENDCG
    }
    FallBack "Diffuse"
}

NormalMap을 viewNormal로 변환 후 Matcap 텍스쳐 적용한 모습

 

Comments