[유니티 쉐이더 스타트업] Part13 커스텀 라이트2: Blinn Phong 스페큘러

jungizz_·2023년 2월 26일
0

Unity Shader StartUp

목록 보기
13/17
post-thumbnail

1. Bilnn-Phong 반사 이론

  • Specular(하이라이트)의 표현 방식은 다양하지만, 가장 유명하고 전통적인 방식은 Phong 반사이다.
  • PBR이전의 라이팅 공식은 Lambert의 Diffuse + BlinnPhong의 Specular + Ambient 색상 이다.

☑️ Phong 공식

  • 내가 보는 방향으로부터 반사된 방향에 조명이 있으면 그 부분의 하이라이트가 가장 높다.
  • Specular가 흰 동그라미로 표현됨 (태양을 상정한 간략화된 표현)
  • 이 하이라이트는 내 시선 벡터V를 노말 방향N을 기준으로 반사하는 반사 벡터와R 조명 벡터L의 내적(R·L)으로 표현된다. 하지만 이 반사 벡터를 연산하기 위해서는 dot 연산이 또 들어간다는 부담이 있다.
  • dot연산이 두번 들어가는 위 공식을 간략화 한 것이 Blinn-Phong 공식이다!

☑️Blinn-Phong 공식

  • 시선 벡터V와 조명 벡터L의 중간값인 하프 벡터H를 구하고, 이를 노멀벡터와 내적한다(N·H).
  • 하프벡터는 조명벡터와 시선벡터를 더해서 구하고, 길이를 1로 조절하기 위해 normalize()함수를 사용한다.

2. Blinn-Phong Specular 만들기

  • Part11의 커스텀 라이트에서 시작
  • 결과를 확실히 보기 위해 noambient로 환경광을 끄고, Lambert 연산을 따로 독립시켜 DiffColor라는 이름으로 만들어 보기 편하게 수정했다.
