[Unity] URP 셰이더 Sub Graph, Custom Lighting, Keyword

2023. 6. 5. 19:24Unity

728x90
반응형

서브 그래프

유니티 URP에는 원하는 노드들을 커스텀으로 묶어서 저장할 수 있는 '서브 그래프' 기능이 있다.

 

 

만들어진 Sub Graph는 Create Node 리스트에서 찾아볼 수 있다. 저장된 이름은 "IsoscelesTrapezoid"이다.

 

 

Sub Graph의 장점

  • 셰이더 그래프의 가독성이 좋아진다.
  • 그래프의 재활용을 통해 작업 효율이 향상된다.
  • 유지 보수 측면에서 좋다. 특정 기능 서브 그래프가 여기저기 쓰였는데 수정을 해야 한다면 하나만 수정해도 전체에 반영될 수 있다.

 

 

셰이더 그래프 커스텀 라이팅

Custom Function 노드 활용

Unlit 셰이더에 라이팅을 적용하려면 [Custom Function] 노드가 필요하다.

[Custom Function]은 HLSL 코드를 작성해서 노드의 행동을 정의하는 노드이다. 셰이더 그래프에서 지원하지 않는 기능도 HLSL 코드를 사용한다면 만들 수 있다는 뜻이다.

 

 

File 또는 String을 선택할 수 있는데, File은 외부의 HLSL 파일을 참조하는 것이고, String은 노드에서 직접 코드를 기록하는 것이다. 리소스 관리 측면에서 여러 셰이더 그래프에서 하나의 HLSL 코드를 참조할 수 있고 여러 프로젝트 진행할 때 비슷 한 셰이더 기반을 공유한다고 가정하면 File 방식이 더 효과적이다.

 

 

Inputs 와 Outputs에 각각 해당하는 요소들을 입력했다. HLSL에 입력되어 있는 형식대로 Input과 Output이 적용되지 않으면 핑크색의 에러가 발생한다.

 

 

void MainLight_float(float3 WorldPos, out float3 Direction, out float3 Color, out float DistanceAtten, out float ShadowAtten)
{
#if defined SHADERGRAPH_PREVIEW
    Direction = float3(0.5, 0.5, 0);
    Color = 1;
    DistanceAtten = 1;
    ShadowAtten = 1;
#else
#if defined SHADOWS_SCREEN
    float4 clipPos = TransformWorldToHClip(WorldPos);
    float4 shadowCoord = ComputeScreenPos(clipPos);
#else
    float4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
#endif
    Light mainLight = GetMainLight(shadowCoord);
    Direction = mainLight.direction;
    Color = mainLight.color;
    DistanceAtten = mainLight.distanceAttenuation;
    ShadowAtten = mainLight.shadowAttenuation;
#endif
}

void MainLight_half(float3 WorldPos, out half3 Direction, out half3 Color, out half DistanceAtten, out half ShadowAtten)
{
#if defined SHADERGRAPH_PREVIEW
    Direction = half3(0.5, 0.5, 0);
    Color = 1;
    DistanceAtten = 1;
    ShadowAtten = 1;
#else
#if defined SHADOWS_SCREEN
    half4 clipPos = TransformWorldToHClip(WorldPos);
    half4 shadowCoord = ComputeScreenPos(clipPos);
#else
    half4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
#endif
    Light mainLight = GetMainLight(shadowCoord);
    Direction = mainLight.direction;
    Color = mainLight.color;
    DistanceAtten = mainLight.distanceAttenuation;
    ShadowAtten = mainLight.shadowAttenuation;
#endif
}

HLSL을 열어보면, MainLight_float, MainLight_half 함수명으로 정의되어 있는데, 셰이더 그래프 컴파일러가 정밀도 형식을 각 함수 이름에 추가하기 때문에 각 정밀도에 맞는 함수를 모두 정의한다.

 

 

추가한 [Position] 노드는 Vertex 포지션이 아니고 그리려고 하는 Pixel의 월드 포지션이다.

Output의 Direction 벡터는 빛의 방향벡터이며 원점을 기준으로 하는 길이 1의 벡터이다.

 

 

Lambert 라이팅

Direction 방향 벡터와 Normal 벡터를 dot 함수로 계산하면 Lambert 라이팅이 된다. 실제 사용에서는 [Saturate] 노드를 중간에 껴서 사용한다.

Lambert 라이팅에서 중요한 점은 각도에 따른 표면 밝기의 변화이다. 코사인 함수로 계산되며, 각도가 커질수록 빛의 밝기가 작아진다.

Unlit / Lambert Lighting

여기서 dot가 벡터의 내적 연산인데, 각 벡터 A, B가 있을 때 A 벡터 끝에서 B 벡터로 수직 투영한 값을 계산한다. 그래서 코사인 계산이 필요한 것이다.

 

 

라이트 컬러 적용

이전 셰이더 그래프에서 Color 적용을 위해 Outputs의 Color를 [Multiply] 노드로 곱했다.

 

 

그림자 적용

그림자를 드리우게 하기 위해서 셰이더를 수정한 결과이다. 출력값 두 개를 곱하고 Color를 곱하여 마지막에 [Dot Product]의 출력과도 곱하여 Base Color와 연결한다.

