250109

凡愚·2025년 1월 9일

개발 일지

목록 보기
46/350

✅ 오늘 한 일


  • 대마왕의 유니티 URP 셰이더 그래프 읽기


📝 배운 것들


🏷️ 의존성 역전 원칙

  • 요리사가 만드는 건 '요리'다. 파스타 하나만 만든다고 설계하면 안된다.
  • 변하기 쉬운 것에 의존하면 안된다.
  • 인터페이스같은 걸로 사용하는 객체들의 공통 부분을 묶어서 그 인터페이스를 사용한다


📖 대마왕의 유니티 URP 셰이더 그래프


Part 14 | Fresnel(프레넬)

01 Fresnel(프레넬) 이란?

모든 일반적인 사물은 기울어질수록 심하게 반사가 된다.
즉, 시선 벡터(카메라 벡터)와 노말 벡터가 90도에 가까워질수록 반사가 심해진다.

금속 재질은 비금속 재질보다 확연하게 반사율이 다르다.
이런 반사 공식을 Fresnel(프레넬)이라고 부른다.

역광이 있다거나, 털이나 반투명의 재질이라거나 하면 프레넬 현상의 정도가 달라진다.
사진에서는 이것을 림 라이트(Rim Light)라 부른다. (같은 개념은 아니지만, 림 라이트를 프레넬로 많이 만든다)

림 라이트 게임 사용 예시

  • 배경과 캐릭터의 분리나 강조
  • 선택되었을 때나 특이한 상태 표현
  • 툰 셰이딩

02 Fresnel을 구현해 봅시다

이전에 배운 램버트 공식을 간단히 정의하면,
Normal Vector와 Light Direction의 각도가 같으면(0도) 밝고, 90도가 되면 어두워져 검은 색이 된다.

램버트 공식에서 Light Direction을 View Direction으로 치환하면,
마치 카메라가 조명인 것처럼, 카메라를 바라보는 면이 밝아지고 카메라와 각도가 벌어질수록 어두워진다.
('언제나' 어두운 부분은 존재하지 않는다고 생각해도 될듯)

one minus 노드를 이용해 결과를 뒤집으면
보는 각도를 바꿔도 언제나 외곽이 밝아보이면서 림 라이트처럼 보이기 시작한다.

두께를 얇게 만들어주기 위해 3제곱을 해준다.

03 Fresnel을 다듬어 완성해 봅시다

림 라이트의 두께와 색깔을 커스텀할 수 있게 프로퍼티로 만들고 컬러를 곱해줬다.
거기에 이전 단원에서 만들었던 램버트 라이트를 더해줬다.

Fresnel 기능은 자주 쓰이므로, Fresnel Effect라는 이름으로 노드가 만들어져 있다.

Troubleshooting
책에서처럼 고급스럽게 빛나지를 않아 예제 씬을 Import해서 비교했는데, Global Volume의 Tone Mapping이 ACES로 돼있었다. 나도 ACES로 바꿔주니 해결됨.

04 Fresnel을 응용한 이펙트를 만들어 봅시다

여기 Emission 맵이 있는 가로등이 있다. Intensity를 올리면 Bloom 효과와 함께 발광할 것이다.
하지만 저사양의 모바일 게임이거나 레트로한 느낌을 주기 위해 HDR이나 Bloom을 사용하지 않고 이런 효과를 내야 한다고 생각해보자.

Fresnel Effect 노드를 이용하면 색상을 굳이 HDR로 하지 않아도 빛나는 것처럼 보이는 이펙트를 만들 수 있다.

이 방법은 모델링을 이용해서 빛나는 효과를 만드는 것이기 때문에, 모양대로 빛나게 만드는 것이 가능하다.

조명이 깜빡이는 효과를 주기 위해, 0에서 1 사이를 오가는 값을 곱해주었다.
하지만 그냥 곱해주면 sine의 음수 부분 때문에 이상한 색이 나온다.

해결책

  • Saturate : 불이 꺼져 있을 때의 시간이 너무 길다
  • 하프 램버트 (*0.5+0.5) : 부드럽게 지속적으로 반짝이는 곡선이 나온다
  • 절대값 함수 : 통통 튀는 느낌이 된다

Time에 계수를 곱해주면 반짝이는 속도를 조절할 수 있다.

애니메이션이 잘 보이지 않으면?
씬 뷰 상단의 스택에 반짝이는 효과가 붙어있는 아이콘 (Toggle skybox, fog...) 에서 Always Refresh를 체크한다.

