Home 4. 커스텀 라이트와 정적분기
Post
Cancel

4. 커스텀 라이트와 정적분기

Rambert

램버트 라이트 모델은 간단하게 표면의 법선빛의 방향내적하여 나타내어집니다.

즉 관찰자 입장에서, 빛과 어떻게 보느냐에 따라 해당 영역의 밝기가 나타내어 지는 것이죠.

Untitled

그걸 위해서는 일단 빛을 받아와야합니다.

Custom Light

יְהִי אוֹר (창세기 1장 3절)

월드에는 사실 빛이 하나 있을 수도 있습니다.

그 빛을 통해 저희는 연산을 진행해야합니다.

그러면 그 빛을 가져올 수 있을까요?

코드로는 가능하지만 노드로는 그것을 받아올 수 없죠. (작성 기준 : 12.1.6)

Untitled

그러니 코드를 작성해줍니다.

…말이 안됩니다.

엄청작은 텍스트블럭에 엄청많은 내용을 적어야 합니다.

어 그러면 파일로 작성해서 읽어오면 안되나?

답은 “가능하다”입니다.

shaderFiel.gif

그러면 이렇게 받아온 파일은 어떻게 처리되는걸까요?

자. 확인을 위해 쉐이더 그래프를 제네레이션을 하면 다음과 같이 됩니다.

쉐이더그래프에서 생성된 쉐이더코드 쉐이더그래프에서 생성된 쉐이더코드

여기서 #include는 결론적으로 해당 파일을 복사해서 붙여넣기 하였다고 보면 됩니다.

일반적인 프로그래밍에서의 include와 동일하죠.

(C++에서는 그렇게 순환참조오류가 발생하고…)

그런데 이런 내용들을 쉐이더그래프로만 구성할 수 없을까요?

답은 “가능하다”입니다.

서브그래프

저는 NdotL을 너무나도 좋아합니다.

하지만 이거 너무 귀찮은 것 있죠??

그래서 함수를 하나 만들고 싶었습니다.

NDotL.shadersubgraph NDotL.shadersubgraph

Untitled

이렇게 사용할 수 있습니다.

어라? 그런데 겨우 저정도에 저런걸 써도 되나요?

라고 물으신다면 예외를 몇개 던져봅니다.

노말이 만약 정규화 되어지지 않았다면?

빛의 방향이 정규화 되어지지 않았다면?

출력값이 0~1의 값임을 보장받고 싶다면?

그걸 이제 조절해서 다시 쉐이더그래프를 변경해볼까요?

Untitled

그래프는 위측과 같이 복잡해져야합니다.

하지만 이걸 쉐이더’서브그래프’에서 처리한다면?!??!?!

(subGraph, NDotL) (subGraph, NDotL)

Untitled

여전히 똑같이 사용할 수 있습니다.

즉 여기는 프로그래밍의 설계원칙과 똑같습니다.

재사용 가능할 수 있게, 아주 작은 단위의 기능으로 구성하여 둔다면,

훨씬 효율적으로, 직관적으로 작업할 수 있게 됩니다.

흥미롭지 않나요? 프로그래밍 하실래요?

벡터 (Vector)

그러면 일단 좌표개념부터 들어갑시다.

https://youtu.be/fNk_zzaMoSs?t=175

벡터는 화살표입니다. 어느 방향으로 얼마만큼 이라는 정보를 가진 화살표 입니다. 이 화살표에게 크기를 늘린다는것은, 진행하는 방향을 하나로 축으로 하는 직선에서의 크기입니다.

즉 벡터의 공간은 1차원 직선입니다.

벡터의 정규화(Normalize)

정규화는 단순히 크기(노름)를 1로 한 벡터를 말합니다.

방향성만 가지고 있다고 봐야겠죠.

식은 다음과 같이 작성됩니다.

1
2
3
4
5
6
void Normalize(this Vector2 vector)
{
	  var norm = sqrt(vector.x * vector.x + vector.y * vector.y)
	  vector.x /= norm;
	  vector.y /= norm;
}

