[유니티 쉐이더 스타트업] Part 12 | Rim 라이트

jungizz_·2023년 2월 22일
0

Unity Shader StartUp

목록 보기
12/17
post-thumbnail
  • Fresnel(프레넬)
    • Standard Shader같은 물리 기반 쉐이더는 재질의 특성에 따른 반사율이 BRDF를 이용해 구현되어있음.
    • 아래 사진과 같은 반사 공식을 프레넬이라고 한다.
  • 기본적으로 게임에서는 배경과 캐릭터의 분리나 강조를 위해 Fresnel 공식을 과장되게 사용하곤 한다.

☑️ Fresnel 공식 구현은 빛 방향과 전혀 상관없게 간단히 만들어볼 것이다!


1. Fresnel 공식 구현

  • 이전 챕터의 lightDir벡터는 버텍스에서 바라보는 조명의 방향을 나타내는 단위벡터였으며, 이 벡터와 노멀 벡터를 내적하여 연산했다.
  • 이번에는 버텍스에서 바라보는 카메라의 방향 벡터viewDir와 노멀벡터의 내적을 연산할 것이다.
  • 뷰벡터와 노멀벡터를 내적하면, 카메라의 방향(내가 바라보는 방향)이 조명처럼 인식되어 밝아진다.
struct Input
{
	float2 uv_MainTex;
	float3 viewDir; //뷰벡터 받아오기
};

void surf (Input IN, inout SurfaceOutput o)
{
    fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    o.Albedo = 0; //순수한 Fresnel의 공식을 보기 위해 검은색으로 설정
    
    //노멀벡터와 뷰벡터의 내적
    float rim = dot(o.Normal, IN.viewDir);
    o.Emission = rim;       
    
    o.Alpha = c.a;
}

  • 내적값의 결과를 뒤집어서 외각이 밝아보이는 Rim light를 구현한다.
o.Emission = 1 - rim;  

  • Rim Light 느낌을 더 살리기 위해 흰 테투리를 더 얇게 만들 것이다.
  • 두 벡터의 각도 차이가 벌어질수록 흰색이 되는데, 이를 제곱함수로 표현하여 어두운 부분이 한참 존재하다가 갑자기 밝아지도록 한다.
o.Emission = pow(1 - rim, 3);  

  • 카메라 방향에 따른 어두운 면의 변화도 잘 확인된다.

2. Rim Light 완성

  • Rim Light의 색상, 넓이 조절 속성 만들기
//Rim light의 넓이는 제곱으로 조절하고, 색상은 곱해주기
o.Emission = pow(1 - rim, _RimPower) * _RimColor.rgb;

  • Albedo 활성화로 텍스쳐 받기 / NormalMap 적용하기
o.Albedo = c.rgb;
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));

  • dot연산에서 -1로 넘어가는 부분 막아주고 noambient 지워서 최종 결과물 완성
float rim = saturate(dot(o.Normal, IN.viewDir));

📝 최종 코드

Shader "Custom/RimShader"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _BumpMap("NormalMap", 2D) = "bump"{}
        _RimColor("RimColor", Color) = (1, 1, 1, 1)
        _RimPower("RimPower", Range(1, 10)) = 3
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        CGPROGRAM
        #pragma surface surf Lambert

        sampler2D _MainTex;
        sampler2D _BumpMap;
        float4 _RimColor;
        float _RimPower;

        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float3 viewDir;
        };

        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));
            float rim = saturate(dot(o.Normal, IN.viewDir));
            o.Emission = pow(1 - rim, _RimPower) * _RimColor.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
  • 게임에서 캐릭터가 데미지를 입거나 선택할 때 등 자주 사용되는 방식
  • 실제 존재하는 Rim 라이트를 구현했다고 하기엔 부족함
  • 높은 품질을 위해서는 응용이 필요하다!

3. 홀로그램 만들기

  • Rim 라이트를 알파에 집어넣으면 '홀로그램'의 기본형을 만들 수 있다.
  • Fresnel 기본 연산에서 시작
void surf(Input IN, inout SurfaceOutput o)
{
    fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    //o.Albedo = c.rgb;
    float rim = saturate(dot(o.Normal, IN.viewDir));
    o.Emission = pow(1 - rim, 3);
    o.Alpha = c.a;
}

  • 반투명을 구현하기 위해 알파에 Rim 라이트를 넣는다. (Part6-불 이펙트의 반투명 구현과 같은 방식)
//1. Tags { "RenderType"="Opaque" }를 아래와 같이 수정
Tags { "RenderType"="Transparent" "Queue"="Transparent" }

//2. alpha:fade 추가
#pragma surface surf Lambert noambient alpha:fade

//3. surf 함수에서 rim제곱값을 rim에 다시 넣고, 알파에 rim 넣기
rim = pow(1 - rim, 3);
o.Alpha = rim;

  • Emission에 원하는 색상을 넣어준다.
o.Emission = float3(0, 1, 0);


