WinCNT

URP로 Toon Shader 만들기 -Three(Two) Tone Shading 2- 본문

Unity/URP or Shader 관련

URP로 Toon Shader 만들기 -Three(Two) Tone Shading 2-

WinCNT_SSS 2023. 2. 6. 16:21

저번 글에 이어서 Power와 Feather로 컨트롤하는 Three Tone Shading의 구현에 대해서 정리하고자 한다


Power와 Feather로 컨트롤하는 Three Tone Shading의 구현

특별한 것 마냥 따로 빼긴 했지만 개념 자체는 다른 방법과 똑같다

즉, 왼쪽(Diffuse)의 값을 오른쪽으로 바꾸는 것!

다만 일반적인 Lambert가 아니라 Half Lambert를 사용하며,
Power와 Feather란 프로퍼티로 음영의 경계선을 컨트롤한다는 점이 다를 뿐

 

일단 하나씩 살펴보자


Half Lambert

왼쪽이 Lambert, 오른쪽이 Half-Lambert

Half Lambert에 대해서는 다른 좋은 글이 너무 많으므로 자세한 설명은 생략한다

(참고 사이트를 참고해주세요)

위키피디아에 따르면 Half Lambert는 서브서피스 스캐터링(Subsurface scattering, SSS)의 간이 NPR모델로 사용된다고 기재되어 있다

(물리적으로 올바르지 않지만 카툰 렌더링은 물리적 올바름을 추구하지 않으니 상관 없음)

코스트도 얼마 들지 않으면서 화사한 느낌을 줄 수 있다고 한다

 

뭐 일단 어려운 설명은 둘째치고 평범하게 Lambert로 만들면 음영이 차지하는 부분이 너무 커서 Power 프로퍼티의 조정 값이 너무 한쪽으로 쏠리게 된다…

실제 사용할 것을 생각하면 Half Lambert가 여러모로 적당하다고 할 수 있다


Power와 Feather

Power는 음영의 범위, Feather는 경계선이 흐린 정도를 조정하는 프로퍼티로 정의한다

step()와 smoothstep()로 비교하자면 Power은 1로 스텝하는 경계선을 컨트롤하고,
Feather는 0과 1의 경계의 보간 레벨을 컨트롤한다

Power는 0 ~ 1를 범위를, Feather는 0.0001 ~ 1의 범위로 둔다

(Feather는 나누기에 사용하기 때문에 0이 되면 안 됨)

 

실제로 사용한 코드와 그로 인한 결과 그래프는 다음과 같다

float y(float x) {
  float shadePow = 0.5;
  float shadeFeather = 0.0001;
  float halfLambert = 0.5 * cos(x) + 0.5;
  
  float shadeMask = (shadePow - halfLambert) / shadeFeather;
  shadeMask = saturate(shadeMask);
  
  float ret = mix(0.0, 1.0, shadeMask); // mix == lerp(HLSL)
  return ret;
}

float y(float x) {
  float shadePow = 0.7;
  float shadeFeather = 0.1;
  float halfLambert = 0.5 * cos(x) + 0.5;
  
  float shadeMask = (shadePow - halfLambert) / shadeFeather;
  shadeMask = saturate(shadeMask);
  
  float ret = mix(0.0, 1.0, shadeMask); // mix == lerp(HLSL)
  return ret;
}

위의 코드를 이용하니 step()과 smoothstep()와 비슷한 그래프를 만들 수 있었다

그리고 Power와 Feather 값의 변화로 그래프를 컨트롤할 수 있다는 것도 확인할 수 있었다


Half Lambert를 뒤집는 이유?

사실 위의 그래프는 step(), smoothstep()의 그래프와 다른 점이 있다

바로 그래프의 결과가 뒤집혀 있다(-1를 곱하)는 것!

 

실제로 위의 값을 출력해보면 아래와 같은 느낌이 된다

실제로 음영이 생길 부분은 하얗고 반대로 밝아야할 부분은 어두워진 것을 알 수 있다

왜 이런 짓을 하는 걸까……안 뒤집고 그대로 쓰면 안 되나?

 

개인적인 추측이지만 Half Lambert를 뒤집는 이유는 음영을 만들기 위해서가 아니라
음영인 부분을 판정하기 위해 Half Lambert를 뒤집는 것으로 생각된다

즉, 계산 결과가 하얀색(1.0)이면 음영으로, 검정색(0.0)이면 기본색이라 판정하는 것이다

(물론 이는 코딩하는 사람이 바꿀 수 있는 기준이라고 생각한다)


Three Tone Shading 구현하기

Two Tone Shading이면 위의 방법으로 이미 끝나지만, Three Tone Shading이면 한 단계 더 필요하다

 

그렇다고 딱히 어려운 공정이 필요한 것은 아니다

먼저 1음영과 2음영 중 어느 것을 음영의 색으로 할 것인지 결정하고,

그 음영 색과 기본 색 중 어느 색으로 최종적으로 출력할 것인지 결정하면 된다

// 1음영과 2음영 중 어느 것을 음영의 색으로 할 것인지 결정
const float _Shadow1And2Mask = saturate((_ShadowPower2 - _HalfLambert) / _ToonFeather1stTo2nd);
half4 _FinalBaseColor = lerp(_ShadowColor1, _ShadowColor2, _Shadow1And2Mask);

// 음영 색과 기본 색 중 어느 색으로 최종적으로 출력할 것인지 결정
const float _FinalShadowMask = saturate((_ShadowPower1 - _HalfLambert) / _ToonFeatherBaseTo1st);
_FinalBaseColor = lerp(_BaseColor, _FinalBaseColor,_FinalShadowMask);

픽셀 쉐이더에서 위의 코드를 구현하면 아래와 같은 Two Tone Shading를 표현할 수 있다

기본 색 : Red, 1음영 : Green, 2음영 : Blue로 설정


마무리

이것으로 Three Tone Shading 구현에 대한 기본적인 내용을 정리해보았다

사실 실제로 색을 지정하는 경우는 거의 없고 텍스처를 사용하는 경우가 대부분이겠지만

그에 대해서는 나중에 실제로 구현할 때 다시 정리해 보려고 한다

다음에는 스펙큘러 라이트나 림 라이트에 대해 정리해보려고 한다


참고 사이트

https://gamedevforever.com/150

 

Wrapped Diffuse

오랜만이네요. 한 동안 멘탈이 붕괴되어서, 방황하다가 우리 아가랑 신나게 놀다가 정신차리고 돌아왔습니다. ㅎㅎ슬슬 GDC12의 기술문서들이 올라오네요.. ㅎㅎㅎ.. 비록 GDC는 못 갔지만, 대충

gamedevforever.com