250108

lililllilillll·2025년 1월 8일

개발 일지

목록 보기
45/350

✅ 오늘 한 일


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


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


Part 11 | Lit 셰이더의 추가 기능과 응용기능을 사용해 봅시다

01 Height Map (Parallax Mapping)

Height Map : 높이를 저장하고 있는 그레이 스케일의 텍스쳐를 통칭

  • 유니티 Lit Shader에선 Parralax의 맵으로 사용
  • 실제 코드로 구현하긴 복잡하지만, 미리 구현된 Parallax Mapping을 사용하면 간단하게 구현 가능
  • Parallax Maping의 출력을 모든 텍스쳐의 UV에 연결해주고, Amplitude를 20 정도로 올려주면 효과가 잘 보인다.

Parallax Mapping(시차 맵핑)?
시야각에 따라 UV를 이동시켜 깊이감을 흉내내는 기법.
실제로 vertex를 이동시키지는 않고, Normal map으로 표현하기 힘든 기울어진 표면에서 입체감을 강화시켜주는 효과를 보여줌.
기울기에 의한 차폐까지 계산되는 Paorallax Occlusion이라는 기술도 있음.

02 Detail Map

Detail Map : 근접했을 때 흐려지는 텍스쳐의 디테일을 보완하고자 만든 기법

  • 내장 Lit 셰이더 Inspector > Detail Inputs에서 사용할 수 있는 기능이다.
  • Tiling And Offset으로 Tiling을 (20,20) 적용한 DirtDetail 텍스쳐를 Blend 노드로 합쳐준다
    • 이때 Detail Map은 결과물의 밝기의 변화를 주지 않기 위해 sRGB 옵션을 꺼야 하고, 이미지도 회색이다.
    • Detail Map의 Normal Map을 블랜딩할 땐 Normal Blend 노드를 사용한다. (Normal Map은 데이터 텍스쳐이기 때문에, 그냥 블랜딩으로 합치면 값이 손실되거나 옳지 않은 벡터가 나올 수 있다)
  • 사람의 모공 등에서 사용할 수 있다
    • Lit Shader > Detail Inputs > Maks 옵션이 이때 쓰인다. 입술과 같은 부분은 Detail Map이 나오지 않도록 Masking을 할 수 있다.

03 [응용하기] 돌이 젖고 마르는 기능을 만들어 봅시다

돌이 젖는다는 것은,

  • 돌 위에 물 레이어가 한 겹 올라가는 것
  • 물 레이어가 돌의 요철을 메꾸어 매끈하게 만들어 정반사가 심해지게 만든다
  • 물의 굴절율에 따라 많은 양의 빛을 반사해서 표면이 어두워진다
  • 물과 표면 사이에서 한 번 더 내부적으로 전반사가 이루어져 색이 강해진다

정리하면,

  • 텍스쳐가 어두워지고
  • 색이 진해지고
  • 정반사가 높아진다

  • Base Color에 Base Color를 곱해서 색을 진하고 어둡게 만들고,
  • Smoothness를 0.8로 올려서 정반사를 올리는 것만으로

간단하고 효율적으로 젖은 표면을 만들 수 있다.

  • Polybrush로 윗면을 칠하고
  • Vertex Color의 칠한 색깔 채널로
    • 원래 색 / Multiply로 어둡게 만든 색을 Lerp하고
    • Smoothness에 연결해주면

색을 칠한 윗 부분만 어두워지고 정반사가 늘어난다.

Troubleshooting
책에서 말하는대로 윗부분만 어두워지는게 아니라 아래 부분도 어두워지길래 다시 한 번 확인해봤더니, 책에선 색을 칠하지 않은 부분은 검은색으로 칠해져있었다. 나는 검은색을 칠해두지 않아서 윗부분과 아래 부분의 차이가 없었다.

Lerp에 연결하는 값에 Multiply로 계수를 조절해줄 수도 있다.

Part 12 | Digital Light 이론

01 디지털 라이트의 분류

컴퓨터 그래픽 라이트 기본 3가지 종류

  • Directional Light : 조명 강도, 칼라와 '방향'밖에 없는 조명. 시작점도 끝점도 없다.
  • Point Light : 사방으로 뻗어나가는 조명. 성능에 신경써서 작업해야함.
  • Spot Light : 특정한 부분을 강조할 때 사용하는 조명.

02 디지털 라이트의 원리

