이 글은 인터넷에서 찾은 자료와 강의를 들으면서 이해한 내용을 정리한 것입니다. 오류가 있을 수 있습니다. 정정할 부분이 있으면 댓글로 남겨주시면 감사하겠습니다.
참고 자료
Ultimate Trim Texturing Techniques : https://www.gdcvault.com/play/1022324/The-Ultimate-Trim-Texturing-Techniques
위의 GDC 자료를 가지고 설명해주는 영상 : https://www.youtube.com/watch?v=G_Mv-mFSHcM
Trim Sheet에 대한 디테일한 설명 : https://youtu.be/jd2G_TNB-PY?si=ULXIZhiLVAXBvTRs
먼저 Trim Sheet를 이용한 Bevel에 대해서 이해를 해야 한다.
Bevel은 왜 필요한가?
완전한 주관적 견해이지만 왜 Bevel이 들어간 3D 오브젝트가 단순한 큐브에 비해서 더 현실감있게 다가오고 더 퀄리티 있어 보이는지에 대해서 다름대로 생각해보았다.
* High Polygon 처럼 보이기 위한 필수적인 모서리 처리이기 때문이다.
현실에서 엄청 날카로운 물체가 아닌 이상 모두 조금은 마모되거나 둥근 형태로 물체가 구성된다. 면과 면 사이가 딱 끊기는 느낌으로 구성이 되면 셰이딩이 비현실적으로 딱딱 끊기는 느낌으로 출력된다. 그 로우폴 느낌이라고 느껴지는 3D 메쉬 티가 딱 나는 느낌으로 셰이딩이 된다.
그리고 다음과 같이 면과 면 사이를 구분 시켜주는 하이라이트 표현을 위해서 면과 면사이의 중간 면이 추가되어야 한다.
무엇이 로우 폴리곤 틱하고 무엇이 하이 폴리곤 틱한가?
하이 폴리곤 처럼 보이기 위해서는 다음 요소가 모델에 포함되어 있어야 한다.
* 묘사 (안쪽의 미세한 표면의 요철에 대한 표현)
이 표현은 모델링으로 표현할 수 없는 요소이다. 무조건 텍스처를 이용해 하이폴리곤의 요철을 만들어야 한다.
* 모서리, 또는 면과 면 사이의 경계 (물체의 외곽 라인을 형성하는 부분)의 형태
(곡률, 면이 얼마나 오목하고 볼록한가? 면과 면사이가 날카롭게 끊어지는가, 아니면 둥그스름하게 이어지는가?)
메쉬의 Normal 벡터 교정 및 추가적인 면을 사용해서 어느정도 보완할 수 있다.
현실과 비슷하게 면 사이가 부드럽게 연결된다면 둥그스름한 형태로 보여져야 하며,
90도로 날카롭게 각이 쪼개지더라도 사이 부분에 작게 마모된 듯한 중간 면(Bevel)이 들어가 있음을 표현해주어야 한다.
게임 모델링(특히 모바일)에 Bevel(중간면)을 추가하지 않는 이유
게임에서 사용하는 모델링의 표면은 Bevel이라고 불리는 중간 면을 삽입할 경우 결국 다음과 같은 문제가 있어 사용하기 어렵다.
* Triangle Count가 늘어나 퍼포먼스에 영향을 준다. (메모리 및 렌더링 성능)
구체적으로는 면이 추가됨으로써 증가하는 vertex 데이터로 인한 메쉬 데이터 용량이 증가하는 것을 꼽을 수 있는데, 모델 하나하나에 대해서 vertex가 좀 추가되었다고 해서 크리티컬하냐고 묻는다면 그건 아니다.
진짜 문제는 Bevel 특유의 얇고 긴 형태의 삼각형이 사용되어 발생하는 Fragment Shader 중복호출(Over-Shading)에 있다.
Triangle 수가 늘어날수록 렌더링 성능이 낮아지는 일반적인 이유와 일맥상통한데, 메쉬 하나를 그리는 하나의 Drawcall 명령 내에서도 삼각형 이음새 부분에 대해서 Fragment Shader를 중복호출하는 Rasterize Pipeline의 한계로 인해 발생하는 문제이다. 항상 2X2 타일 형태로 Thread Group을 만들어서 프로그램을 실행하다보니 삼각형이 실제로 1픽셀만 보이도록 Rasterizer에서 결정되었다고 해도 프로그램을 항상 4개를 실행해야 하는 특성에서 발생한다.
(자세한 내용은 Quad Overdraw / Over Shading 키워드로 검색)
래스터라이저에 문제를 야기하는 얇고 긴 삼각형 사용 관련 언급
https://simonschreibt.de/gat/renderhell-book3/
https://polycount.com/discussion/160794/the-ultimate-trim-technique-from-sunset-overdrive
면의 Normal을 제어하여 Fake Bevel 달성하기
렌더링 프로그램에서 라이팅에 필요한 메쉬데이터는 World Position도 일부 필요하지만 가장 중요한건 결국 Normal이다. 이 면이 바라보고 있는 방향이 어디인가를 통해 조명 방향과의 각도를 계산하여 빛이 얼마나 차폐될 것인지, 반사되는 빛의 양이 얼마나 될 것인지 결정한다.
Terrain과 그 위의 메쉬가 자연스럽게 파묻힌 것 처럼 보이도록 하는 등의 pixel depth offset 처리니 RVT를 이용한 Terrain Blending이라던지 하는 것들도 결국 Terrain의 Normal, BaseColor 등의 정보를 추가로 제공하여 Mesh위에 블렌딩해서 처리하는 것이고, 메쉬가 서로 떨어져 보이거나, 붙어있는 것처럼 보이거나 하는 것들은 결국 메쉬의 Normal 정보를 어떻게 제어하냐에 달려있다.
그러면 간단한 케이스부터 해결해보자.
실제로 다음과 같이 90도 꺾여 있는 면이 서로 갈라져보이지 않도록 하려면 어떻게 해야할까?
면이 갈라져 보이는 이유는 결국 모서리 부분의 노멀이 하나는 수직 위를 바라보고, 하나는 수평을 바라보도록 쪼개져 있기 때문이다. 다음과 같이 Normal 방향을 하나로 통일해주면 면이 쪼개져 있음에도 불구하고 하나로 이어진 것 처럼 보이게 된다.
Vertex 단위에서 Normal을 제어한다면 위와 같이 할 수 있다. 그러면 이를 텍스처를 이용해서 달성하려고 한다면 텍스처를 어떻게 제작해야 할까? 간단하다. vertex normal을 방금처럼 구부린 것을 Normal Map에서 해줄 수 있도록 만들어주면 된다.
45도 Normal Bevel
Trim Sheet는 기본적으로 매핑 되는 면이 평면(Plane, 항상 수직 방향을 바라보는 Normal 벡터를 가짐)이라는 가정하에 제작된다. 그러면 위 메쉬처럼 면 2개가 90도로 꺾인 채로 마주보고 있는 형태에서 평면의 모서리 부분이 다음 면과 맞닿았을 때 서로 연결된 것 처럼 보이려면 Normal Map에서 Normal이 45도로 꺾이도록 만들어 주어야 한다.
만약 45도가 아니라면 다음과 같이 이음새 부분이 나타나게 된다.
실제 메쉬는 그냥 두 개 면이 연결된 것이지만 3dsmax 에서 Chamfer를 주는 것처럼 마치 면과 면 사이에 중간 면이 하나 더 들어간 것 처럼 표현하기 위함이다.
45도가 아닌 각도라면 90도로 꺾인 면에서 이음새가 보일 수 밖에 없다. 면의 모서리 부분에서 Normal이 두 면이 서로 다른 값을 가지게 될 것이기 때문이다.
메쉬를 만들어서 노멀을 구성하면 정확한 각도를 얻을 수도 있겠지만 매우 번거롭고 귀찮은 작업이므로 섭스턴스 디자이너에서 어떻게 45도 각도의 Normal Map을 생성할 수 있는지 알아보자.
Height To Normal World Unit 이라는 노드를 이용해 입력으로 들어온 Height 데이터, 즉 0~1 사이의 Value 값을 Normal 데이터로 재구성하는 작업에서 값을 어떻게 세팅해야 45도의 Normal 값을 얻어낼 수 있을까?
Height To Normal World Unit 노드는 실제 Unit 값을 이용해서 Normal을 생성하는 노드이다.
Normal 벡터를 계산하는 방식은 보통 현재 픽셀과 주변 픽셀 간의 밝기 차이를 이용해서 x축과 y축의 값 차이를 이용해 방향벡터를 구성한다.
Height To Normal World Unit 노드가 아니라 Normal 노드를 썼다면 단순히 밝기 차이를 이용해서 벡터를 구성하게 된다. 즉, 두 픽셀 간 차이가 1이므로 x축 값은 -1이 되고 끝이다.
(현재 픽셀이 0일 때 이후 변화된 픽셀이 1이라고 가정했을 때 0 - 1 = -1)
만약 y축도 -1이라면 normal 벡터는 다음과 같이 구성할 수 있다.
normal vector = (k * -1, k * -1, 1) (비정규화된 벡터, k는 normal intensity 또는 strength)
z값을 대충 1로 놓는 이유에 대해서는 좀 더 찾아봐야 할 것 같다. 아무튼 normal 노드에서는 이런 식으로 normal vector 값을 구한다.
그렇다면 실제 Unit 값을 이용한 normal 벡터를 계산한다는 건 어떤 차이가 있는걸까?
정확한 계산식이 맞는지는 모르겠지만, 계산 방식을 대략 유추해보도록 한다.
만약 Surface Size가 100cm이고, Height Depth가 16cm일 때, 512*512 사이즈의 텍스처가 입력으로 들어왔다면 1픽셀이 차지하는 너비는 100/512 = 0.1953125cm가 될 것이다.
그러면 만약 현재 픽셀의 값이 0이고 가로 방향 바로 오른쪽 1픽셀 떨어진 부분이 흰색이라면 두 픽셀 간 차이를 그림으로 나타내면 다음과 같다.
이렇게 픽셀 간에 실제 떨어진 거리와 높이 값을 cm 단위로 정확히 계측해서 실제로 normal 벡터의 각도가 어떻게 되어야 하는지 결정하는 것이다.
위 이미지를 기준으로는 거의 89.3도로 90도에 근접한 형태로 x축이 증가하는 형태가 된다.
그러면 딱 45도 만큼 증가하도록 하려면 예전에 학교에서 배운 1 : 1 : 루트2 비율로 직각삼각형이 만들어져야 쎼타 값이 45도가 될 것이다. 즉, 가로가 0.1953125일 때 세로도 0.1953125가 되어야 한다. 즉 현재 픽셀이 0일 때 인접한 픽셀은 0.1953125라는 값을 가지고 있어야 45도 증가하는 형태의 normal 벡터가 생성된다는 의미이다.
512 텍스처를 기준으로 surface size 100, height 16cm로 세팅했을 때 45도 각도를 가지는 Normal Map을 만드려면 Bevel 노드에서 각 픽셀의 변화량이 0.1953125가 되도록 해야 한다. 물론 입력으로 들어오는 텍스처는 보통 2048*2048이기 때문에 512와 비교해서 4배 더 크다. 즉 0.1953125 / 4 = 0.048828125를 한 만큼의 픽셀 밝기 변화를 보장해주면 45도 각도의 normal 벡터를 얻을 수 있을 거라고 생각할 수 있다.
Bevel 노드는 근본적으로 Distance Field를 이용한다. Bevel 노드의 Distance 값은 아마 uv 값이지 않을까 생각한다. 0~1값으로 보통 증감하기 때문이다.
그러면 현재 위치에서 45도 각도가 되어야 하는 목표 지점까지의 uv 값까지 1픽셀당 0.048정도의 증가량을 보이면서 증감하도록 만드려면 1 / 0.048828125 는 대충 20.48로, 20.48 픽셀 떨어진 지점에서 그라디언트가 1이 되기 때문에 이를 uv값으로 치환하면 20.48 / 2048 = 0.01 이다. 그리고 이 값은 매우 작다..
실제로 사용하려면 Bevel 노드의 Distance 값을 미리 정해놓은 상태에서 Surface Size와 Height Depth 값을 결정하는 식으로 만들어야 할 것이다.
Bevel 노드에서 Distance 를 0.34로 했을 때 대충 큐브가 다 감싸지는 것을 확인했다.
그러면 Bevel노드의 Distance가 0.34인 상태에서 Height To Normal World Unit 노드의 Surface Size를 100cm로 고정했을 때 Height Depth의 값은 어떻게 되어야 할까?
먼저 Bevel 노드의 Distance는 uv 값이고, 실제로 확장되는 너비는 Distance / 2이다. 왜냐하면 전방향으로 확장되기 때문에 Distance 값의 절반을 사용한 것으로 보인다.
그러면 텍스처 전체 사이즈가 100cm인데, Bevel로 0.34 /2 = 0.17 만큼 uv로 확장했으니 실제 너비도 17cm 미터 확장했다고 생각할 수 있다.
그러면 17cm 확장하는 동안 45도 각도를 유지하면서 normal 벡터가 만들어지려면 당연히 1 : 1 : 루트 2라는 삼각비에 따라서 Height Depth 값도 17cm가 되어야 한다.
내가 생각한 게 대충 맞아 떨어진 것 같다. Normal 이 거의 유사하게 보인다. 선이 여전히 보이는 이유는 Bevel에서 깔끔하게 uv끝까지 들어가지 않아서 그런 것 같다. 이걸 텍스처로 뽑아서 uv를 잘 배치해주면 이음새가 없는 완벽한 Bevel이 될 것이다.
결론은
Surface Size를 100cm로 고정했을 때,
Bevel 노드의 Distance / 2 * 100 만큼의 값을 Height Depth에 넣어줄 경우 45도 각도의 Normal을 얻을 수 있다.
더 일반화를 해볼까?
Abs(Bevel Distance) * 0.5 * Surface Size = Height Depth 일 때 Bevel 노드에 의해 생기는 Gradient값은 45도 각도의 Normal 벡터로 치환된다.
Trim Sheet의 규격화
트림 시트를 보통 가로로 타일링 되는 맵 정도로만 이해하게 될 경우 다음의 형태로 텍스처를 작성하게 된다.
'3D' 카테고리의 다른 글
20250122 Trim Sheet 실습 [보물상자 2] (1) | 2025.01.21 |
---|---|
20250121 Trim Sheet 실습 [보물상자 1] (1) | 2025.01.21 |
블루아카이브 시모에 코하루 3d로 만들어 봄.. (1) | 2022.04.20 |
2019년 2달동안 인턴하면서 만들었던 것들 (0) | 2022.04.20 |
내가 원하던 캐릭터스타일... 드디어 제작함! (0) | 2022.04.20 |