이전 포스팅에서는 커스텀 라이트 모델을 만들기 위해 벡터 기본 이론과 구형 라이트모델인 Lambert, BlinnPhong을 알아 봤다. 이번 포스팅에서는 Unity에서 준비된 구형 라이트모델을 사용하는게 아닌 Surface Shader내에서 자체

라이트 모델을 만들어 Diffuse, Specular를 직접 구현하고 추가로 Fresnel까지 구현하여 하나의 쉐이더를 완성해보겠다.

 

 

Custom Light 만들기 

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

 

이전 포스팅을 참고해서 우선 Lambert 쉐이더를 제작한다. 

결과를 확인하면 단순 조명효과만 받는 Diffuse가 구현된 Lambert 라이트 쉐이더를 확인할 수 있다.

이제 위 기본 코드에 커스텀 라이트 모델 함수를 만들어서 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
33
Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        
        CGPROGRAM
        
        #pragma surface surf _CatDarkLight noambient
 
        sampler2D _MainTex;
 
        struct Input {
            float2 uv_MainTex;
        };
 
        fixed4 _Color;
 
        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
 
        float4 Lighting_CatDarkLight(SurfaceOutput s, float3 lightDir, float3 viewDir, float attan)
        {
 
            return float4(1.0f, 0.0f, 0.0f, 1.0f);
        }
 
        ENDCG
    }
cs

커스텀 라이트모델 함수 _CatDarkLight를 만든 후 내용에는 빨간컬러 값만 반환하도록 코드를 작성하고

#pragma 컴파일 지시자에서 Lambert에서 _CatDarkLight로 커스텀 라이트모델로 변경했다.

 

커스텀 라이트모델 함수 제작법

커스텀 라이트 모델 함수 만들 때는 기본적으로 float4 Lighting  까지는 예약어 처럼 선언하고 이후 

_CatDarkLight는 변수 이름 짓는 개념으로 마음대로 지어도 된다. 여기서 지어진 이름은 

위 처럼 컴파일 지시자에 이전에 Lambert를 불러오듯이 커스텀 라이트 모델 함수에서 지은 이름을 넣으면 

해당 라이트 모델 함수를 사용하게 된다.

 

이후 마지막 인자 값은 용도에 따라 다른 인자 값을 사용할 수 있으며 이 포스팅에서 사용하는 인자값을 아래와 같다.

SurfaceOutput s  :  surf함수에서 입력된 구조체를 받아오는 인자

float3 lightDir     :  월드상의 라이트 벡터 인자

float3 viewDir     :  월드상의 카메라(뷰) 벡터 인자

float attan         :   월드상의 라이트 감쇠량 인자

 

이로서 커스텀 라이트 모델을 제작이 완료되었고 관련 문서링크는 아래에 있다.

이제 커스텀 라이트에서 Lambert, BlinnPhong들 유니티에서 기본으로 제공하는 라이트 모델을 자체 제작 해보겠다.

 

커스텀 라이트 모델 문서 : https://docs.unity3d.com/kr/2017.4/Manual/SL-SurfaceShaderLighting.html

 

표면 셰이더의 커스텀 조명 모델 - Unity 매뉴얼

표면 셰이더를 작성할 때 사용자가 표면의 프로퍼티(알베도 컬러 및 노멀 등)를 기술하면 Lighting Model 이 조명의 상호작용을 연산합니다.

docs.unity3d.com

 

Lambert 라이트 모델 제작

1. 기본 Lambert 공식 구현