반짝임 효과에서 one minus를 빼주면 보호막, 비눗방울, 홀로그램같은 느낌을 줄 수 있다.
(위에서의 깜빡임 효과를 적용하면 깜빡이는 홀로그램도 만들 수 있다)

World Position을 UV 대용으로 사용하면 어떤 물체라도 World 좌표계의 기준에 맞게 텍스쳐를 입힐 수 있다.
Time을 Negate로 음수로 만들고, 이를 Combine Y에 연결한 후 Add하여 시간에 따라 y축도 흘러간다.

Swizzle 노드?
Vector4에서 필요한 좌표만, 순서까지 마음대로 바꾸어 출력하는 노드

Triplaner?
xy, zy, xz 방면으로 각 면의 방향에 맞게 UV를 만들어 입혀주는 기술. 노드로도 만들어져 있다.

만들어져 있는 것을 사용하는 것은 응용의 폭에 한계가 있고, 혹시나 낭비되는 영역이 있더라도 최적화를 할 수 없다.
각 노드를 선택한 뒤 F1이나 우클릭 > Open Documentation으로 도움말을 볼 수 있다.

Checkerboard 노드를 이용하지 않고 줄무늬를 구현해보자.

World Position과 Fraction 노드를 이용해 줄무늬를 만들 수 있다. Power을 빼면 검은 부분이 사라진다.
이때 Graph Settings에서 Blending Mode를 Alpha로 바꿔주어야 검은색이 투명해지지 않는다.

Fraction 노드?
들어온 숫자의 소수점 부분만 리턴한다. 1.1은 0.1을, 1.9는 0.9를 리턴한다.

Step 노드를 추가하면, 딱 끊어져 보이는 줄무늬를 만들 수 있다.

Step 노드?
in에 들어온 값이 Edge에 쓴 수보다 크거나 같으면 1을 반환하고, 작으면 0을 반환한다.

Part 15 | 커스텀 라이트 2

01 퐁(Phong)블린 / 퐁(Blinn-Phong) 반사 이론

Simple Lit = Diffuse Lambert + Ambient Color + Specular

가장 유명하고 전통적인 스페큘러 표현 방식은 퐁 반사(Phong Reflection)이다.
이 공식의 기본 원리는 '내가 보는 방향으로부터 반사된 방향에 조명이 있으면 그 부분의 하이라이트가 가장 높다'이다.

스페큘러는 거울 반사를 의미한다.
하이라이트는 '강한 조명을' 거울반사한 것을 의미한다.
과거 컴퓨터의 연산 능력이 높지 않았을 땐 광원에 의한 스페큘러만 적당히 계산해주었다.

퐁 공식 : 노말 방향을 기준으로 조명 벡터를 반사하는 반사 벡터 R과 시선 벡터 V의 내적으로 스페큘러 표현 (반사벡터 계산이 옛날 기준으로 무거움)
블린-퐁 공식 : 시선 벡터와 조명 벡터의 중간 값인 하프벡터 H를 구하고, 이를 노말 벡터 N과 내적

02 퐁 반사(Phong Reflection) 만들기

Phong Specular 구현

  • directional light의 방향 벡터와 normal vector의 반사 벡터를 구하고 negate로 뒤집는다
  • view direction과 dot product하고 saturate한다
  • power 노드로 스페큘러의 넓이를 줄인다
  • 마지막에 directional light의 color와 곱해준다

View Direction과 View Vector의 차이?
View Direction : 길이가 1로 Normalize되어 있는지
View Vector : 길이가 Normalize되어 있지 않은, 순수하게 카메라와 버텍스와의 거리를 가지고 있는 노드

Lambert Light와 더해주면 정반사임이 확연히 드러난다.

03 블린 퐁 반사(Blinn-Phong Reflection) 만들기

Blinn-Phong Specular 구현

  • 시선 방향 벡터와 빛 방향 벡터를 더한 후 Normalize하면 Half Vector가 된다
  • Half Vector를 Normal Vector와 내적하고 saturate한 후 power로 크기를 줄이고 Color를 곱해주고 Lambert 더해주면 된다

Part 16 | 커스텀 라이트 완성