조명의 방향 벡터와 물체의 노말 벡터(법선 벡터)가 서로 마주보고 있을 때 그 면은 가장 밝다.

04 벡터의 특성

숫자로 색상, 위치, 방향을 표현할 수 있다.

Vector3(1,0,0)은 빨강으로도, X축 방향 길이 1인 화살표로도 표현될 수 있다.
Vector3(0,1,0)은 녹색으로도, Y축 방향 길이 1인 화살표로도 표현될 수 있다.
Vector3(0,0,1)은 파랑으로도, Z축 방향 길이 1인 화살표로도 표현될 수 있다.

Normal Map의 RGB 칼라도 같은 느낌으로 보면 된다.

05 노말 벡터 (Normal Vector)

노말 벡터와 라이트 벡터가 서로 마주보면 가장 밝고, 각도가 벌어지면 점점 어두워지는데,
노말 벡터가 면에 있으면 부드러운 라이팅을 구현할 수 없으므로,
요즘에는 노말 벡터가 버텍스에 있다. 버텍스 사이의 밝기 값은 보간으로 표현한다.

그런데 보간을 해버리면 각지게 표현할 때 버텍스 주변에 있는 면들이 모두 다른 밝기를 가질 수가 없다.
그래서 요즘의 게임 엔진은 각진 물체를 표현하기 위해 버텍스를 복사해서 같은 자리에 추가하여 표현한다.
즉, 각지게 표현하면 요즘엔 버텍스 양이 오히려 늘어난다.

07 벡터 연산을 이용한 디지털 조명 연산

가장 밝은데 -1이면 곤란하니까 연산하기 전에 조명 벡터 방향을 반대로 해준다.
(유니티에선 조명 벡터가 뒤집힌 채로 받아오기 때문에 안 뒤집어줘도 된다)

램버트 (Lambert) 라이팅 : 조명 벡터랑 노말 벡터 내적. Diffuse Light를 표현하는 가장 흔한 방식.

Part 13 | 커스텀 라이트 - Lambert Light

Shader Graph의 라이팅 계산법은 Lit와 Unlit 정도.

Lit 셰이더는 퀄리티는 좋지만 모바일에선 약간 무거움.
물리적으로 옳지 않은 창의적 효과 표현 못 함.
라이팅 구조를 커스텀으로 조정해서 창의적 표현해야됨.

이를 위해 SimpleLit이라 부르는 전통적 라이팅 구조를 만들어보는 실습할 것.

01 커스텀 라이트 기본형 만들기

라이팅 구조를 새로 만들어야 하니 아무 라이팅 연산 없는 Unlit Shader Graph를 만든다.
Normal Vector 노드를 추가한다.
Light Vector를 받아오기 위해 Custom Function 노드를 추가한다.
Custom Function 노드는 직접 HLSLI 코딩으로 노드에 없는 기능을 작성하는 노드이다.

Normal Vector 노드 Space 옵션

  • Object : 로컬 좌표계
  • View : 카메라 좌표계
  • World : 월드 좌표계
  • Tangent : 각 면이 가지고 있는 좌표계. 모두 0,0,1이라 시각화하면 전부 파랗다. 3D를 2D처럼 생각하는 좌표계이므로 노말맵에서 주로 사용된다.

Custom Function 노드의 Graph Inspector 옵션

  • Precision : 사용될 값의 정밀도. 일반적으론 높은 품질의 Single로 해도 괜찮다.
    • HLSL 함수 이름에 Precision에 맞춰서 자동으로 접미어가 붙는다
    • 출력 변수의 단위가 Precision과 맞지 않으면 에러가 발생한다
    • Inherit : 해당 노드 이전의 노드에서 상속받은 정밀도로 설정
    • Single : Float 32비트
    • Half : 16비트
  • Preview : 노드 프리뷰 2D로 볼건지 3D로 볼건지
  • Inputs / Outputs : 함수 입력값과 출력값
  • Type
    • File : 외부 HLSL 파일을 연결
    • String : HLSL 코드를 노드에 직접 작성

Custom Function의 Precision은 Single, Preview는 Preview3D, Outputs는 Direction이라는 이름을 가진 Vector3로 설정해놓고

#ifdef SHADERGRAPH_PREVIEW
	Direction = float3(1,1,1);
#else
	Light light = GetMainLight();
	Direction = light.direction;
#endif