왼쪽 나무 판의 그림자가 드리워지는 것을 볼 수 있다.

이 셰이더에서 추가된 세 개의 프로퍼티들의 [Reference]는 정확해야 한다.

 

 

그림자에 직접적으로 영향을 주는 값은 출력값 중 [ShadowAtten]이며, [DistanceAtten]은 현재 프로젝트 Directional Light의 Culling Mask와 관련된 값이다. 현재 오브젝트의 레이어에 라이트가 영향을 받는다면 값이 1이고 받지 않는다면 값이 0이다.

 

 

GI 적용

[Baked GI] 노드를 추가하고 포지션 값은 Pixel 포지션 그대로, [Normal Vector]는 동일하게 만들어서 연결하고 마지막에 더해서 Base Color로 연결한다.

 

 

Custom Additional Lights

유니티 공식 블로그의 Sub Graph 중 "Calculate Additional Lights" 서브 그래프는 위의 캡처와 같다.

 

 


 

 

정적 분기

샘플 프로젝트의 오브젝트들 중에서는 [Receive Shadows]가 일반적으로 켜저 있는 것들이 있다. 이 옵션을 Inspector 창에서 보면 체크박스 체크가 되어 있는 것을 볼 수 있고 그 상태일 때 Debug모드로 보면 [Shader Keywords]가 어떻게 설정되어 있는지를 볼 수 있다.

 

아래는 [Receive Shadows] 체크박스를 껐을 때의 [Shader Keywords] 이다.

 

 

이처럼 키워드에 의해 발생하는 분기를 정적(Static) 분기라고 부른다. 내부적으로 최종 셰이더를 여러 개 생성하는 방식이다. 이처럼 자동으로 엔진이 생성하는 여러 개의 셰이더를 "Variant"라고 부른다.

 

 

셰이더의 정적 분기는 포그 On/Off 또는 라이트맵의 사용/미사용 등 조건이 많아질수록 배리언트 숫자가 기하급수적으로 늘어나게 되고 이렇게 증폭되는 현상을 셰이더 폭발이라고 부른다. 이렇게 되면 로딩 시간이 길어진다.

SRP Batcher는 같은 셰이더를 사용하는 서로 다른 재질에 대해 Set Pass Call을 줄여주는 최적화 기술이라서 정적 분기에 의해 분기된 셰이더는 재질이 같아도 다른 셰이더로 인식되는데 이 SRP Batcher가 작동하지 않게 된다.

 

 

키워드 생성과 적용

이전 셰이더에서 Fog 관련 노드들이 추가되었다. 픽셀 포지션 값을 받아서 [Fog] 노드를 거쳐 [Lerp] 노드에서 연산을 했다. 이후 [Use Fog] 노드에서 On/Off 조건이 추가된 것을 볼 수 있다. [Lerp] 노드는 A와 B 색을 T의 비율로 선형 보간하는 노드이다. T가 0이면 A의 색, 1이면 B의 색이 된다. 0.5면 A와 B의 중간색이 된다.

추가된 "UseFog" 키워드이다. [Reference] 이름으로 "_USEFOG"를 쓰고 있는 것을 볼 수 있다. [Keyword] - [Boolean]으로 추가된 것을 볼 수 있다.

 

 

[UseFog] Graph Inspector 창의 [Definition]을 보면 드롭 다운 박스에 세 가지 옵션이 있는 것을 볼 수 있다.

  • Shader Feature: 프로젝트 빌드 시에 재질에서 선택한 키워드만 배리언트를 생성한다. 프로젝트 모든 재질을 살펴보니 UseFog 키워드를 모두 사용했다면 해당 키워드가 없는 분기의 배리언트 셰이더는 빌드에 포함되지 않는다. 빌드 실행 후에 UseFog 키워드를 다시 사용하더라도 해당 셰이더가 없으므로 오류를 일으킬 수 있다.
  • Multi Compile: 재질에 지정된 키워드 상관없이 모든 경우의 수를 컴파일해서 빌드에 포함시킨다. 빌드 이후에도 키워드 옵션 변경 시 셰이더 오류가 발생하지 않는다.
  • Predefined: 렌더 파이프라인이 이미 정의한 키워드일 경우에 사용하고 셰이더 그래프는 이 키워드를 정의하지 않는다.

재질 키워드 옵션을 변경할 일이 없으면 Shader Feature, 있다면 Multi Compile을 선택한다.

 

 

  • Local: 재질 안에서만 사용되는 키워드. 특별한 경우가 아니라면 일반적으로 Local을 쓴다.
  • Global: 프로젝트 전체에서 사용되는 키워드

*Global과 Local은 각각 키워드 숫자의 제한이 있다.

 

 


참고자료

 

아티스트를 위한 유니티 URP 셰이더 입문 - YES24

셰이더(Shader)를 HLSL(High Level Shader Language)로 다루고 싶은 아티스트를 위한 입문서다. 아티스트들의 눈높이에 맞추어 기초 그래픽스 이론과 셰이더 개념을 설명하고, 이를 유니티 엔진에서 활용할

www.yes24.com

 

728x90
반응형