1
2
3
4
5
6
float4 Lighting_CatDarkLight(SurfaceOutput s, float3 lightDir, float3 viewDir, float attan)
{            
    float fNDotL = dot(s.Normal, lightDir);
 
    return fNDotL;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

이전에 만든 커스텀 라이트 함수에 Lambert 공식을 사용하여 단순 Diffuse 연산하는 쉐이더를 작성했다.

공식은 단순히 dot(NormalVector, LightVector) 이며 이것을 흔히 NDotL이라고 부른다.

 

Lambert 공식의 원리는 간단하다 빛이 비추는 방향의 값은 1에 가깝고 그 반대는 0에 가까워져 음영처리가 되는것이다.

https://gamedevforever.com/220

위 처럼 구체에 핀처럼 박힌것이 노말벡터이고 라이트벡터가 더해져 dot(내적)연산을 하면

위 사진과 같은 형태로 값이 나오는 것을 확인 할 수 있고 그 결과 빛에 의한 물체에 음영효과가 나타났다.

 

2. 텍스처 추가하기

1
2
3
4
5
6
7
8
9
10
float4 Lighting_CatDarkLight(SurfaceOutput s, float3 lightDir, float3 viewDir, float attan)
{
    float fNDotL = dot(s.Normal, lightDir);
 
    float4 fFinalColor;
    fFinalColor.rgb = s.Albedo * fNDotL;
    fFinalColor.a = 1.0f;
 
    return fFinalColor;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

이전에 surf함수에서 Albedo에 텍스처를 넣었는데 그 구조체 정보를 커스텀라이트함수에서 받아서 사용 할 수 있다.

그래서 위 처럼 SurfaceOutput s에의 Albedo와 Lambert 공식의 fNDotL을 연산하여 위 처럼 텍스처가 씌어진 채로 

음영처리된 쉐이더가 완성됬다.

 

3. 라이트 감쇠 효과 적용

이전에 Directional Light를 이용해서 결과물을 확인해서 빛의 방향만 확인 가능했지 감쇠효과는 확인하지 못했다.

하지만 이번엔 Point Light를 설치하니 위 움짤처럼 단순히 범위에만 들어오면 라이트가 켜졌다 꺼졌다하는 현상이 있다.

 

현재 포스팅에서 좀 위로 올려보면 커스텀라이트함수의 인자 값중에 float attan을 확인 할 수 있다.

attanAttenuation의 약자로 감쇠라는 뜻이다.

 

attan 인자값을 곱해주니 위 처럼 제대로 빛의 감쇠 효과가 구현이 되었다.

 

4. NDotL 응용 - saturate

이번엔 좌측에 DirectionalLight, 우측에 PointLight로 두 개의 조명을 동시에 비추었다. 

하지만 무언가 어색하다. 그 이유는 간단하다.

단순 NDotL공식을 사용하면 어두운 부분이 0값이 아닌 -1값이 된다. 그래서 반대쪽에서 라이트를 비춰 명도 값을 

올려도 -1부터 올라가기에 제대로 표현이 안되고 있는 것이다.

이것을 해결하는 방법은 saturate(x) 함수를 이용하여숫자 값을 0~1로 표현하면 된다.

 

1
2
3
4
5
6
7
8
9
10
    float4 Lighting_CatDarkLight(SurfaceOutput s, float3 lightDir, float3 viewDir, float attan)
    {
        float fNDotL = saturate(dot(s.Normal, lightDir));
 
        float4 fFinalColor;
        fFinalColor.rgb = s.Albedo * fNDotL * attan;
        fFinalColor.a = 1.0f;
 
        return fFinalColor;
    }
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

5. _LightColor0 - 라이트 컬러 적용

위 과정까지 진행 했지만 우측 포인트라이트는 빨강색 조명으로 세팅되어 있는데 구체는 빨간빛이 나지 않는다.

원인은 단순히 어디에도 색상을 연산하는 코드는 없고 단순히 NDotL로 명도 계산밖에 하지 않았기 때문이다.

현재 물체가 받고 있는 라이트의 색상을 가져오는 방법은 float4 _LightColor0 라는 빌트인 쉐이더 전역변수를

통해 사용 할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
float4 Lighting_CatDarkLight(SurfaceOutput s, float3 lightDir, float3 viewDir, float attan)
{
    float fNDotL = saturate(dot(s.Normal, lightDir));
 
    float4 fFinalColor;
    fFinalColor.rgb = s.Albedo * fNDotL * attan * _LightColor0.rgb;
    fFinalColor.a = 1.0f;
 
    return fFinalColor;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

빌트인 쉐이더 문서 : https://docs.unity3d.com/kr/current/Manual/SL-UnityShaderVariables.html

 

빌트인 셰이더 변수 - Unity 매뉴얼

Unity는 셰이더에 사용할 수 있는 여러 빌트인 전역 변수를 제공합니다. 예를 들어 현재 오브젝트의 변환 매트릭스, 광원 파라미터, 현재 시간 등이 있습니다. 이 변수는 다른 변수와 마찬가지로 셰이더 프로그램에서 사용하며 유일한 차이는 변수를 선언할 필요가 없다는 점입니다. 이 변수는 자동으로 포함되는 UnityShaderVariables.cginc 인클루드 파일에 모두 선언되어 있습니다.

docs.unity3d.com

Half-Lambert 라이트 모델 제작

https://developer.valvesoftware.com/wiki/File:Alyx_lambert_half_lambert.jpg

이번엔 Lambert 라이트모델의 개량형인 Half-Lambert를 구현해 볼것이다.

Half-Lambert란 하프라이프2를 제작한 Value회사에서 공개한 하프라이프2에서 사용된 Lambert 공식이다.

 

원리은 매우 간단하다. 위 사진의 빨간 그래프같이 -1~1로 값이 오가는 기존 램버트에서

saturate로 강제로 자르는 것이 아닌 특정 공식을 추가하여 파란 그래프처럼 0~1로 부드러운 그래프로 

만드는 것이다. 

 

해당 공식은 이와 같다.

(dot(NormalVector, LightVector) * 0.5f + 0.5f

 

비교해보면 전체적으로 Half-Lambert가 명암이 부드럽고 물체가 밝아 보인다. 

이는 GI 연산이 없어도 충분히 물체가 밝아 보인다는 뜻이다.

 

BlinnPhong - Specular 효과 추가

https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model

Half-Lambert를 통해 Diffuse효과는 완성했고 다음 Specular효과를 넣을 차례이다.

Specular공식은 크게 Phong과 Blinn-Phong이 존재하고 당연히 이번에도 유니티에서 준비된 라이팅 모델을

사용하는 것이 아닌 커스텀 라이팅 모델에 직접 구현해서 이전에 구현한 Half-Lambert와 합칠 것이다.

 

Blinn-Phong 공식은 이와 같다

dot(Normal-Vector, Half-Vector)

여기서 Half-Vector란 LightVector와 ViewVector 합의 정규화다.

그래서 결국 아래와 같이 정리된다.

Half-Vector = normalize(LightVecotr + ViewVector);

Specular = dot(Normal-Vector, Half-Vector);

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
float4 Lighting_CatDarkLight(SurfaceOutput s, float3 lightDir, float3 viewDir, float attan)
{
    float fNDotL = (dot(s.Normal, lightDir)*0.5f+0.5f);
    float3 fHalfVector = 0.0f;
    float fBPSpecular = 0.0f;
 
    if (fNDotL > 0)
    {
        fHalfVector = normalize(lightDir + viewDir);
        fBPSpecular = saturate(dot(s.Normal, fHalfVector));
        fBPSpecular = pow(fBPSpecular, 60.0f);
    }
 
    float4 fFinalColor;
    fFinalColor.rgb = (s.Albedo * fNDotL * attan * _LightColor0.rgb) +
                              (fBPSpecular * _LightColor0);
    fFinalColor.a = 1.0f;
 
    return fFinalColor;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

-Normal Map 텍스처 추가

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
Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _BumpMap("Normal Map", 2D) = "Bump" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        
        CGPROGRAM
        
        #pragma surface surf _CatDarkLight noambient
 
        sampler2D _MainTex;
        sampler2D _BumpMap;
 
        struct Input {
            float2 uv_MainTex;
            float2 uv_BumpMap;
        };
 
        fixed4 _Color;
 
        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
 
            float3 fNormal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            o.Normal = fNormal;
        }
 
        float4 Lighting_CatDarkLight(SurfaceOutput s, float3 lightDir, float3 viewDir, float attan)
        {
            float fNDotL = (dot(s.Normal, lightDir)*0.5f+0.5f);
            float3 fHalfVector = 0.0f;
            float fBPSpecular = 0.0f;
 
            if (fNDotL > 0)
            {
                fHalfVector = normalize(lightDir + viewDir);
                fBPSpecular = saturate(dot(s.Normal, fHalfVector));
                fBPSpecular = pow(fBPSpecular, 60.0f);
            }
 
            float4 fFinalColor;
            fFinalColor.rgb = (s.Albedo * fNDotL * attan * _LightColor0.rgb) +
                              (fBPSpecular * _LightColor0);
            fFinalColor.a = 1.0f;
 
            return fFinalColor;
        }
 
        ENDCG
    }
    FallBack "Diffuse"
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

Specular효과를 구현했으니 더 이쁘게 표현되도록 노멀맵 텍스처를 적용했다.

 

Fresnel - Rim Light 효과 추가

https://www.slideshare.net/agebreak/0806-rim-lighting

Rim light란 역광현상이라는 뜻이다. 오브젝트 뒤쪽에 존재하는 라이팅이 오브젝트에 가려져 외곽선 부분만 빛이 세는 효과이다. Fresnel이란 공식의 이름으로 간단하게 빛이 물체에 도달했을때 같은 양의 빛이라고 해도 시야각에 따라 일정량은 반사하고 일정량은 굴절시키는 공식이며 Fresnel 구현을 통해 Rim light를 만들 수 있다.

 

 -viewDir을 이용한 장난

이전에 Lambert 공식은 라이트방향과 물체의 노멀 방향의 내적을 이용해서 라이트 방향에 따른 음영처리를 만들었다.

만약 카메라방향(viewDir)과 물체의 노멀 방향의 내적을 계산하면 마치 카메라가 빛이 된것 마냥 손전등 효과가 나지

않을까?

1
2
3
4
5
6
7
    float4 Lightingcatdark(SurfaceOutput s, float3 lightDir, float3 viewDir, float attan)
        {
            float fFresnel = dot(s.Normal, viewDir);
            fFresnel = pow(fFresnel, 5);
 
            return fFresnel;
        }
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

예상대로 위와 같이 코드를 작성하면 보는 시야(카메라)가 라이트가 되서 손전등을 비추는 것 마냥 얼굴 조명이 변한다.

 

- one minus(1-)

손전등 비추는 조명효과에서 one minus(1-)를 해서 뒤집으니 놀랍게 Rim Light효과가 만들어졌다.

 

 

- 기존 쉐이더에 Fresnel적용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
float4 Lighting_CatDarkLight(SurfaceOutput s, float3 lightDir, float3 viewDir, float attan)
        {
            float fNDotL = (dot(s.Normal, lightDir)*0.5f+0.5f);
            float3 fHalfVector = 0.0f;
            float fBPSpecular = 0.0f;
 
            if (fNDotL > 0)
            {
                fHalfVector = normalize(lightDir + viewDir);
                fBPSpecular = saturate(dot(s.Normal, fHalfVector));
                fBPSpecular = pow(fBPSpecular, 100.0f);
            }
 
            float fFresnel = 1 - dot(s.Normal, viewDir);
            fFresnel = pow(fFresnel, 10);
 
            float4 fFinalColor;
            fFinalColor.rgb = (s.Albedo * fNDotL * attan * _LightColor0.rgb) +
                                (fBPSpecular * _LightColor0) +
                                (fFresnel);
            fFinalColor.a = 1.0f;
 
            return fFinalColor;
        }
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

기존 코드의 fFinalColor.rgb 부분에 fFresnel 변수를 합하여 Rim Light효과를 추가했다.

 

- 라이트 조명에 반응하는 Fresnel 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
float4 Lighting_CatDarkLight(SurfaceOutput s, float3 lightDir, float3 viewDir, float attan)
        {
            float fNDotL = (dot(s.Normal, lightDir)*0.5f+0.5f);
            float3 fHalfVector = 0.0f;
            float fBPSpecular = 0.0f;
 
            if (fNDotL > 0)
            {
                fHalfVector = normalize(lightDir + viewDir);
                fBPSpecular = saturate(dot(s.Normal, fHalfVector));
                fBPSpecular = pow(fBPSpecular, 100.0f);
            }
 
            float fFresnel = 1 - dot(s.Normal, viewDir);
            fFresnel = pow(fFresnel, 10);
 
            float4 fFinalColor;
            fFinalColor.rgb = (s.Albedo * fNDotL * attan * _LightColor0.rgb) +
                                (fBPSpecular * _LightColor0) +
                                (fFresnel * _LightColor0 * attan);
            fFinalColor.a = 1.0f;
 
            return fFinalColor;
        }
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

실제 뒤쪽에 있는 라이트의 색을 받아 실제 Rim Light처럼만들기 위해 Fresnel 합하는 부분에 라이트컬러와 감쇠 값을

곱하였다.

 

 

 

 

 


WRITTEN BY
CatDarkGame
Technical Artist dhwlgn12@gmail.com

,