내적 (Dot product)

이에 관해서는 예전에 쓴 글 을 참조하시면 좋을 것 같습니다.

간단하게 저희가 이제 사용할 내적은,

정규화 된 벡터 두개의 유사도를 구한고 생각하시면 편합니다.

https://ashuatz.github.io/assets/(Graphic)4/Untitled%208.png

조금 깊게 들어가면, 왜 그렇게 되는지 한번 (과거에) 정리해보았습니다

https://ashuatz.github.io/assets/(Graphic)4/Untitled%209.png

따라서 정규화된, 노름이 1인 두 벡터에 대해서는

두 벡터의 내적은 cos $\theta$값이다 라는 것을 알 수 있습니다.

그리고 cos(0)의 값은 1이고, cos(180deg)의 값은 -1이니, 대충 눈치를 챌 수 있죠.

그러면 이제 대충 램버트를 구성하는 것들에 대해서는 대충 알았으니,

저번주 과제에 숨겨진 무언가에 대해 썰을 풀 시간입니다.

좌 : 보이는 영역을 고정, 우: 색상값을 고정 좌 : 보이는 영역을 고정, 우: 색상값을 고정

사실 이렇게 처리한다면, 너무 어둡게 된 부분을 살리고,

전체적인 명암의 연속성은 비슷하게 가져갈 수 있습니다.

이런걸 제가 생각했냐고요? 절대아니죠.

half-lambert

