Home 14. Import FBX
Post
Cancel

14. Import FBX

Import FBX

들어가며


저는 왜 fbx를 했을까요… 저도 그냥 다른 학우분들 처럼 미쿠미쿠댄스(mmd) 할껄…

AnimationImplViaSoftRenFinal.gif

최종결과물입니다.

메쉬 그냥 소프트렌더러로 만든거 아니냐고요?

AnimationImplViaSoftRenWire.gif

바로 아래의 가져올 데이터를 보면 엥 애니메이션이 다른데? 싶을텐데 그 서사가 있습니다.

외부 캐릭터 모델링 데이터를 소프트 렌더러로 불러들이기


가져올 데이터


저는 기존 소프트렌더러의 캐릭터도 스티브였으므로, 스티브를 불러왔습니다.

유니티에서 불러온 스티브. 하지만 마지막에 가서 원본 데이터가 남는것은 UV밖에 없었다고 한다.

유니티에서 불러온 스티브. 하지만 마지막에 가서 원본 데이터가 남는것은 UV밖에 없었다고 한다.

출처 : https://sketchfab.com/3d-models/the-perfect-steve-rigged-0cffc39bdab04551bde4f8cdfbc52eca

(퍼펙트 스티브같은 소리 합니다. 화가 납니다.)

그리고 FBX SDK를 불러옵니다.

