Programming/D3D12

[일기장] 무작정 FBX SDK 사용해보기 (삽질 + Mesh)

Dorasima 2024. 4. 30. 19:51

친구랑 Unity만지다가....모집공고 올라와서 이력서 넣으려는데 뭔가 텅텅 빈 느낌이라. 급하게 시작한 FBX공부 ㅋㅋㅋㅋ

FBX SDK | Autodesk Platform Services

FBX SDK Help | Autodesk

 

1. 냅다 content /  attribute / Type 정보를 출력해보는 예제 따라하기

- 엄청나게 많은 skeleton과 2개의 Mesh가 있다...흠

 

- 주어진 코드를 그대로 따라했고 다른 점은, WinApp이여서
- printf 대신에 C++20의 std::format으로 출력창에 출력을 해줬다.

- 모델은 Mixamo 의 이 친구를 사용했다.

 

2. FBXLayer가 뭐지??

- 일단 Mesh를 얻고 싶어서 옛날 문서를 찾아보다가...

- (안전하지 않은 옛날 링크) FBX SDK Documentation: C++: FbxLayer Class Reference (autodesk.com)

-  볼게 이것 말고 없다...FbxLayer라는 것을 발견했다.

- 노멀, 탄젠트, 바이 탄젠트, Material 등 이런저런 정보를 가지고 있는 클래스 같다...

(그래서 Vertex 랑 Index 는 도대체 어디있는거지?)

그래서 대충 요로콤 확인을 해보니

- normal / uv / material이 있다고 한다.

- 안에 뭐가 들었는지 출력을 시도해 보겠다.

- 이렇게 파일에 출력하도록 바꾸면?

- 총 2개의 Mesh가 존재하는데 각각  대충 결과는 이렇게 나온다. 

요게 첫번째 메쉬
요게 두번째 메쉬

(.... 그래서 이걸 어떻게 써먹으라는 거지?)

(게다가 lock 문제로 manager를 destroy 할 때 Exception이 자꾸 생긴다...)

(이거에 대한 해결책은 GetDirectArray() 나 GetIndexArray()을 값으로 받지 말고 포인터로 받으면 된다.)

 

3. FbxNode로 다시 돌아가기

- FBX SDK Help | FBX nodes | Autodesk 여기 보면 루트노드에서 뭔가 막 하는 것 같은데... 문서를 봐보자.

- FBX SDK Documentation: C++: FbxNode Class Reference (autodesk.com)  (설명이 많다... 해석해 볼 가치가 있을 듯?)

- FBX는 모델 말고도, 애니메이션, 카메라, 조명 등 뭔가 많이 들어있는 것 같다. 이 전체를 Scene이라고 부르고, 그 메쉬노드? 애니메이션 노드? 같이 Node들이 모여서 Scene 을 구성하는 느낌인 듯하다.

- 각각의 node 들은 (Scene 안에서) Transform을 가지고 있고, pivots이나 ik stiffness나 daming 값도 가지고 있다.

- Node들의 종류? 속성값을 정의하는 FbxNodeAttribute가 있고... 여기에 맞는 동작들이 있고, 프로그래머가 알맞게 사용하면 되는 느낌인 듯하다.

 

- 근데... FbxNode의 멤버 함수를 보니까 그냥 이것저것 다 있다.

> GetChild 나 GetParent부터 

> Local로 만질 수 있는 Transform이나 좌표계 값

> Node Attribute와 그것으로 특정되는 FbxNode의 자식 클래스로 반환하는 함수

> 쉐이더에서 사용하기 좋은 Matrix 값도 구해서 주는 함수도 있고

> Material도 그냥 가져올 수 있고

> pivot... 이 말하는 게 기존 Mesh Offset을 말하는 건가??? 아니면 bone 최종 위치를 계산하는 전체 과정을 말하는건가???

 

아무튼 시도해 보자.

 

4. 일단 FbxMesh 클래스부터 살펴보자.

- 얘는 FbxNodeAttribute -> FbxLayerContainer -> FbxGeometryBase -> FbxGeometry으로부터 상속받은 친구다.

(그 위에 있는 FbxEmitter나  FbxObject는... 매우 일반적인 기능을 제공하는 클래스여서 나에겐 너무 어렵고, 많다.)

 