➕ 색상을 속성으로 받아서 처리

📝 최종 코드

Shader "Custom/HoloShader"
{
    Properties
    {
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _HoloColor("HologramColor", Color) = (0, 1, 0, 1)
    }
    SubShader
    {
        Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }

        CGPROGRAM
        #pragma surface surf Lambert noambient alpha:fade

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
            float3 viewDir;
        };

        fixed4 _HoloColor;

        void surf(Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _HoloColor;
            //o.Albedo = c.rgb;
            o.Emission = c.rgb;
            float rim = saturate(dot(o.Normal, IN.viewDir));
            rim = pow(1 - rim, 3);
            o.Alpha = rim;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

➕ 홀로그램 응용

1. 깜박이는 효과

  • rim에 시간의 sin값을 곱해서 알파에 넣어준다.
o.Alpha = rim * sin(_Time.y * 3); //_Time.y에 곱해진 수는 속도를 조절한다

  • sin곡선은 -1~1 사이를 반복하기 때문에 0 이하로 내려가는 부분이 절반이라 홀로그램이 깜빡일 때 안보이는 시간이 길다.
  • Half Lambertabs()를 사용하여 0~1사이 값이 되도록 한다.
//Half Lambert
o.Alpha = rim * sin(_Time.y * 0.5 + 0.5);

//abs() 함수 - 모든 음수를 양수로 변화
o.Alpha = rim * abs(sin(_Time.y));
❓위 gif에서처럼 파-빨 색 변화가 있는데, abs함수를 쓸 때만 색이 안변한다. 그 이유는 머르겠음. 사실 색 변화가 왜 있는지도 모르겠음.. 다시한번 살펴보기!

2. 줄무늬가 위로 올라가는 효과

  • input에서 받을 수 있는 다른 값 응용 - float3 worldPos(월드 공간상의 위치)

☑️ worldPos 확인

  • 정확한 확인을 위해 오브젝트를 검게 만들기
o.Emission = 0;
o.Alpha = 1;
  • Input에서 받아온 worldPos출력
  • xyz가 각각 rgb에 대입되어 나타나는 모습
  • 상하의 값만 필요하기 때문에 Y축만 가져온다. 아래와 같이 출력한다.
o.Emission = IN.worldPos.g;

  • frac() 함수를 사용해 숫자의 소수점 부분만 반환하면, 숫자가 증가할수록 0~0.999가 반복되는 결과가 나온다.
  • 즉, 검-흰 그라데이션이 반복된다.
o.Emission = frac(IN.worldPos.g);

  • 흰 부분을 줄여주기 위해 pow()함수를 사용해 검정 부분을 확장시킨다.
o.Emission = pow(frac(IN.worldPos.g), 30);

  • 흰색 라인간의 간격을 좁히기 위해 worldPos.g에 수를 곱해주고, _Time.y를 빼서 위로 흘러가도록 만든다.
o.Emission = pow(frac(IN.worldPos.g * 3 - _Time.y), 30);
  • 이 결과물을 알파채널에 더해주고 원래대로 정리하면 완성

📝 최종코드 (+세부조절)

Shader "Custom/HoloShader"
{
    Properties
    {
        _BumpMap("NormalMap", 2D) = "bump"{} //필요없는 MainTex대신 NormalMap 받기
        _RimPower("RimPower", Range(1, 10)) = 3
        _HoloColor("Hologram Color", Color) = (0, 1, 0, 1)
        _HoloGap("Hologram Gap", Range(1, 50)) = 5
        _HoloTrans("Hologram Transparent", Range(0, 100)) = 10
    }
    SubShader
    {
        Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }

        CGPROGRAM
        #pragma surface surf nolight noambient alpha:fade //Lambert 연산이 필요 없으므로 nolight로 수정

        sampler2D _BumpMap;
        fixed4 _HoloColor;
        float _RimPower;
        float _HoloGap;
        float _HoloTrans;

        struct Input
        {
            float2 uv_BumpMap;
            float3 viewDir;
            float3 worldPos;
        };
        

        void surf(Input IN, inout SurfaceOutput o)
        {
            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            o.Emission = _HoloColor.rgb;
            float rim = saturate(dot(o.Normal, IN.viewDir));
            rim = saturate(pow(1 - rim, _RimPower) + pow(frac(IN.worldPos.g * 3 - _Time.y), _HoloGap) * _HoloTrans*0.01); //_HoloGap으로 줄무늬의 간격 조절, 전체적으로 _HoloTrans*0.01를 곱해 투명도 줄이기
            o.Alpha = rim;
        }

        float4 Lightingnolight(SurfaceOutput s, float3 lightDir, float atten) { //nolight 커스텀 라이트 만들기
            return float4(0, 0, 0, s.Alpha); //알파 채널만 리턴
        }
        ENDCG
    }
    FallBack "Diffuse"
}

profile
( •̀ .̫ •́ )✧

0개의 댓글

관련 채용 정보