OpenGL/개발 일지

[OpenGL] 게임 엔진 개발 (5) - Skeletal Animation (3)

ciel45 2024. 7. 26. 18:14

이제 애니메이션을 프로젝트에 적용해볼 시간이다.

 

이번에도 애니메이션은 프로젝트할 때마다 항상 덕을 많이 보고있는 mixamo에서 구했다.

https://www.mixamo.com/#/

 

Mixamo

 

www.mixamo.com

 

그리고 모델링은 니어: 오토마타의 캐릭터 데볼의 모델링을 사용하였다.

https://sketchfab.com/3d-models/devola-nier-automata-27271403970d417cab3e8cb1715e1769

 

Devola - Nier: Automata - Download Free 3D model by Banof - Sketchfab

Open with QR Code: Scan this code to open the model on your device, then, tap on the AR icon. Open this link with your mobile:

sketchfab.com

gltf 포맷을 다운받았다.

 

 

블렌더에서 gltf를 fbx로 변환 후, mixamo에 업로드하여 이것저것 애니메이션을 다운받았다.

 

 

이렇게 블렌더에서 다운받은 fbx파일은, 애니메이션임과 동시에 모델링이기도 하다.

원본 모델링은 리깅이 되어있지 않기 때문에, mixamo에서 다운받은 fbx파일을 모델링으로 사용할 것이다.

 

그런데 여기서 매우매우 주의해야할 점이 있는데, OpenGL에서 사용하는 좌표계는 Blender나 Mixamo에서 사용하는 좌표계와는 다르다는 것이다.

 

OpenGL은 +y가 윗 방향, -z가 앞 방향을 의미하는 반면, Blender에서는 +y가 앞 방향, +z가 윗 방향을 의미한다.

그래서 Mixamo에서 다운받은 애니메이션을 바로 프로그램에 넣어보면, 이런식으로 모델링이 마구 뒤틀려보일 것이다.

끼야아아아악

 

그래서 해결법은 단순하다. Mixamo에서 다운받은 fbx파일을 먼저 Blender로 불러와서, 좌표계 설정을 바꿔서 다시 export해주면 된다.

이렇게 export 시 설정에서 Y Forward, Z up으로 하고 Apply Transform을 체크해주면 된다.

 

 

(2024-08-19 추가)

나중에 보니까 이렇게 해도 계속 깨지는 경우가 있었다.

그냥 glTF로 export하면 OpenGL이 어느정도 좌표계 처리를 해주기 때문에, 그 편이 낫다고 생각된다.

 

 

이제 이걸 적용하는 main 함수는 다음과 같다.

// ...
Animator* animator;

Animation* idleAnim;
Animation* runAnim;
Animation* danceAnim;

//...

// 쉐이더 변수 핸들
// ...
GLuint loc_finalBonesMatrices = 0;

// ...
int main()
{
    // ...
    
	// Model
	mainModel = new Model();
	std::string modelPath = "devola_-_nier_automata/devola1.fbx";
	mainModel->LoadModel(modelPath);

	currModel = mainModel;


	// Animation
	idleAnim = new Animation("Models/devola_-_nier_automata/Idle.fbx", currModel);
	runAnim = new Animation("Models/devola_-_nier_automata/Slow_Run.fbx", currModel);
	danceAnim = new Animation("Models/devola_-_nier_automata/dance.fbx", currModel);

	// Animator
	animator = new Animator(runAnim);

	// ...
    ///////////////////////////////////////////////////////////////////////////
    /// main loop
    //////////////////////////////////////////////////////////////////////////
	while (!mainWindow->GetShouldClose())
	{
		// ...
		animator->UpdateAnimation(deltaTime);
		// ----------------------------------------

		// ...

		auto transforms = animator->GetFinalBoneMatrices();
		for (int i = 0; i < transforms.size(); i++)
		{
			char locbuff[100]= {'\0'};
			snprintf(locbuff, sizeof(locbuff), "finalBonesMatrices[%d]", i);

			int loc = glGetUniformLocation(shaderList[0]->GetID(), locbuff);
			glUniformMatrix4fv(loc, 1, false, glm::value_ptr(transforms[i]));
		}

		mainModel->RenderModel();

		glUseProgram(0);

		// --------------------------------------------------------------------------------
		// ...
	}

    return 0;
}

 

main.cpp 전문 : https://github.com/sys010611/YsEngine/blob/main/YsEngine/main.cpp

 

YsEngine/YsEngine/main.cpp at main · sys010611/YsEngine

Contribute to sys010611/YsEngine development by creating an account on GitHub.

github.com

 

 

 

https://www.youtube.com/watch?v=2vNZI_SJmus&ab_channel=Ciel45

 

 

여태까지 했던 것 중 역대급으로 힘들었는데, 코드 자체가 워낙 길기도 하고 변환행렬 간 관계, 클래스들 간 관계를 머리 속으로 그리는 것이 많이 까다로웠다.

 

또한 그래픽스 프로그래밍 특성 상 어딘가에서 실수를 해도 컴파일 에러를 내는게 아니고 조용히 화면이 안 나오는 것 때문에 애를 많이 먹었고,

GPU로 넘긴 데이터는 디버깅이 불가능하다는 것도 화를 돋구는데 한 몫 했다.

 

그래도 애니메이션이 잘 나와줄 때는 당연히 쾌감이 상당했다.

 

아직도 관련 내용이 머릿속에서 완벽하게 정리되었다고 하기엔 어려워, 이 포스팅들은 추후에도 두고두고 볼 예정이다.