01 Diffuse Reflection 연산 - 04 Fresnel 연산

  • Lambert 라이트와 텍스쳐를 곱해서 Diffuse 연산
  • Baked GI와 Base Map 텍스쳐를 곱하여 Ambient Light 연산
  • Ambient Occlusion 맵을 Ambient Light와 곱하기
  • Blinn Phong 또는 Phong으로 specular 연산
  • Gloss 맵을 specular와 곱하여 마스킹 (색을 표현하는게 아니니 sRGB 끄기)
  • Fresnel Effect를 Baked GI와 곱하기. 각 노드에 World Normal Map 삽입.

05 Fresnel을 이용한 가짜 Specular 만들기

Fresnel을 One Minus로 뒤집고, Power로 작게 만든 후, 컬러를 조절할 수 있게 컬러를 곱하면 스페큘러처럼 보인다.
스페큘러는 특정 방향을 바라볼 때 생기는 하이라이트이므로 크게 다를게 없기 때문.
하지만 Fresnel의 특성상 내가 바라보는 방향만이 언제나 빛난다. (카메라 위에 붙어 있는 조명 느낌)

이걸 스페큘러로 써도 아무 문제 없다.
빛의 영향이 없는 뒷면에서도 보이기 때문에 어두운 곳에서도 음영과 질감을 좀 더 잘 보이게 할 수 있다.
이 공식은 림 라이트 연산에서 나오는 부산물로 만든 결과물이므로 연산량도 절약할 수 있다.

Normal map을 적용하고 Gloss로 마스킹도 해주면 가짜 스페큘러 완성.

(이쯤 오면 Add가 너무 많아지는데, Sub graph로 묶어 4Add, 5Add 이런 식으로 사용하면 정리하기 좋다)

06 그림자 감쇠(Shadow Attenuation)

ShaderGraph에서는 그림자가 그려지는 Shadow Pass를 자동으로 생성한다.
HLSL 코드로 셰이더를 작성하면 Shadow Pass와 Depth Pass까지 모두 작성해야 한다.

하지만 우리가 만든 Shader Graph를 적용한 오브젝트 위에 공을 가져다 놓으면 그림자가 드리워지지 않는다.
'그림자를 드리우는 연산'은 했는데, '내가 그림자를 받는 연산'은 해주지 않았기 때문이다.

이것을 연산하려면, 그림자 연산을 받아와서 Lambert 연산과 Specular 연산에 곱해줘 조명 계산을 없애줘야 한다.

void CustomLight_File_float(out float3 Direction, out float3 Color)
{
    #ifdef SHADERGRAPH_PREVIEW
        Direction = float3(1,1,1);
        Color = float3(1,1,1);
    #else
        Light light = GetMainLight();
        Direction = light.direction;
        Color = light.color;
    #endif
}

CustomLight.hlsl 파일을 만들고 sub graph로 만들었던 Lambert에 가서 Cumstom Function의 Precision, Type, Name 등을 바꿔주면 String이 아닌 File 형식으로 CustomLight.hlsl을 적용시킬 수 있다.

//커스텀라이트
void CustomLight_File_float(out float3 Direction, out float3 Color)
{
    #ifdef SHADERGRAPH_PREVIEW
        Direction = float3(1, 1, 1);
        Color = float3(1, 1, 1);
    #else
        Light light = GetMainLight();
        Direction = light.direction;
        Color = light.color;
    #endif
}


//커스텀라이트 셰도우
void CustomLight_Shadow_float(float3 worldPos, out float ShadowAtten)
{
    #ifdef SHADERGRAPH_PREVIEW
        ShadowAtten = 1.0f;
    #else
        //shadow Coord 만들기
        #if defined(_MAIN_LIGHT_SHADOWS_SCREEN) && !defined(_SURFACE_TYPE_TRANSPARENT)
            half4 clipPos = TransformWorldToHClip(worldPos);
            half4 shadowCoord = ComputeScreenPos(clipPos);
        #else
            half4 shadowCoord = TransformWorldToShadowCoord(worldPos);
        #endif
        
        Light light = GetMainLight();
        //메인라이트가 없거나 받는 셰도우 오프 옵션이 되어 있을때는 그림자를 없앤다
        #if !defined(_MAIN_LIGHT_SHADOWS) || defined(_RECEIVE_SHADOWS_OFF)
            ShadowAtten = 1.0f;
        #endif

        //ShadowAtten 받아와서 만들기
        #if SHADOWS_SCREEN
            ShadowAtten = SampleScreenSpaceShadowmap(shadowCoord);
        #else
            ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
            half shadowStrength = GetMainLightShadowStrength();
            ShadowAtten = SampleShadowmap(shadowCoord, TEXTURE2D_ARGS(_MainLightShadowmapTexture,
            sampler_MainLightShadowmapTexture),
            shadowSamplingData, shadowStrength, false);
        #endif
    #endif
}