- FbxNodeAttribute 는 node 속성을 가질 수 있게 하는 클래스이다. 여기서 (3번 항목에서 언급했던) 정해진 노드 타입(eType) 값으로 얘가 어떤 역할을 할지 정해지는 느낌인 듯하다.

 

- FbxLayerContainer 는 Layer를 가지고 있는 친구다... (2번 항목에서 했던걸 말하는 건가??).

뭐가 엄청 많다.

- 이 클래스에서 멤버로 가지고 있는 FbxLayerElement 가 있는데 위 오른쪽 사진 처럼... 엄청 많은 eType과 Template을 제공한다. (이거랑 엮어서 lock이 가능한 Array도 제공한다.)

-  문서에 보면 위와 같은 타입의 layer 요소들이 geometry surface에 어떤 식으로 mapping이 될 것인가를 정의하고, 그 정보들이 메모리에서(?) 어떻게 올라갈 것인지도 정의한다고 한다.

- 그를 위해서 이 친구가 Normals 이랑 UVs를 소유하고 있다고 한다. (무친...)

-  뭔가... FbxLayerElement, 이 친구가 핵심 역할을 하는 느낌이다.

 

- FbxGeometryBase를 보자. 클래스 이름도 그렇고, 문서의 맴버 함수들 이름도 그렇고.. Layer에서 Geometry의 기능에 집중한 친구란 걸 알 수 있다. ControlPoint(?) / Normal / Tangent / Binormals 등 뭐가 많다...

- 근데 ControlPoint는 뭐냐... Vertex랑 Index는 어디 감?... 여기 안에 있나? (ㄹㅇㅋㅋ) 

 

- FbxGeometry는 컨트롤 포인트 변형 (control point deformation)을 지원하는 친구다. 즉, 애니메이션을 할 수 있게 하는 친구라는 뜻. (하위 클래스를 보면 Mesh 말고 Nurbs나 Patch가 있다.)

- 멤버나 혹은 연결 관계로 FbxDeformer, FbxGeometryWeightedMap, FbxShape 등등을 가지고 있는 듯하다.

- 그리고 변형을 시켜주는 translation, rotation, scaling에 관한 정보를, FbxAMatrix라는 행렬 타입을 이용해서 geometry위의 모든 control point에  적용(pivot)시키는 함수도 가지고 있다.

 

- FbxMesh는 Polygon이라는 구조를 이용해서 Geometry를 정의하는 클래스인 듯하다. 멤버도 polygon에서 vertex / normal / uv를 뽑아내는 기능으로 보인다.

- 그리고 FbxLayer 부분에서 하는 Material , Texture,  UV 작업도 경유해서 할 수 있도록  기능을 제공한다.

 

5. 그러면... FbxMesh와 상위 클래스가 가지고 있는 속성을 최대한 출력해 보자.

- 앞에서 뭔지 살펴본 친구들만 테스트용으로 출력해 보는 코드다.

- 너무 길어서 없앰 ㄹㅇㅋㅋ

 

- 무튼 이것의 결과는 첫 번째 메쉬는 다음과 같고..

 

- 두 번째 메쉬는 다음과 같다.

(그래서 Vertex랑 Index는 어디 있는데.... ㅡ.ㅡ)

(Material 도 어떻게 쓰는지 모르겠고... ㅡ.ㅡ)

(보니깐 deformer 랑 crease가 하나씩(오류입니다. 없습니다.) 있던데... 이건 또 뭐야 ㅡ.ㅡ)

 

6. 그냥 샘플코드 보면서 공부해 보자.

- 오... 위와 같은 과정 덕분인지 샘플코드가 읽히기 시작했다 ㄹㅇㅋㅋ (ViewScene, ImportScene)

- 이거를 적절히 변형해 가면서 + 모르는 게 나왔을 때 정리하는 식으로 해야겠다.

 

7. EMappingMode

- 모르는게 바로 나왔다... eMappingMode

- (이상하게 FbxGeometryElement으로 typedef 되어 있는) FbxLayerElement에 정의되어 있는 enum이다.

- 위에서 언급했던, 엄청나게 다양했던 FbxLayerElement (정확히는 FbxLayerElementTemplate) 들이 Geometry에서 어떻게 매핑이 될지 정해지는... 뭐 그런 거 같다.

- 해석을 대충 해보면.

 

