[유니티 쉐이더 스타트업] Part 11 | 커스텀 라이트1: Lambert 라이트

jungizz_·2023년 2월 19일
0

Unity Shader StartUp

목록 보기
11/17
post-thumbnail

📝무거운 Standard Shader의 라이팅 구조를 버리고, 커스텀 라이트를 직접 건드려서 라이팅 연산을 만들어보기!

1. 커스텀 라이트 기본형 만들기

  • 커스텀 라이트를 만들기 위해 라이팅 이름을 Test로 설정했다.
#pragma surface surf Test noambient
  • 위 이름에 적합한 LightingTest()라이팅 함수를 추가한다.
    Lighting + (라이트이름) 구조를 지켜서 라이팅 함수로 만들어야한다.
float4 LightingTest(SurfaceOutput s, float3 lightDir, float atten) {
	return float4(1, 0, 0, 0); //아직 붉은 결과물만 출력한다.
}
  • 최종적으로 화면에 컬러를 출력할 것이기 때문에 float4의 반환값을 가진다.
  • 매개변수는 Properties에서 만들어서 변수로 받아오는 값빼고는 미리 정해져 있어 수정할 수 없다.

☑️ LightingTest()함수의 매개변수

1. SurfaceOutput s

  • 이전에는 surf()에서 o.Albedoo.Normal에 넣은 값들은 그냥 Standard나 Lambert, BlinnPhong 라이팅 함수를 거쳤다면, 현재는 우리가 만든 LightingTest() 라이팅 함수를 거치는 것이다.
  • LightingTest() 커스텀 라이팅 함수에서는 surf()에서 o.Albedo로 집어넣었던 값을 s.Albedo로 꺼내서 쓸 수 있다.

2. float3 lightDir

  • 조명 방향의 벡터 (뒤집혀 있고, 길이기 1인 단위 벡터 상태)

3. float atten

  • attenuation(감쇠)의 준말
  • 그림자를 받거나 거리가 멀어지면서 점점 조명이 흐려지는 라이트의 거리별 감쇠현상을 나타냄
  • '내가 빛을 받는 것을 없앨 때' 사용됨


2. Lambert 라이트 연산 만들기

  • 커스텀 라이트로 램버트 라이트 구현해보기
  • 위의 라이팅 함수를 수정한다.
  • ndot1 - 노멀 벡터와 라이트 벡터의 내적을 연산한 값
  • 버텍스에서 노멀값을 평범하게 받아와있는 상태라서 s.Normal이라고 받아도 제대로 노멀값이 들어온다.
float4 LightingTest(SurfaceOutput s, float3 lightDir, float atten) {
	float ndot1 = dot(s.Normal, lightDir);
	return ndot1;
}
  • 내적 연산한 값(-1~1)에 따라 음영이 계산된 상태

❗하지만 나중에 추가 라이트를 비추면 음수 값 때문에 문제가 발생할 수 있다. (ex-빛을 비추었는데도 음수라서 밝아지지 않음)

  • 위 현상을 방지하기 위해 0 아래는 전부 0으로 잘라버리는 함수를 사용하고, 많이 사용하는 두가지가 있다.
  1. saturate(값)
    : 0보다 낮은 값은 0으로, 1보다 높은 값은 1로 잘라준다.
float ndot1 = saturate(dot(s.Normal, lightDir));
  1. max(값, 값)
    : 두 값 중 큰 값을 내보내며, 한쪽의 값을 0으로 설정하면 0 이하인 값은 나오지 않는다.
float ndot1 = max(0, dot(s.Normal, lightDir));
  • 이제는 가장 어두운 부분은 0, 가장 밝은 부분은 1로 적용된다.
  • 확인을 위해 return ndot1 + 0.5;로 수정한 모습이다. (함수를 적용하지 않아 내적값이 -1까지 있을 때는 0.5를 더했음에도 불구하고 검은 면이 보인다.)

➕NormalMap 추가 (Part 08)


3. Half-Lambert 라이트 연산 만들기

☑️ Half-Lambert

  • 하프라이프에서 사용된 Lambert 라이팅의 수정 공식
  • 기존 Lambert 라이트의 cos그래프 연산 특성상 밝다가 갑자기 검게 음영이 떨어지는 단점을 보완
  • 물리적으로 옳지 않지만, 가볍고 보기좋다. (쉐이더는 보기 좋은게 우선!)

https://developer.valvesoftware.com/wiki/Half_Lambert

  • 기존 라이트 결과물 (Half-Lambert의 극명한 결과를 위해 조명 각도만 수정한 상태)

  • 위에서 적용시켜준 saturate()함수를 없애고, Half-Lambert공식을 넣는다.

float ndot1 = dot(s.Normal, lightDir) * 0.5 + 0.5;
//마법의 숫자 '* 0.5 + 0.5'
//-1부터 1까지의 숫자를 0에서 1까지의 범위로 맵핑한다.

  • 물론, 너무 부드러워서(물리적으로 옳지 않아서) 실제 사용 시에는 결과물에 제곱을 해주곤 한다.
return pow(ndot1, 3); //세제곱



4. Lambert 라이트 완성하기

  • 지금까지는 '빛 방향'에 따른 밝기만 구현되었고, '조명의 색상과 강도', '빛 감쇠', 'Albedo 텍스쳐'는 적용되지 않은 상태이다.
  • 텍스쳐와 조명색상, 감쇠를 연산하기 위해 라이팅 함수에 코드를 추가한다.
  • final - 반환값을 위한 float4 변수
  • s.Albedo - Albedo로 입력받은 텍스쳐
  • _LightColor0.rgb - 내장변수를 이용해 조명의 색상과 강도를 가져옴
  • atten - 빛의 감쇠현상 (아래에서 추가설명)
//[2.Lambert 라이트 연산 만들기]의 코드에서 이어서 수정
float4 LightingTest(SurfaceOutput s, float3 lightDir, float atten) {
  float ndot1 = saturate(dot(s.Normal, lightDir));
  float4 final;
  final.rgb = n.dot1 * s.Albedo * _LightColor0.rgb * atten;
  final.a = s.Alpha;
  return ndot1;
}

  • atten(빛의 감쇠)를 계산하면?
    • Self Shadow
      : 자기 자신의 그림자를 자기가 받음 (위 이미지에서 턱과 목 부근의 그림자)
    • Receive Shadow
      : 다른 물체가 위 얼굴에 그림자를 만들 수 있음
    • Directional Light 대신 범위가 있는 다른 라이트를 사용했을 때, 라이트와 멀어질수록 어두워지는 것을 확인할 수 있다.

☑️ 작업 과정 중간에 순수한 색을 확인하기 위해 적용해두었던 noambient를 삭제하여 최종적으로 Lambert를 완성한다.

//#pragma surface surf Test noambient (수정 전)
#pragma surface surf Test

➕ Half-Lambert 적용

📝최종 코드

Shader "Custom/LambertShader"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _BumpMap ("NormalMap", 2D) = "bump"{}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        CGPROGRAM
        #pragma surface surf Test

        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) {
            float ndot1 = dot(s.Normal, lightDir) * 0.5 + 0.5;
            float4 final;
            final.rgb = ndot1 * s.Albedo * _LightColor0.rgb * atten;
            final.a = s.Alpha;
            return pow(final, 2.5);
        }

        ENDCG
    }
    FallBack "Diffuse"
}
profile
( •̀ .̫ •́ )✧

0개의 댓글

관련 채용 정보