Contents

[클라우디아 도피오슬래시]유니티 물리 기반 셰이더 개발 04

   Apr 28, 2023     13 min read     - Comments

라이팅 셰이더


  • 디퓨즈(Diffuse) 불규칙한 미세면을 가진 표면의 부분집합이다. 다양한 방향으로 빛을 반사한다.

  • 스펙큘러(Specular) 정렬된 미세면을 가진 표면의 부분집합이다. 비슷한 방향으로 빛을 반사한다.

  • 엠비언트(Ambient) 씬 안에서의 최소 빛의 강도다. 따라서 빛이 전혀 도달하지 않는 곳이라 해도 단순 검정색이 되지 않게 한다.


근사


근사(Approximation)라는 것은 값이나 수량이 거의 가깝지만 정확한 것이 아님을 의미한다. 렌더링을 실시간으로 연산 수행 하는 것은 불가능하기 때문에(빛의 반사가 무한대로 일어나서) 근사를 사용하여 연산을 수행한다.

밝기를 계사하는 두 가지 방법(노멀 벡터의 크기와 빛의 방향 벡터의 크기는 1이다.)

float brightness = cos(angle_of_incidence) //입사각으로 구하는 밝기
float brightness = dot(normal, lightDir)   //노멀 벡터와 빛의 방향 벡터로 계산한 밝기
float3 pixelCorol = brightness * lightColor * surfaceColor // 최종 표면 색상값

이 기본 디퓨즈를 람버트(Lambert) 혹은 람버트 반사율(Lambertian Relectance)이라고 한다.


람버트 디퓨즈


접기/펼치기
Shader "Custom/DiffuseShader"
{
    Properties
    {
        _Color("Color", Color) = (1, 0, 0, 1)
    }
    SubShader
    {
        Tags { "LightMode" = "ForwardBase" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
            };

            float4 _Color;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                float3 worldNormal = UnityObjectToWorldNormal(v.normal);    //월드 노멀 벡터 계산
                o.worldNormal = worldNormal;    //출력 데이터 구조체에 할당
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                float3 normalDirection = normalize(i.worldNormal);

                float nl = max(0.0, dot(normalDirection, _WorldSpaceLightPos0.xyz));
                float4 diffuseTerm = nl * _Color * _LightColor0;

                return diffuseTerm;
            }
            ENDCG
        }
    }
}

오리 비교


텍스쳐 및 엠비언트 추가


접기/펼치기
Shader "Custom/DiffuseShader"
{
    Properties
    {
        _DiffuseTex("Texture", 2D) = "white" {}
        _Color("Color", Color) = (1, 0, 0, 1)
        _Ambient("Ambient", Range(0,1)) = 0.25
    }
    SubShader
    {
        Tags { "LightMode" = "ForwardBase" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD1;
            };

            sampler2D _DiffuseTex;
            float4 _DiffuseTex_ST;
            float4 _Color;
            float _Ambient;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _DiffuseTex);
                float3 worldNormal = UnityObjectToWorldNormal(v.normal);    //월드 노멀 벡터 계산
                o.worldNormal = worldNormal;    //출력 데이터 구조체에 할당

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                float3 normalDirection = normalize(i.worldNormal);

                float4 tex = tex2D(_DiffuseTex, i.uv);

                float nl = max(_Ambient, dot(normalDirection, _WorldSpaceLightPos0.xyz));
                float4 diffuseTerm = nl * _Color * tex *  _LightColor0;

                return diffuseTerm;
            }
            ENDCG
        }
    }
}

텍스쳐 추가 엠비언트 추가


스펙큘러


빛의 반사를 구현하는 것이다. 가장 간단한 스펙큘러 공식 중 하나가 퐁(Phong)이다.
\(R = 2(N \cdot L)N-L\)
이것이 반사 벡터다. 빛의 방향 벡터와 노멀 벡터의 내적에 2를 곱하고 다시 노멀 백터의외적을 구한 후 거기에 빛의 방향 벡터를 빼서 구할 수 있다.