Shader "Custom/Blinn-PhongShader"
{
    Properties
    {
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _BumpMap("NormalMap", 2D) = "bump"{}
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" }

        CGPROGRAM
        #pragma surface surf Test noambient

        sampler2D _MainTex;
        sampler2D _BumpMap;

        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
        };

        void surf(Input IN, inout SurfaceOutput o)
        {
            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) 
        {
            float4 final;

            //Lambert영역
            float3 DiffColor;
            float ndot1 = saturate(dot(s.Normal, lightDir));
            DiffColor = ndot1 * s.Albedo * _LightColor0.rgb * atten;
            
            //Specular영역


            //final영역
            final.rgb = DiffColor.rgb;
            final.a = s.Alpha;
            return final;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

  • 라이팅 함수에는 아직 카메라 벡터가 없기 때문에 float3 viewDir매개변수로 받아오고, Specular영역에서 스펙큘러를 연산한다.
  1. 하프벡터H 연산
float4 LightingTest(SurfaceOutput s, float3 lightDir, float3 viewDir, float atten) 
{
	···
	//Specular영역
    float3 H = normalize(lightDir + viewDir);
    ···
    //결과 확인을 위해 하프벡터를 반환
    return float4(H,1);
}


2. 하프벡터H와 노멀벡터s.Normal의 내적 연산

float3 H = normalize(lightDir + viewDir);
float spec = saturate(dot(H, s.Normal));
spec = pow(spec, 100); //스펙큘러의 넓이 줄이기
···
return spec;

  1. 색 연산, 범위 조절 인터페이스 추가 / Diffuse와 더해서 최종 결과 출력
//Specular영역
float3 SpecColor;
float3 H = normalize(lightDir + viewDir);
float spec = saturate(dot(H, s.Normal));
spec = pow(spec, _SpecPow); //properties에서 받아온 값으로 범위 조절 
SpecColor = spec * _SpecCol.rgb; //properties에서 받아온 값으로 색 연산

//final영역
final.rgb = DiffColor.rgb + SpecColor.rgb; //Diffuse와 Specular 합
final.a = s.Alpha;
return final; //최종 결과 반환

📝 최종코드 (noambient 삭제)

Shader "Custom/Blinn-PhongShader"
{
    Properties
    {
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _BumpMap("NormalMap", 2D) = "bump"{}
        
        //유니티에 내장되어 있는 BlinnPhong에서 사용하고 있는
        //_SpecColor는 Porperties에서 사용하면 안되므로, 
        //_SpecCol이라고 살짝 이름을 바꿔 사용
        _SpecCol("Specular Color", Color) = (1, 1, 1, 1)
        _SpecPow("Specular Power", Range(10, 200)) = 100
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" }

        CGPROGRAM
        #pragma surface surf Test //noambient 삭제로 최종 결과물 확인

        sampler2D _MainTex;
        sampler2D _BumpMap;
        float4 _SpecCol;
        float _SpecPow;

        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
        };

        void surf(Input IN, inout SurfaceOutput o)
        {
            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, float3 viewDir, float atten) 
        {
            float4 final;

            //Lambert영역
            float3 DiffColor;
            float ndot1 = saturate(dot(s.Normal, lightDir));
            DiffColor = ndot1 * s.Albedo * _LightColor0.rgb * atten;
            
            //Specular영역
            float3 SpecColor;
            float3 H = normalize(lightDir + viewDir);
            float spec = saturate(dot(H, s.Normal));
            spec = pow(spec, _SpecPow);
            SpecColor = spec * _SpecCol.rgb;

            //final영역
            final.rgb = DiffColor.rgb + SpecColor.rgb;
            final.a = s.Alpha;
            return final;
        }
        ENDCG
    }
    FallBack "Diffuse"
}


3. Gloss 맵으로 스펙큘러 강도 조절

  • 위의 쉐이더는 전체적으로 공평하게 나오는 스펙큘러로, 원하지 않는 부분까지 스펙큘러가 나타나기 때문에 강도를 조절할 필요가 있다.
❓😥아래와 같은 맵을 넣었지만 스펙큘러가 강해지기만 할 뿐, 책처럼 디테일한 차이가 느껴지진 않았다.

4. Rim라이트를 커스텀 라이트로 옮기기

  • 직전 파트에서 한 Rim라이트는 surf안에서 연산을 했지만, 이제는 Specular 공식으로 viewDir를 커스텀라이트 안에서 사용할 수 있기 때문에 Rim도 커스텀라이트 안에서 처리해 볼 것이다.
···
//Rim영역
float3 rimColor;
float rim = abs(dot(viewDir, s.Normal));
float invrim = 1 - rim;
rimColor = pow(invrim, _RimPow) * _RimColor.rgb; //_RimPow, _RimColor는 properties에서 받아온 값

//final영역
final.rgb = DiffColor.rgb + SpecColor.rgb + rimColor.rgb; //rim도 더해준다.
final.a = s.Alpha;
return final;


5. Rim 라이트를 이용한 가짜 스펙큘러 만들기

  • 위 코드에서 굳이 riminvrim을 구분해둔 이유는 가짜 스펙큘러를 만들기 위해서이다.
  • 먼저, return pow(rim, 200);로 제곱한rim의 결과만 따로 확인해보면 나의 시선 쪽으로 하이라이트가 생긴것을 확인할 수 있다.
  • 즉! 이것을 스펙큘러로 활용할 수 있다. (오히려 연산양을 줄일 수 있어서 좋다)
  • 카메라 위치에 따라 바뀌는 또 하나의 스펙큘러를 확인할 수 있다.

❗실제로 조명이 하나만 있는 경우는 거의 없으므로 디테일을 살리기 위해 이런 방식을 이용하는 것을 권장한다.

➕ 만든 기능을 전부 Porperties에 넣은 최최최종 코드

Shader "Custom/Blinn-PhongShader"
{
    Properties
    {
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _BumpMap("NormalMap", 2D) = "bump"{}
        _GlossTex("GlossTex", 2D) = "White"{}

        _SpecCol("Specular Color", Color) = (1, 1, 1, 1)
        _SpecPow("Specular Power", Range(10, 200)) = 100

        _SpecCol2("Specular Color", Color) = (1, 1, 1, 1)
        _SpecPow2("Specular Power", Range(10, 200)) = 100
        
        _RimColor("Rim Color", Color) = (0.5, 0.3, 0.2, 1) 
        //3번의 스펙큘러 강도 조절이 잘 안되서 그런지 부자연스러움이 있어 색을 조정하여 그나마 자연스러워 보이도록 했다
        _RimPow("Rim Power", Range(1, 10)) = 7
    }
    SubShader
    {
        Tags { "RenderType" = "Opaque" }

        CGPROGRAM
        #pragma surface surf Test 

        sampler2D _MainTex;
        sampler2D _BumpMap;
        sampler2D _GlossTex;
        float4 _SpecCol;
        float _SpecPow;
        float4 _SpecCol2;
        float _SpecPow2;
        float4 _RimColor;
        float _RimPow;

        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float2 uv_GlossTex;
        };

        void surf(Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
            float4 m = tex2D(_GlossTex, IN.uv_GlossTex);
            o.Albedo = c.rgb;
            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            o.Gloss = m.a;
            o.Alpha = c.a;
        }

        float4 LightingTest(SurfaceOutput s, float3 lightDir, float3 viewDir, float atten) 
        {
            float4 final;

            //Lambert영역
            float3 DiffColor;
            float ndot1 = saturate(dot(s.Normal, lightDir));
            DiffColor = ndot1 * s.Albedo * _LightColor0.rgb * atten;
            
            //Specular영역
            float3 SpecColor;
            float3 H = normalize(lightDir + viewDir);
            float spec = saturate(dot(H, s.Normal));
            spec = pow(spec, _SpecPow);
            SpecColor = spec * _SpecCol.rgb * s.Gloss;

            //Rim영역
            float3 rimColor;
            float rim = abs(dot(viewDir, s.Normal));
            float invrim = 1 - rim;
            rimColor = pow(invrim, _RimPow) * _RimColor.rgb;

            //Fake Specualr 영역
            float3 SpecColor2;
            SpecColor2 = pow(rim, _SpecPow2) * _SpecCol2 * s.Gloss;

            //final영역
            final.rgb = DiffColor.rgb + SpecColor.rgb + rimColor.rgb + SpecColor2.rgb;
            final.a = s.Alpha;
            return final;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

profile
( •̀ .̫ •́ )✧

0개의 댓글

관련 채용 정보