Unity/Unity C#

CustomLight

YoonJongSeok 2023. 2. 13. 15:35

Lambert 라이팅

빛의 음영만 계산하고 specular는 계산하지 않는 라이팅이다. 

Shader "Custom/CustomLight"
{
    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)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Alpha = c.a;
        }
        float4 LightingTest (SurfaceOutput s, float3 lightDir, float atten)
        {
            return float4(1,0,0,1);
        }
        ENDCG
    }
    FallBack "Diffuse"
}

Lambert lighting은 SurfaceOutput 구조체를 사용해야 쓸 수 있다.

이제 lambert 라이팅 말고 custom light를 만들자 라이팅에서는 lambert, 블린 퐁 같은 라이팅도 실제 물리 세계에서는 계산이 무겁고 때문에 원하는 라이팅 표현을 하기가 어려울 때가 많다. 그래서 custom 라이팅을 해야한다.

 

Custom light를 만들기 위한 기본 준비이다.

Shader "Custom/CustomLight"
{
    Properties
    {
         _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        CGPROGRAM
        #pragma surface surf Test noambient

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };
        fixed4 _Color;
        void surf (Input IN, inout SurfaceOutput o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Alpha = c.a;
        }
        float4 LightingTest (SurfaceOutput s, float3 lightDir, float atten)
        {
            return float4(1,0,0,1);
        }
        ENDCG
    }
    FallBack "Diffuse"
}

surface surf 옆에 Test 라는 원하는 이름을 써주었고

float4 자료형인 LightingTest 라는 함수를 만들어주었다. 여기서 이름이 중요한데 Lighting + '라이트 이름' 을 써주어야 라이트 함수로 받아들이게 된다.

라이팅 함수의 매개변수들인 (SurfaceOutput s, float3 lightDir, float atten) 이것들은 정해진 것이므로 수정할 수가 없다.

SurfaceOutput 구조체는 Albedo, Normal, Emission, Specular, Gloss, Alpha를 가지고 있는 구조체이다. lightDir 은 빛의 방향벡터이고 크기가 1인 방향벡터이다. 그런데 내적 계산하기 쉽게 하기 위해서 빛의 방향에서 뒤집혀진 벡터를 이용하게 된다. atten은 그림자를 받거나 거리가 멀어지면서 빛이 흐려지는 현상을 표현하기 위한 것이다. 즉 빛을 받는 것을 없앨 때 사용한다.

 

 float4 LightingTest (SurfaceOutput s, float3 lightDir, float atten)
        {
            float dot1 = dot(s.Normal, lightDir);
            return dot1;
        }

조금씩 작성을 하게되는데 Lambert 의 기본형을 구현해보았다. 해당 알고리즘은 diffuse color 알고리즘과 똑같다.

그러나 음수까지도 나오게 된다. 여기서 C++ 에서는 glm api의 max함수를 이용하였는데 여기서는 saturate 함수를 이용한다. 0 ~ 1 사이로 잘라주게 된다.

그리고 효과를 더 잘 보이기 위해서 normal map을 추가하였다.

 

Shader "Custom/CustomLight"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _BumpMap("Normal Map",2D) = "bump" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        CGPROGRAM
        #pragma surface surf Test noambient

        sampler2D _MainTex;
        sampler2D _BumpMap;

        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
        };
        fixed4 _Color;
        void surf (Input IN, inout SurfaceOutput o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            o.Alpha = c.a;
        }
        float4 LightingTest (SurfaceOutput s, float3 lightDir, float atten)
        {
            float dot1 = saturate(dot(s.Normal, lightDir));
            return dot1;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

이번엔 half lambert를 이용해볼 것이다.

half lambert는 밸브 사에서 낸 논문 Shading in Valve's Source Engine 에 발표한 Lambert 라이팅 수정 공식이다. 

dot1에 *0.5 + 0.5 를 하게 되면 half lambert이다.

 

조금 더 부드럽게 그림자가 생기는 것을 볼 수 있다.

 

이제 빛의 색상, 감쇠를 연산해서 적용해본다

  float4 LightingTest (SurfaceOutput s, float3 lightDir, float atten)
        {
            float dot1 = pow((dot(s.Normal, lightDir) + 0.5 * 0.5),3);

            float4 final = 0;
            final.rgb = dot1 * s.Albedo *_LightColor0.rgb *atten;
            final.a = s.Alpha;
            return final;
        }

dot1 * s.Albedo 가 되어 lambert 라이트와 연산되면서 diffuse를 표현하였고 _LightColor0.rgb * atten을 통해 그림자를 표현하였다. 물체가 해당 표현이 된 material을 지나면 그림자가 생기게 된다. 

_LightColor0.rgb * atten 적용
_LightColor0.rgb * atten 미적용 위의 material은 custom lighting이 아니다

_LightColor0는 유니티의 내장변수로 빛의 색을 나타내는 변수이다.  거기에 atten을 곱하여 그림자를 표현해줄 수 있다. atten은 Attenuation = 1 / (constant + (linear * distance) + (quadratic * distance^2)) 으로 구할 수 있으며

constant와 linear, quadratic은 변수이다.

 

 

https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html

 

Unity - Manual: Built-in shader variables

Built-in shader helper functions Shader data types and precision Built-in shader variables Unity’s built-in include files contain global variables for your shadersA program that runs on the GPU. More infoSee in Glossary: things like current object’s tr

docs.unity3d.com

유니티 document에서 알려준 UnityLightingCommon.cginc 에 저장되어 있다.

 

결과물