Type을 String으로 바꾼 뒤 Name은 CustomLight, Body에는 위의 코드를 적어준다.
이후 Directional Light의 각도를 바꿔주면 색깔도 바뀌는 것을 확인할 수 있다.

코드 설명

  • 프리뷰면 direction은 float3(1,1,1)로 둬라 (분홍색이면 보기 안 좋으니까 설정)
  • Precision을 Half로 했다면 float3가 아니라 half3로 써줬어야 했을 것
  • light를 받아서 방향을 direction에 할당
  • 받아오는 light는 MainLight, 즉 Directional Light. 다른 라이트들은 Additional 라이트라 불리고, 받아오는 방법도 다르다.

Troubleshoting
책에서 하라는대로 Outputs에 변수를 추가하긴 했는데 이름을 Direction으로 안 바꿔서 변수가 할당되지 못하는 오류가 생겼었음.

Normal Vector와 Dot Product 노드를 사용해 내적해주면 빛이 연산된다.
내적 연산하면 -1 값도 나오는데, 이를 Saturate, Maximum, Clamp 노드 등을 이용해 0으로 잘라준다.
앰비언트 라이트나 추가 라이트를 비출 때 음수에 숫자를 더하게 되어서 밝아지지 않는 등 문제가 생길 수 있기 때문이다.

버텍스의 Normal Vector를 Normal Map 텍스쳐로 대체해주면 디테일이 살아난다.
이 과정에서 Tangent Space인 Normal map을 World Space로 바꿔주기 위해 Transform 노드를 이용한다.
Transform 노드에서 Type도 Direction으로 바꿔줘야 함.

래버트 라이트는 가벼운 조명 공식이지만, cos 그래프 특성상 밝다가 너무 급격하게 음영이 떨어진다.
하프 램버트 공식은 물리적으론 옳지 않지만 보기 좋기 때문에 만들어졌다.

이전의 NdotL에서 Saturate를 통해 무시해줬던 음수 부분을 끌어올리기 위해
*0.5+0.5를 추가로 연산해주면 음영이 조금 더 부드러워진다.

실제로 사용할 땐 빛이 180도까지 미치다보니 음영이 너무 부드러워져서
3제곱 혹은 *0.7+0.3 정도로 사용한다. (저자는 *0.7+0.3가 더 가벼워서 좋아한다고 함)

NdotL?
N(Normal)과 L(Light)를 dot(내적)한 결과물

02 램버트 라이트 완성해 보기

지금까지 구현한 것 : 빛 방향에 따른 음영
구현해야 할 것 : 조명의 색상과 강도, Albedo 텍스쳐, 환경광 등등

  • Base Color 텍스쳐를 꺼내서 NdotL 결과물과 곱해준다
  • 커스텀 노드에 Color = light.color;을 추가하여 빛의 색깔을 받아온다
  • Color를 NdotL 결과물과 곱해주는 과정을 추가한다

이제 조명의 색상과 밝기를 변화하면 모델의 색상이 변하는 것을 확인할 수 있다.
이것은 '빛을 받는 부분은 자신이 가진 색상을 사방으로 방출해서 물체의 색이 보이게 된다'는 난반사(Diffuse Reflection)를 NdotL로 간단하게 구현한 것.

환경광

모든 난반사 물체는 자신의 색의 빛을 사방으로 연하게 반사한다.
이렇게 한 번 이상 반사된 빛이 다른 물체에 닿아 그 물체를 밝히면 이것을 환경광(Ambient Light)라 한다.
환경광이 아주 강해서 조명처럼 색이 묻어나는 것을 컬러 블리딩(Color Bleeding)이라 한다.

아주 맑은 날 야외라면 하늘에는 파란 환경광, 대지에는 지면 색의 환경광이 보일 것.
흐린 날에는 주광도 약해져서, 물체에 음영이 강하지 않아 환경 차폐(Ambient Occlusion)만 강조되어 보일 것.

유니티는 스카이 박스에 환경광이 있다. 이를 받아오려면 Baked GI 노드를 사용하면 된다.

  • World Space Normal을 연결한 Baked GI를 텍스쳐와 곱해준다
  • 그 결과물을 Diffuse 연산에 Add해준다

Ambient Occlusio은 Ambient에 곱해주면 된다.

남은 구현할 것

  • 스페큘러(정반사)
  • 그림자 드리우기
  • 그림자 받기
profile
너 정말 **핵심**을 찔렀어

0개의 댓글