퐁 구현 예
float3 reflectionVector = reflect(-lightDir, normal);   //반사 벡터
float specDot = max(dot(reflectionVector, eyeDir), 0.0);//반사 벡터와 뷰 벡터의 내적(뷰 종속)
float spec = pow(specDot, specExponent);
접기/펼치기
Shader "Custom/SpecularShader"
{
    Properties
    {
        _DiffuseTex("Texture", 2D) = "white" {}
        _Color("Color", Color) = (1, 0, 0, 1)
        _Ambient("Ambient", Range(0,1)) = 0.25
        _SpecColor("Specular Material Color", Color) = (1,1,1,1)
        _Shininess("Shininess", Float) = 10
    }
    SubShader
    {
        Tags { "LightMode" = "ForwardBase" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertexClip : SV_POSITION;
                float4 vertexWorld : TEXCOORD2;
                float3 worldNormal : TEXCOORD1;
            };

            sampler2D _DiffuseTex;
            float4 _DiffuseTex_ST;
            float4 _Color;
            float _Ambient;
            float _Shininess;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertexClip = UnityObjectToClipPos(v.vertex);
                o.vertexWorld = mul(unity_ObjectToWorld, v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _DiffuseTex);
                float3 worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldNormal = worldNormal;

                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                float3 normalDirection = normalize(i.worldNormal);
                float3 viewDirection = normalize(UnityWorldSpaceViewDir(i.vertexWorld));
                float3 lightDirection = normalize(UnityWorldSpaceLightDir(i.vertexWorld));

                // 텍스처 샘플링
                float4 tex = tex2D(_DiffuseTex, i.uv);

                // 디퓨저(람버트)구현
                float nl = max(_Ambient, dot(normalDirection, _WorldSpaceLightPos0.xyz));
                float4 diffuseTerm = nl * _Color * tex *  _LightColor0;

                //스펙큘러(퐁) 구현
                float3 reflectionDirection = reflect(-lightDirection, normalDirection);
                float3 specularDot = max(0.0, dot(viewDirection, reflectionDirection));
                float3 specular = pow(specularDot, _Shininess);
                float4 specularTerm = float4(specular, 1) * _SpecColor * _LightColor0;

                float4 finalColor = diffuseTerm + specularTerm;
                return finalColor;
            }
            ENDCG
        }
    }
}

스펙큘러


더 많은 광원 지원하기


접기/펼치기
Shader "Custom/SpecularShaderFowardAdd"
{
    Properties
    {
        _DiffuseTex("Texture", 2D) = "white" {}
        _Color("Color", Color) = (1, 0, 0, 1)
        _Ambient("Ambient", Range(0,1)) = 0.25
        _SpecColor("Specular Material Color", Color) = (1,1,1,1)
        _Shininess("Shininess", Float) = 10
    }
    SubShader
    {
        Tags { "LightMode" = "ForwardBase" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertexClip : SV_POSITION;
                float4 vertexWorld : TEXCOORD2;
                float3 worldNormal : TEXCOORD1;
            };

            sampler2D _DiffuseTex;
            float4 _DiffuseTex_ST;
            float4 _Color;
            float _Ambient;
            float _Shininess;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertexClip = UnityObjectToClipPos(v.vertex);
                o.vertexWorld = mul(unity_ObjectToWorld, v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _DiffuseTex);
                float3 worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldNormal = worldNormal;

                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                float3 normalDirection = normalize(i.worldNormal);
                float3 viewDirection = normalize(UnityWorldSpaceViewDir(i.vertexWorld));
                float3 lightDirection = normalize(UnityWorldSpaceLightDir(i.vertexWorld));

                float4 tex = tex2D(_DiffuseTex, i.uv);

                float nl = max(_Ambient, dot(normalDirection, _WorldSpaceLightPos0.xyz));
                float4 diffuseTerm = nl * _Color * tex *  _LightColor0;

                float3 reflectionDirection = reflect(-lightDirection, normalDirection);
                float3 specularDot = max(0.0, dot(viewDirection, reflectionDirection));
                float3 specular = pow(specularDot, _Shininess);
                float4 specularTerm = float4(specular, 1) * _SpecColor * _LightColor0;

                float4 finalColor = diffuseTerm + specularTerm;
                return finalColor;
            }
            ENDCG
        }
        Pass
        {
            Tags { "LightMode" = "ForwardAdd" } //추가 광원 지원
            Blend One One                       //포토샵의 레이어 같은 개념

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertexClip : SV_POSITION;
                float4 vertexWorld : TEXCOORD2;
                float3 worldNormal : TEXCOORD1;
            };

            sampler2D _DiffuseTex;
            float4 _DiffuseTex_ST;
            float4 _Color;
            float _Ambient;
            float _Shininess;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertexClip = UnityObjectToClipPos(v.vertex);
                o.vertexWorld = mul(unity_ObjectToWorld, v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _DiffuseTex);
                float3 worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldNormal = worldNormal;

                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                float3 normalDirection = normalize(i.worldNormal);
                float3 viewDirection = normalize(UnityWorldSpaceViewDir(i.vertexWorld));
                float3 lightDirection = normalize(UnityWorldSpaceLightDir(i.vertexWorld));

                float4 tex = tex2D(_DiffuseTex, i.uv);

                float nl = max(0, dot(normalDirection, _WorldSpaceLightPos0.xyz));
                float4 diffuseTerm = nl * _Color * tex * _LightColor0;

                float3 reflectionDirection = reflect(-lightDirection, normalDirection);
                float3 specularDot = max(0.0, dot(viewDirection, reflectionDirection));
                float3 specular = pow(specularDot, _Shininess);
                float4 specularTerm = float4(specular, 1) * _SpecColor * _LightColor0;

                float4 finalColor = diffuseTerm + specularTerm;
                return finalColor;
            }
            ENDCG
        }
    }
}