4. 커스텀 라이트와 정적분기
Rambert
램버트 라이트 모델은 간단하게 표면의 법선과 빛의 방향을 내적하여 나타내어집니다.
즉 관찰자 입장에서, 빛과 어떻게 보느냐에 따라 해당 영역의 밝기가 나타내어 지는 것이죠.
4/Untitled.png)
그걸 위해서는 일단 빛을 받아와야합니다.
Custom Light
יְהִי אוֹר (창세기 1장 3절)
월드에는 사실 빛이 하나 있을 수도 있습니다.
그 빛을 통해 저희는 연산을 진행해야합니다.
그러면 그 빛을 가져올 수 있을까요?
코드로는 가능하지만 노드로는 그것을 받아올 수 없죠. (작성 기준 : 12.1.6)
4/Untitled 1.png)
그러니 코드를 작성해줍니다.
…말이 안됩니다.
엄청작은 텍스트블럭에 엄청많은 내용을 적어야 합니다.
어 그러면 파일로 작성해서 읽어오면 안되나?
답은 “가능하다”입니다.
4/shaderFiel.gif)
그러면 이렇게 받아온 파일은 어떻게 처리되는걸까요?
자. 확인을 위해 쉐이더 그래프를 제네레이션을 하면 다음과 같이 됩니다.
4/Untitled 2.png)
여기서 #include는 결론적으로 해당 파일을 복사해서 붙여넣기 하였다고 보면 됩니다.
일반적인 프로그래밍에서의 include와 동일하죠.
(C++에서는 그렇게 순환참조오류가 발생하고…)
그런데 이런 내용들을 쉐이더그래프로만 구성할 수 없을까요?
답은 “가능하다”입니다.
서브그래프
저는 NdotL을 너무나도 좋아합니다.
하지만 이거 너무 귀찮은 것 있죠??
그래서 함수를 하나 만들고 싶었습니다.
4/Untitled 3.png)
4/Untitled 4.png)
이렇게 사용할 수 있습니다.
어라? 그런데 겨우 저정도에 저런걸 써도 되나요?
라고 물으신다면 예외를 몇개 던져봅니다.
노말이 만약 정규화 되어지지 않았다면?
빛의 방향이 정규화 되어지지 않았다면?
출력값이 0~1의 값임을 보장받고 싶다면?
그걸 이제 조절해서 다시 쉐이더그래프를 변경해볼까요?
4/Untitled 5.png)
그래프는 위측과 같이 복잡해져야합니다.
하지만 이걸 쉐이더’서브그래프’에서 처리한다면?!??!?!
4/Untitled 6.png)
4/Untitled 7.png)
여전히 똑같이 사용할 수 있습니다.
즉 여기는 프로그래밍의 설계원칙과 똑같습니다.
재사용 가능할 수 있게, 아주 작은 단위의 기능으로 구성하여 둔다면,
훨씬 효율적으로, 직관적으로 작업할 수 있게 됩니다.
흥미롭지 않나요? 프로그래밍 하실래요?
벡터 (Vector)
그러면 일단 좌표개념부터 들어갑시다.
https://youtu.be/fNk_zzaMoSs?t=175
벡터는 화살표입니다. 어느 방향으로 얼마만큼 이라는 정보를 가진 화살표 입니다. 이 화살표에게 크기를 늘린다는것은, 진행하는 방향을 하나로 축으로 하는 직선에서의 크기입니다.
즉 벡터의 공간은 1차원 직선입니다.
벡터의 정규화(Normalize)
정규화는 단순히 크기(노름)를 1로 한 벡터를 말합니다.
방향성만 가지고 있다고 봐야겠죠.
식은 다음과 같이 작성됩니다.
void Normalize(this Vector2 vector)
{
var norm = sqrt(vector.x * vector.x + vector.y * vector.y)
vector.x /= norm;
vector.y /= norm;
}
내적 (Dot product)
이에 관해서는 예전에 쓴 글 을 참조하시면 좋을 것 같습니다.
간단하게 저희가 이제 사용할 내적은,
정규화 된 벡터 두개의 유사도를 구한고 생각하시면 편합니다.
4/Untitled 8.png)
조금 깊게 들어가면, 왜 그렇게 되는지 한번 (과거에) 정리해보았습니다
4/Untitled 9.png)
따라서 정규화된, 노름이 1인 두 벡터에 대해서는
두 벡터의 내적은 cos 값이다 라는 것을 알 수 있습니다.
그리고 cos(0)의 값은 1이고, cos(180deg)의 값은 -1이니, 대충 눈치를 챌 수 있죠.
그러면 이제 대충 램버트를 구성하는 것들에 대해서는 대충 알았으니,
저번주 과제에 숨겨진 무언가에 대해 썰을 풀 시간입니다.
4/Untitled 10.png)
사실 이렇게 처리한다면, 너무 어둡게 된 부분을 살리고,
전체적인 명암의 연속성은 비슷하게 가져갈 수 있습니다.
이런걸 제가 생각했냐고요? 절대아니죠.
half-lambert
Half Lambert 공식은 Value 사에서 Half-Life에서 처음 소개된 방식으로 오브젝트가 너무 어둡게 나오는 것을 방지하기 위한 목적으로 사용되었습니다. (https://developer.valvesoftware.com/wiki/Half_Lambert)
그래프로 표현하면 다음과 같습니다.
쉬우니까 빨리빨리 넘어갈께요.
4/Untitled 12.png)
그리고 본다면 다음과 같이 처리됩니다. 확실히 덜어둡죠?
4/halflambert.gif)
어라 그런데 이상한게 있습니다.
4/Untitled 13.png)
문제점 : 그림자는 안그려지는데?
뭔가 안된다. 그러면 대처방법은 크게 두가지로 나뉩니다
- 포기한다
- 뜯어본다
쉐이더를 뜯어봅시다.
겁먹지 않아도 됩니다. 그냥 몇가지 글자들만 좀 검색해볼 뿐입니다.
시작합시다. 어디로가냐고요? 쉐이더파일로요.
어떻게아냐고요? 그러게요…
4/Untitled 14.png)
(혹은 프로젝트 내의 캐시파일로도 접근할 수 있습니다.)
4/Untitled 16.png)
사용부는 찾았지만 실제 내용은 못찾았습니다.
그러면 아까 보았던 include를 통해 다른 파일에서 가져오는 것 같습니다.
뒤져봐야겠죠?
4/Untitled 17.png)
저희가 사용하고 있는건
4/Untitled 18.png)
shadowCoordination을 인자로 던지는 GetMainLight입니다.
4/Untitled 19.png)
realtimeLights.hlsl
찾았습니다. 그런데 또 찾아야하네요?
또 이 파일에 없습니다. include를 뒤지고, 찾고를 반복합니다.
4/Untitled 20.png)
이걸 찾아 냅니다. (아마 shadow.hlsl이였나?)
만약 MAIN_LIGHT_CALCULATE_SHADOWS 가 없다면 그냥 1을 반환하는군요!
그럼 MAIN_LIGHT_CALCULATE_SHADOWS 를 define하는건 무슨소리일까요??
키워드 : 정적분기
4/Untitled 21.png)
네.
여기서 말하고있는 분기가 아까 보았던 #if 구문입니다.
이런 분기를 지정할 수 있는 방법들이 있는데, 크게 두가지로 나뉩니다.
shader featrue
사용되지 않는 파생(배리언트)은 빌드에 물리지 않습니다.
약간 Resources폴더 외에 존재하는 리소스의 느낌이라고 볼 수 있죠.
주로 머테리얼 단위에서 처리할 수 있는게 좋습니다.
예를들어 알파테스트, 깊이테스트, 노말맵 존재유무, 이미션맵 존재유무 등이 있겠죠.
multi complie
모든 파생이 빌드에 물립니다.
따라서 모든 경우의 수의 파생을 가지죠.(2의 N승..)
약간 Resources폴더 내에 존재하는 리소스의 느낌이라고 볼 수 있습니다.
이런 MultiComple전역(글로벌)설정일 때 좋음
그림자, 메인라이트의 그림자 유무, cascade 등이 있겠네요.
확인
자 그렇게 추가된 키워드는 어디서 확인할 수 있을까요?
유니티의 debug모드는 모든것을 알고 있습니다.
(사실 다 모릅니다. 구림)
쉐이더 (혹은 쉐이더그래프)가 가진 정보를 뽑아내서 보여주는 것이
parsed form(구문 분석된 형태)인데,
추가한 키워드 프로퍼티와
이미 사용중인 키워드들을 다 볼 수 있습니다.
4/Untitled 22.png)
제한
이러한 쉐이더 배리언트는 유니티 프로젝트 전체의 ‘전역 배리언트’가 최대 256개(2^8)로 제한되어 있는데다가 약 60개의 전역 배리언트가 이미 유니티 프로젝트 내부에서 디폴트로 사용되고 있기 때문에 제한을 초과하여 오류가 나지 않게 해야 합니다.
그래서 해결책으로 쉐이더마다 제공되는 ‘로컬 배리언트’ 최대 64개(2^6)를 사용하면 됩니다.
추가 : 2021.2버전에서는 갯수가 달라졌습니다.
4/Untitled 23.png)
- shader_feature_local
- multi_compile_local
아무튼 그러면 키워드를 적용하면 그림자가 그려진다는 소리인가요???
네 잘 그려집니다.
추가적인 몇개의 옵션이 있어서, 더 들여야겠지만 된다는 소리입니다.
- _MAIN_LIGHT_SHADOWS_CASCADE : cascade(그림자 단계)를 적용하는가
- _SHADOWS_SOFT : 부드러운 그림자를 그리는가
4/Untitled 24.png)
SRP : batching(draw call) and set pass call
드로우콜과 셋패스콜쪽을 알고계십니까?
긴말하지 않겠습니다. 한번 드셔보십시오.
유니티 에반젤리스트 ozlael(오지현)님의 2016 유나이트 슬라이드입니다.
아무튼SRP에서는 머테리얼 기준(기존 DrawCall) 이 아닌
쉐이더 기준으로 연산부하를 계산하는데,
따라서 이 Set pass call이 장착중인 쉐이더를 한번 바꾸는 비용이라고 봐야합니다.
그런데 키워드를 넣어서 렌더링을 한프레임 한프레임 찾아보면 무언가 이상합니다.
4/srpBatching.gif)
최적화를 하는데 맨날 배칭이 깨져서 보면 키워드가 다르다고 합니다.
잘 보면 enum_71d어쩌구의 마지막글자가 다른것을 알 수 있습니다.
그렇습니다.
정적분기로 인해 달라지는 쉐이더들은 같은 쉐이더라고 할 수 없는 것입니다.
응용
계속 이론얘기만 했더니 너무 싫습니다.
CustomLight를 사용한 Toon Shader
4/Untitled 25.png)
4/toon.gif)
맺으며
쓸게 많아서 정리가 제대로 안된 것 같아 아쉽습니다.
조금 더 깊은 내용들과 조금 더 다양한 이야기를 하고 싶지만
이정도에서 마무리 짓도록 하겠습니다..
4/Untitled 11.png)