CustomLight.hlsl에 CustomLight_Shadow 함수를 추가한 뒤 테스트해보면 두 가지 문제가 있다.

  • 거리가 멀어지면 아예 새까맣게 되거나 새하얗게 됨
  • Soft Shadow 설정이 되지 않아 그림자의 픽셀이 두드러짐

이유는 Keyword로 Multi Compile을 제대로 설정해줘야 하기 때문이다.

그림자는 유니티 엔진 내부에서 제어하고 있다.

  • 그림자 On/Off 명령
  • 오브젝트에 받아라/받지 말아라는 명령
  • 소프트 셰도우와 하드 셰도우 옵션
  • 그림자 Cascade 1개-4개까지의 옵션
  • 그림자 거리 조절 등

그래서 그림자 관련 셰이더를 만든다면, 유니티에서 제어되고 있는 그림자 옵션과 동일하게 만들어야 사용할 수 있다.

Keyword
키워드는 Property의 일종이다. 경우에 따라 다른 셰이더 옵션을 활성시키는 기능이다.
키워드는 플랫폼별, 머테리얼별, 조건별로 변화하는 셰이더를 만들어야 할 때 유리하다.

  • Boolean : On/Off로 선택
  • Enum : A, B, C와 같은 다중 메뉴 중에서 선택
  • Material Quality : 내장된 상중하 셰이더 옵션 선택

if문은 실시간으로 필요에 따라 작동하지만,
키워드는 미리 지정한 선택대로 셰이더를 자동으로 분리해서 갖고 있다는 점이 다르다.
즉, 이 작업이 많아지면 셰이더의 개수가 제곱으로 늘어난다.

Keyword의 Node Settings

  • Name : Property의 이름. Refernce와 같은 이름을 써주는 것이 가장 일반적.
  • Exposed : 이 기능이 체크돼있으면 Property가 Inspector에 노출된다
  • Reference Name : 키워드의 이름
    • 키워드는 모두 영어 대문자로 써야 한다
    • HLSL에서 지원하지 않는 문자는 모두 _로 변환된다
    • Reference의 이름을 오른 클릭하고 Reset Reference를 선택하면 기본 이름으로 돌아간다
  • Definition : 키워드가 정의되는 방식을 설정한다
    • Shader Feature : 빌드할 때 사용되지 않는 셰이더 베리언트를 제거한다. 용량을 절약할 수 있지만 필요한 베리언트가 포함되지 않을 수도 있다.
    • Multi Compile : 빌드할 때 사용되지 않는 셰이더 베리언트도 제거하지 않는다. 용량이 커질 수 있으니 조심해야 한다.
    • Predefined : 현재의 Render Pipeline에서 이미 이 키워드를 정의했으므로, 셰이더 그래프에서 생성되는 코드에서는 이를 정의하지 않음을 의미
  • Scope : 키워드를 정의할 범위를 설정한다. Predefined Keywords일 경우 이 옵션은 비활성화된다.
    • Global Keywords : 전체 프로젝트에 대한 키워드를 정의하며, 글로벌 키워드 한도에 포함된다
    • Local Keywords : 로컬 키워드 제한이 있는 하나의 셰이더에 대해서만 키워드를 정의한다

그림자는 직접광, 즉 Diffuse와 Specular에만 영향을 끼치는 것이므로
Diffuse + Specular에 Shadow를 곱해주면 된다.

Troubleshooting
책과 유니티 버전이 달라서인지 혼란이 조금 있었다. 책에서는 Keyword로 Multi Compile을 제대로 설정해줘야 하기 때문이다.라는 언급이 있었는데, 확인해보니 Keyword를 추가해주지 않아도 Soft Shadow가 설정됐다. Soft Shadow 옵션을 켜니 뭔가 달라지긴 하는데, 이상하게도 바닥에 드리워진 그림자와 돼지에게 드리워진 그림자 둘 다 "Soft"하진 않아 보인다. 일단 그림자 드리워졌으니 넘어가기.

아직 남은 것들

  • 포인트 라이트에 받는 영향
  • Fog 연산
  • 라이트맵과의 관계
  • 라이트 프로브
  • 리플렉션 프로브

0개의 댓글