- eNone : 매핑이 정해지지 않음 

- eByControlPoint : 각각의 컨트롤 포인트에 하나씩 매핑 좌표가 존재함

- eByPolygonVertex: 각각의 Vertex와 그것이 포함된 폴리곤마다 매핑 좌표가 존재함 점으로 정해짐. 이것은 한 vertex와 그 vertex가 포함된 polygon과 같은 매핑 좌표를 가질 것이라는 것을 의미함

- eByPolygon : 폴리곤 전체에 하나의 매핑 좌표를 가짐

- eByEdge : 메쉬에 각 유일한 선분마다 매핑 좌표를 가짐. smoothing layer elements과 함께 쓰임을 말함

- eAllSame : 전체 surface에 하나의 매핑 좌표를 가짐

 

- 쉽지 않다. 일단 내가 가져온 모델은 무슨 친구인지 확인해 보자.

- 내가 가져온 fbx 모델은 두 메쉬 모두 eAllSame 방법으로 mapping이 되어 있다.

- 흠... 아직 잘 모르겠다. 

 

8. EReferenceMode

- EMappingMode와 같이 See also에 붙어있는 친구이다.

- 첨 보는 거라서 들어가서 봤더니...

(대충 해석을 해보면)

- "좌표 배열에 매핑 정보가 어떤 식으로 저장이 될지 정한다."

- eDirect : n 번째 매핑 정보가, 직접배열(FbxLayerElementTemplate::mDirectArray)에 n 번째 요소에 존재한다.

- eIndex : (요거는... 버전 업이 되어서 v6.0 이후 버전이면 쓰이지 않는다. eIndexToDirect로 대체되었다.)

- eIndexToDirect : 주소배열(FbxLayerElementTemplate::mIndexArray)은 , n 번째 요소에 대해, 직접배열(mDirectArray)에서의 인덱스를 가지고 있다. eIndexToDirect는 보통 eByPolygonVertex 매핑 모드에 쓰인다. 같은 좌표가 반복적으로 나온다면, 좌표는 한 번만 저장하고 + 그것을 참조하는 index를 여러 번 쓰며 용량을 아낄 수 있다. Material이나 Texture도 같은 모드를 사용하고, (mIndexArray에서 얻은 index를 통해) mDirectArray에서 실제 값을 가져와서 사용한다.

 

- 각 잡고 해석을 하니까 이해가 되었다. (버텍스 버퍼, 인덱스 버퍼 느낌쓰)

- 그동안 생각 없이 사용하던 GetDirectArray가 위와 같은 의미를 가지고 있었다.

 

9. eMappingType에 따른 Mesh import 방법

(부제 : 예제를 살펴보면서 끼워 맞추기)

 

9_1. 머테리얼 Mapping Type에 따른 사전작업(?)

 

-  Mesh를 분해하기 전에 MaterialElement의 Mapping Type을 얻어서,  eByPolygon의 경우 추가 작업을 한다.

 

eByPolygon의 경우]

1) 일단은, 멤버로 (int)IndexOffset과 (int)TriangleCount를 가지는 구조체로, 배열을 만든다.

2) 전체 polygon 개수를  제어 변수 인덱스로 이용해서,  MaterialElement의 IndexArray에서 값을 가져온다. 가져온 값은 MaterialElement의 DirectArray의 인덱스값이 될 것이고... 이 인덱스에 해당하는 SubMesh에 TriangleCount을 1로 해준다.

- polygon Index로 Material IndexArray를 참조하여 나온 값이 SubMesh 배열을 참조하는 Index로 쓰인다. 

- 매 루프마다 가장 큰 값만큼 Submesh 배열의 크기를 지정해 준다. 

3) Material 개수만큼 생긴 Submesh 배열을 돌면서 삼각형 점 개수 * 3 만큼 오프셋을 늘려주면서 값을 기록해 준다.

- Material IndexArray의 값이 없는 SubMesh는 TriangleCount 가 0이었으니, offset 값은 이전과 똑같은 값을 가질 것이다.

(근데 애초에... Material이 개수가 그렇게나 많나?)

 

그 이외의 경우]

- SubMesh 구조체를 사용하지 않는다.

 

9_2. Index / Normal / UV 구하기 (+  tangent, bitangent)