Half Lambert 공식은 Value 사에서 Half-Life에서 처음 소개된 방식으로 오브젝트가 너무 어둡게 나오는 것을 방지하기 위한 목적으로 사용되었습니다. (https://developer.valvesoftware.com/wiki/Half_Lambert)

Untitled

그래프로 표현하면 다음과 같습니다.

쉬우니까 빨리빨리 넘어갈께요.

Untitled

그리고 본다면 다음과 같이 처리됩니다. 확실히 덜어둡죠?

halflambert.gif

어라 그런데 이상한게 있습니다.

그림자가 안그려지네? 그림자가 안그려지네?

문제점 : 그림자는 안그려지는데?

뭔가 안된다. 그러면 대처방법은 크게 두가지로 나뉩니다

  1. 포기한다
  2. 뜯어본다

쉐이더를 뜯어봅시다.

겁먹지 않아도 됩니다. 그냥 몇가지 글자들만 좀 검색해볼 뿐입니다.

시작합시다. 어디로가냐고요? 쉐이더파일로요.

어떻게아냐고요? 그러게요…

깊고 깊은 경로. 깊고 깊은 경로.

(혹은 프로젝트 내의 캐시파일로도 접근할 수 있습니다.) (혹은 프로젝트 내의 캐시파일로도 접근할 수 있습니다.)

Untitled

사용부는 찾았지만 실제 내용은 못찾았습니다.

그러면 아까 보았던 include를 통해 다른 파일에서 가져오는 것 같습니다.

뒤져봐야겠죠?

Untitled

저희가 사용하고 있는건

Untitled

shadowCoordination을 인자로 던지는 GetMainLight입니다.

realtimeLights.hlsl

realtimeLights.hlsl

찾았습니다. 그런데 또 찾아야하네요?

또 이 파일에 없습니다. include를 뒤지고, 찾고를 반복합니다.

이걸 찾아 냅니다. (아마 shadow.hlsl이였나?)

이걸 찾아 냅니다. (아마 shadow.hlsl이였나?)

만약 MAIN_LIGHT_CALCULATE_SHADOWS 가 없다면 그냥 1을 반환하는군요!

그럼 MAIN_LIGHT_CALCULATE_SHADOWS 를 define하는건 무슨소리일까요??

키워드 : 정적분기


Untitled

네.

여기서 말하고있는 분기가 아까 보았던 #if 구문입니다.

이런 분기를 지정할 수 있는 방법들이 있는데, 크게 두가지로 나뉩니다.

shader featrue

사용되지 않는 파생(배리언트)은 빌드에 물리지 않습니다.

약간 Resources폴더 외에 존재하는 리소스의 느낌이라고 볼 수 있죠.

주로 머테리얼 단위에서 처리할 수 있는게 좋습니다.

예를들어 알파테스트, 깊이테스트, 노말맵 존재유무, 이미션맵 존재유무 등이 있겠죠.

multi complie

모든 파생이 빌드에 물립니다.

따라서 모든 경우의 수의 파생을 가지죠.(2의 N승..)

약간 Resources폴더 내에 존재하는 리소스의 느낌이라고 볼 수 있습니다.

이런 MultiComple전역(글로벌)설정일 때 좋음

그림자, 메인라이트의 그림자 유무, cascade 등이 있겠네요.

확인

자 그렇게 추가된 키워드는 어디서 확인할 수 있을까요?

유니티의 debug모드는 모든것을 알고 있습니다.

(사실 다 모릅니다. 구림)

쉐이더 (혹은 쉐이더그래프)가 가진 정보를 뽑아내서 보여주는 것이

parsed form(구문 분석된 형태)인데,

추가한 키워드 프로퍼티와

이미 사용중인 키워드들을 다 볼 수 있습니다.

Untitled

제한

이러한 쉐이더 배리언트는 유니티 프로젝트 전체의 ‘전역 배리언트’가 최대 256개(2^8)로 제한되어 있는데다가 약 60개의 전역 배리언트가 이미 유니티 프로젝트 내부에서 디폴트로 사용되고 있기 때문에 제한을 초과하여 오류가 나지 않게 해야 합니다.

그래서 해결책으로 쉐이더마다 제공되는 ‘로컬 배리언트’ 최대 64개(2^6)를 사용하면 됩니다.

추가 : 2021.2버전에서는 갯수가 달라졌습니다.

Untitled

  • shader_feature_local
  • multi_compile_local

아무튼 그러면 키워드를 적용하면 그림자가 그려진다는 소리인가요???

네 잘 그려집니다.

추가적인 몇개의 옵션이 있어서, 더 들여야겠지만 된다는 소리입니다.

  • _MAIN_LIGHT_SHADOWS_CASCADE : cascade(그림자 단계)를 적용하는가
  • _SHADOWS_SOFT : 부드러운 그림자를 그리는가

Untitled

SRP : batching(draw call) and set pass call

드로우콜과 셋패스콜쪽을 알고계십니까?

긴말하지 않겠습니다. 한번 드셔보십시오.

유니티 에반젤리스트 ozlael(오지현)님의 2016 유나이트 슬라이드입니다.

아무튼SRP에서는 머테리얼 기준(기존 DrawCall) 이 아닌

쉐이더 기준으로 연산부하를 계산하는데,

따라서 이 Set pass call이 장착중인 쉐이더를 한번 바꾸는 비용이라고 봐야합니다.

그런데 키워드를 넣어서 렌더링을 한프레임 한프레임 찾아보면 무언가 이상합니다.

묶이지 않음. Node use different shader keywords 라고 들어보셨습니까? 묶이지 않음. Node use different shader keywords 라고 들어보셨습니까?

최적화를 하는데 맨날 배칭이 깨져서 보면 키워드가 다르다고 합니다.

잘 보면 enum_71d어쩌구의 마지막글자가 다른것을 알 수 있습니다.

그렇습니다.

정적분기로 인해 달라지는 쉐이더들은 같은 쉐이더라고 할 수 없는 것입니다.

응용

계속 이론얘기만 했더니 너무 싫습니다.

CustomLight를 사용한 Toon Shader

Untitled

toon.gif

맺으며

쓸게 많아서 정리가 제대로 안된 것 같아 아쉽습니다.

조금 더 깊은 내용들과 조금 더 다양한 이야기를 하고 싶지만

이정도에서 마무리 짓도록 하겠습니다..

This post is licensed under CC BY 4.0 by the author.
Contents