[https://www.autodesk.com/developer-network/platform-technologies/fbx-sdk-2020-2-1](https://www.autodesk.com/developer-network/platform-technologies/fbx-sdk-2020-2-1)

https://www.autodesk.com/developer-network/platform-technologies/fbx-sdk-2020-2-1

  • 환경설정 헤딩


    설치부터가 어렵습니다.

    저는 C++을 잘 모르고, 라이브러리와 포함을 설정하는 방법도 모릅니다.

    설명서도 안보이고 뭐가뭔지 외부참조만 엄청나게 뜹니다.

    다르게 넣으면 외부참조와 모호합니다만 잔뜩입니다.

    그렇게 삽질하다 드디어 설정법을 찾았습니다.

    https://help.autodesk.com/view/FBX/2020/ENU/?guid=FBX_Developer_Help_getting_started_installing_and_configuring_configuring_the_fbx_sdk_for_wind_html

    하라는대로 다 합니다.

    Untitled

    안됩니다.

    다시한번 읽어봅니다.

    1
    2
    3
    4
    5
    6
    7
    8
    
      libfbxsdk.lib
      libfbxsdk.dll
      libfbxsdk-md.lib
      libfbxsdk-mt.lib
      libxml2-md.lib
      libxml2-mt.lib
      zlib-md.lib
      zlib-mt.lib
    

    md와 mt는 런타임 라이브러리에 따라 달라질 것입니다.

    그리고 사실 넣으라는거 말고 몇개 더 넣었는데,

    아마 과거에 발생하던 모호함은 저 두개를 다 넣어서 그럴것이고,

    이번에는 동적라이브러리, DLL을 사용하지 않고 정적라이브러리 libfbxsdk-md.lib만 사용해봅니다.

    Untitled

    여기까지 3시간이 걸렸습니다.

    아무것도 안했는데 말이죠.

    유니티 에반젤리스트, 오즈라엘님의 14년도 블로그글을 읽었는데,

    Untitled

    알아서 할 수 있었으면 얼마나 좋았을까요?

    C++에 취약한 저는 그만 정신을 잃을 뻔 했습니다.

    그렇게 이제 변환을 해볼려는 찰나,

    LoadResource는 Engine모듈에 존재한다는걸 알게됩니다.

    다시설정할려하니 링커설정이 없습니다.

    C++진짜하기싫습니다.

그러나 저는 몰랐습니다.. sdk자체도 방대해서 어떤값을 어떻게 써야하는지 알아야하는데,

포인터로 맨날 캐스팅하고 있고 C++은 불편했다는 사실을.

하지만 써드파티 플러그인 작업 노가다로 굳어진 제 짬밥은 어디가지 않습니다.

어떻게든 하면 됩니다.

구조 분리


EngineModule에서 FBX를 읽어올 수 없다고 판단,

Mesh설정단을 모두 SoftRenderer에게 위임합니다.

그렇게 ApplyFbxToMesh 함수에 기존 LoadResources를 두게합니다.

정상적으로 동작하는지 테스트.

Untitled

계층구조


다시 FBX로 돌아와,

해당 fbx의 구조는 다음과 같습니다.

바이패드를 사용하고 있지 않습니다.

(이래서 무료 에셋이란..)

Untitled

만약 정상적인 바이패드를 사용하여 읽어온다면, AttributeType이 eSkeleton이 되어야하지만

팀플 캐릭터를 잠시 데려왔습니다. 바이패드를 사용하죠. 팀플 캐릭터를 잠시 데려왔습니다. 바이패드를 사용하죠.

우리의 스티브는 Null을 뱉습니다.

Untitled

왜일까요? 맥스를 켜서(1) Fbx를 열어봅니다.

Untitled

본의 형상을 하고있는, dummy들입니다.

폴리곤카운트


fbx를 가져오게되면 디폴트는 쿼드입니다.

물론 사각형도 삼각형 두개이므로 변환코드를 작성해도 되지만,

그냥 max에서 Export시 triangulate를 처리합니다.

코드에서 해줘도 되지만 너무 힘들어요..

메쉬


fbx내부의 객체는 기본적으로 Node라고 불립니다.

해당 node에서 노드어트리뷰트를 가져와 어트리뷰트 타입을 가져오면, 타입을 알 수 있습니다.

이 중 버텍스에 대한 정보를 가지고 있는 타입은 eMesh.

1
lMesh = (FbxMesh*)pNode->GetNodeAttribute();

이 포인터 하나로 모든걸 처리합니다.

수량부터 확인합니다.

1
2
3
auto mVerticesCount = pMesh->GetControlPointsCount();
auto mIndicesCount = pMesh->GetPolygonVertexCount();
auto mTrianglesCount = pMesh->GetPolygonCount();

일단 폴리곤 수량만큼 루프를 돌며,

폴리곤 사이즈(삼각형=3, 쿼드=4)만큼 서브인덱스를 돌립니다.

그리고 해당 메쉬의 폴리곤버텍스,

즉 버텍스 index와 텍스쳐UVindex를 가져올 수 있습니다.

또한 버텍스의 위치도 가져올 수 있죠.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (int triangleIndex = 0; triangleIndex < pMesh->GetPolygonCount(); triangleIndex++)
{
	...
	for (int triangleSubIndex = 0; triangleSubIndex < polySize; triangleSubIndex++)
	{
		int vertexIdx = pMesh->GetPolygonVertex(triangleIndex, triangleSubIndex);
		int textureUVIndex = pMesh->GetTextureUVIndex(triangleIndex, triangleSubIndex);
		vPos.X = static_cast<float>(pMesh->GetControlPoints()[vertexIdx].mData[0]);
		...
		vertices[vertexIdx] = vPos;
		uvs[vertexIdx] = vOutUV;
		triangleIndices.push_back(vertexIdx);
	}
}

버텍스 로드


아무튼 그렇게 버텍스와 인덱스를 가져오면,

Untitled

가져와지긴 합니다. 가져와지긴.

(90도 누워있는건 맥스는 z업기준이라 그렇습니다. 유니티에 넣으면 -90도 돌아가있잖아요?)

UV를 추가적으로 구현합니다.

Untitled

뭔가 UV가 안맞습니다.

버텍스 내부에 포지션과 UV가 있는게 아닌, 따로 리스트를 가지고 관리하다 보니…

DOD의 철칙인 ‘인덱스기준’ 에서 어떤건 참조한 인덱스고 어떤건 직접접근하는 인덱스인지 알 수 없어 발생한 문제였습니다.

Untitled

고쳐서 체커가 그나마 제대로 보이는걸 확인할 수 있습니다.

Untitled

하지만 여전히 문제는 찾아오죠.

저부분이 어디냐고요? 바로 접히거나, 맞닿은 부분입니다.

하지만 지금은 일단 저걸 잡을 시간이 없습니다. 가야할 길이 멀어요.

본 설정


일단 본 설정부터 합니다.

그렇게 본을 만들면…!

Untitled

뭔가 이상합니다. 계층구조가 정확히 동작하지 않습니다.

부모의 포인터가 어떤 본에서는 Null, 어떤본에서는 정상 적용되어있으며,

자식 포인터는 다 깨져있습니다.

그러면 어떻게 해야할까요?

Untitled

답은 『고칩니다.』 입니다.

이때 어설프게나마 위치를 맞출려고 x축을 뒤집었는데,

본 영향은 그대로이므로 반대손이 움직이게 됩니다.

다시한번 맥스를 켜서(2) fbx를 뜯어봅니다.

Untitled

Untitled

본의 위치와 바인드포즈가 이상합니다. 첫 프레임의 본과 바인드포즈의 메쉬가 정확히 반대!

그러니 당연히 이상하게 돌았던겁니다.

얼추 비슷하게 수정합니다.

해당 본(더미)의 위치를 바꿨더니 애니메이션이 이상해집니다.

애니메이션도 수정합니다.

Untitled

애니메이션


fbx에는 애니메이션이 들어있을 수 있습니다.

그러니 해당 fbx에서 애니메이션을 추출해봅니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int numAnimLayers = AnimStack->GetMemberCount();
FbxAnimLayer* lAnimLayer = (FbxAnimLayer*)AnimStack->GetMember(0);

FbxString Name = AnimStack->GetNameOnly();
FbxString TakeName = AnimStack->GetName();
FbxTakeInfo* TakeInfo = Scene->GetTakeInfo(TakeName);
FbxTimeSpan LocalTimeSpan = TakeInfo->mLocalTimeSpan;
FbxTime Start = LocalTimeSpan.GetStart();
FbxTime Stop = LocalTimeSpan.GetStop();
FbxTime Duration = LocalTimeSpan.GetDuration();

FbxTime::EMode TimeMode = FbxTime::GetGlobalTimeMode();
FbxLongLong FrameCount = Duration.GetFrameCount(TimeMode);
double FrameRate = FbxTime::GetFrameRate(TimeMode);

FbxLongLong start = Start.GetFrameCount(TimeMode);
FbxLongLong end = Stop.GetFrameCount(TimeMode);

// 애니메이션을 외부에서 돌리기 위한 lerpFactor
FbxLongLong targetFrame = Math::Lerp(start, end, lerpFactor); 

FbxTime Time;
Time.SetFrame(targetFrame, TimeMode);

특정 시간의 특정 노드의 위치를 얻는법은 쉽습니다.

1
2
3
for (FbxNode* Node : Nodes)
{
	FbxAMatrix LocalTransform = Node->EvaluateWorldTransform(Time);

그리고 애니메이션을 임포트하면…!!

뒤틀린 황천의 애니메이션 뒤틀린 황천의 애니메이션

이런 상황을 해결할 수 있는 방법은 무엇일까요?

뼈와 살을 분리해주마 뼈와 살을 분리해주마

답은 『고칩니다.』 입니다.

농담이고 World Matrix를 가져와서 그렇습니다. 로컬로 바꿔줍니다.

1
2
3
for (FbxNode* Node : Nodes)
{
	FbxAMatrix LocalTransform = Node->EvaluateLocalTransform(Time);

이제 ‘본은’ 제대로 애니메이션이 적용되는걸 볼 수 있습니다.

본만 제대로 되니까 문제죠.

바인드포즈


왜 본의 이동과 버텍스의 이동이 다를까요?

그것은 이동연산은 모두 상대적으로 통해지며,

상대적이라는것은 기준점이 있다는 뜻이기 때문입니다.

기준점. 바인드포즈를 제대로 잡아봅시다.

어떻게요? 맥스를 켭니다(3)

Untitled

본(더미)의 위치를 메쉬와 비슷하게 설정합니다.

Untitled

애니메이션도 엄청꼬여있어서 그냥 다시 만듭니다.

Untitled

(어라? uv만펴면 이제 무료에셋은 과연 다운로드한 무료 에셋이라 할 수 있을까요? : https://ko.wikipedia.org/wiki/테세우스의_배)

이제 돌려줍니다.

boneimpl.gif

그런데 위 gif는 잘 모르겠지만 애니메이션을 진행할 때 정규화된 값으로 플레이를 시키는데,

절반정도는 멈춰있습니다. 제 코드의 문제일까요?

아닙니다. 100프레임짜리 애니메이션에 50프레임만 지정했으니까요.

Untitled

애니도 다 뜯어 고쳐야합니다.

그래서 다 지우고 애니메이션 자체를 다시 만들어 보았습니다.

맥스를 켭니다(4). 맥스로 애니메이션 만드는건 처음해봅니다.

스티브 5호기 출동 스티브 5호기 출동

버텍스 가중치 - 다중 본 영향 (Bone Influence)


Untitled

버텍스 웨이트가 이상합니다. 어느정도에서 갑자기 뚝 끊깁니다.

맥스를 켭니다.(5)

Untitled

Untitled

웨이트는 잘 박혀있습니다.

그런데 보면 하나의 버텍스에 두개의 본 영향이 들어있습니다.

그렇다면 제가 짠 코드의 구현상 문제일 가능성이 높습니다.

저는 현재 fbx를 불러와 skin을 가져오고,

클러스터 단위, 즉 본 단위로 모든 버텍스에 대해 가중치를 할당하고 있습니다.

그런데 잠깐, 웨이트를 고정값으로 넣고있습니다.

하나의 버텍스에 여러번 참조하면 오버라이드 될 수 밖에 없죠.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for (i = 0; i != lSkinCount; ++i)
{
	lClusterCount = ((FbxSkin*)pGeometry->GetDeformer(i, FbxDeformer::eSkin))->GetClusterCount();
	for (j = 0; j != lClusterCount; ++j)
	{
		...
		for (k = 0; k < lIndexCount; k++)
		{
			int index = lIndices[k];
			float weightFactor = lWeights[k];
		
			Weight weight;
			weight.Bones = { bone.GetName() };
			weight.Values = { weightFactor };
			w[index] = weight;
		}
	}
}

그러니 참조하여 추가하도록 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for (i = 0; i != lSkinCount; ++i)
{
	lClusterCount = ((FbxSkin*)pGeometry->GetDeformer(i, FbxDeformer::eSkin))->GetClusterCount();
	for (j = 0; j != lClusterCount; ++j)
	{
		...
		for (k = 0; k < lIndexCount; k++)
		{
			int index = lIndices[k];
			float weightFactor = lWeights[k];
		
			auto& weight = w[index];
			weight.Bones.push_back(bone.GetName());
			weight.Values.push_back(weightFactor);
			cb[index] = cb[index] + 1;
		}
	}
}

UV - Seam


텍스쳐도 뭔가 이상합니다.

Untitled

UV는 깔끔하게 잘 펴져있는데 말이죠.(텍스쳐는 공백이 있어 한번 수정했습니다.)

Untitled

Untitled

seam line이 존재하는 삼각형이 전부 이상합니다

맥스를 켭니다.(6)

Untitled

하나의 버텍스에 세개의 UV좌표가 붙어있습니다.

그렇다는말은, 소프트렌더러에서는 무언갈 할 수 없습니다.

(버텍스포지션리스트구조에서는 조금 힘듬, 다른 리스트를 추가하여 한번 더 참조해야함)

아무튼 소프트렌더러 및 fbx sdk에서는 할 수 없는 일이므로,

export 옵션의 Split per vertex normal. 즉 버텍스노멀단위로 버텍스를 분리하는 옵션을 추가합니다.

그러면 저런 합쳐진, 3개의 면의 중점은 3개의 버텍스로 분리되게됩니다.

무슨소리냐고요? 잘 나온단 소립니다.

스티브 6호기 (6번 export했다는 뜻) 스티브 6호기 (6번 export했다는 뜻)

최종 렌더 파일


AnimationImplViaSoftRenFinal.gif

AnimationImplViaSoftRenWire.gif

유니티에서 돌려본 결과

AnimationImplViaUnity.gif

바인드포즈가 정상적으로 걸려있는 유니티는 유사하지만, 조금 다르게 나옵니다.

소스코드


https://gist.github.com/ashuatz/054613a1f734ee5cea293669221651ef

버그?


Untitled

저는 해당부분에서 문제가 생겨 it==parent.ChildEnd()로 처리하여 작업을 진행하였습니다.

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