- Mesh가 Normal Element나 UV Element를 가지고 있는지 확인한다. 그리고  Normal과 UV 엘레먼트의 Mapping Type을 확인하는데, eByControlPoint의 경우와 그렇지 않은 경우가 나뉜다.

(예제를 보면 eByControlPoint의 경우는 Control Point에 다 붙어있는 느낌이고 / 그 이외는 Polygon 단위 느낌이다.)

 

eByControlPoint의 경우]

- Mesh에서 GetElementNormal / GetElementUV으로 FbxLayerElementTemplate 배열을 얻어서 접근한다.

- 배열 제어는 Vertex(Control Point) 개수로 한다. (여기도 배열의 RefMode에 따라 IndexArray를 한번 거쳐야 할 수 있다.)

- Vertex는 control Point 자체가 Vertex가 된다. 제어변수값 그대로 참조하면 된다.

pvI - 전체 컨트롤 포인트 인덱스 (0 ~ controlPointsCount)

- Index는 폴리곤 루프를 돌면서, GetPolygonVertex로 구한다.

pI - 폴리곤 인덱스 (0 ~ polygonCount) / vI - 버텍스 인덱스 (0 ~ 2)

 

 

그 이외 경우]

- 폴리곤 단위로 값을 가져오는 느낌이다. Normal 값과 UV 값을 GetPolygonVertexNormal()과 GetPolygonVertexUV()으로 구한다.  (폴리곤 + 버텍스 2중 루프이다.)

- Vertex는 GetPolygonVertex가 폴리곤 루프를 돌면서 나온 값으로 , GetControlPoints()으로 얻은 배열에 참조하여 구한다.

pI - 폴리곤 인덱스 (0 ~ polygonCount) / vI - 버텍스 인덱스 (0 ~ 2)

- 인덱스는 폴리곤 루프 제어변수 값 그대로 넣으면 된다.

pI - 폴리곤 인덱스 (0 ~ polygonCount) / vI - 버텍스 인덱스 (0 ~ 2)

 

9_3. 머테리얼 매핑타입에 따른 Index 저장 위치

- 위에서 했던 사전 작업(SubMesh)을 이용하는데... 왜 이런지는 잘 모르겠다.

 

eByPolygon의 경우]

- 머테리얼 개수만큼 SubMesh를 만들었고, 멤버 indexOffset 값에 3씩 늘려가며 값을 채워주었다.

- 제어 변수인 polygonIndex로 MaterialElement의 IndexArray를 참조해서 나온 값으로 SubMesh 배열을 참조한다.

- 참조한 SubMesh의 IndexOffset 값을 polygonIndex 대신 사용한다..

(왜 이런 거지?)

 

그 이외의 경우]

- 차례대로 넣어주면 된다.

 

10. FbxSurfaceMaterial

- 9번의 이유를 알기 위해서 이걸 공부해야 하는 것 같은데...

- 다음 글로 넘겨야겠다... 너무 길어진다.

 

11. 일단 Mesh만 그려본 결과

- 삼각형이 빈다... 왜 이러지?

 

12. FbxGeometryConverter - Triangulate

- 여러모로 아쉬운 Mixamo 모델을 Triangulate를 해주면?

(Triangulate 작업이 엄청 오래 걸린다)

- 그래도 너무 까맣다. 노멀 방향이 안쪽으로 있나? 

- 일단 방법을 모르겠으니 Normal이 저장되는 방향을 냅다 거꾸로 해주자.

(근데.... 나는 Material Albedo에 회색을 넣었는데, SkyCube 반사에서 문제가 생겼나?)

- 민짜 노멀맵과 민짜 범프맵이여서 문제가 안생길 줄 알았는데...

- fbx 모델에 없던 Tangent를 임의로 (대충 World Up과 모델 중앙으로 근사) 계산해서 넣어줬는데 그게 문제가 생겼던것 같다... 쉐이더 코드를 범프맵을 사용하지 않도록 고쳤더니 잘 작동한다.

두 번째 메쉬가 관절부 였구나?

 

13. 코드 좀 정리하고 다음 글에 쓰기

- 삽질한 것까지 전부 적다 보니까 글이 너무 길어졌다.

- 다음글에 Material을 해보겠다.

- 이력서 넣기 전에 animation 찍먹 해봐야 하는데... 큰일 났다.

sample에 있는 친구들 테스트 (humanoid / box)