1️⃣ FBX 계층 구조 기반 Bone 파싱

Assimp를 통해 FBX 파일의 루트 노드부터 재귀적으로 트리를 순회하며 Bone 정보를 가져옵니다. 현재는 스켈레톤보다 "계층 노드 = Bone" 이라고 가정하고 구조를 구성합니다.

📌 FBX의 Transform 정보는 Relative(부모 기준) 좌표계입니다. 따라서 부모의 Transform과 곱해 최종 월드 기준 좌표계로 전환해야 합니다.

void Converter::ReadModelData(aiNode* node, int32 index, int32 parent)
{
    shared_ptr<asBone> bone = make_shared<asBone>();
    bone->index = index;
    bone->parent = parent;
    bone->name = node->mName.C_Str();

    // Relative Transform → Transpose(전치) → Local Transform 계산
    Matrix transform(node->mTransformation[0]);
    bone->transform = transform.Transpose();

    Matrix matParent = Matrix::Identity;
    if (parent >= 0)
        matParent = _bones[parent]->transform;

    bone->transform *= matParent;

    _bones.push_back(bone);

    // Mesh 데이터 연결
    ReadMeshData(node, index);

    // 자식 노드 재귀 호출
    for (uint32 i = 0; i < node->mNumChildren; i++)
        ReadModelData(node->mChildren[i], _bones.size(), index);
}

2️⃣ Mesh 파싱 및 서브메쉬 처리

각 노드는 하나 이상의 Mesh를 가질 수 있으며, 각 Mesh는 여러 개의 서브메쉬를 포함할 수 있습니다. 이들을 통합해 관리하며, 정점 오프셋을 고려한 인덱스 관리를 수행합니다.

void Converter::ReadMeshData(aiNode* node, int32 bone)
{
    if (node->mNumMeshes < 1) return;

    shared_ptr<asMesh> mesh = make_shared<asMesh>();
    mesh->name = node->mName.C_Str();
    mesh->boneIndex = bone;

    for (uint32 i = 0; i < node->mNumMeshes; i++)
    {
        uint32 index = node->mMeshes[i];
        const aiMesh* srcMesh = _scene->mMeshes[index];

        const aiMaterial* material = _scene->mMaterials[srcMesh->mMaterialIndex];
        mesh->materialName = material->GetName().C_Str();

        const uint32 startVertex = mesh->vertices.size();

        for (uint32 v = 0; v < srcMesh->mNumVertices; v++)
        {
            VertexType vertex;
            ::memcpy(&vertex.position, &srcMesh->mVertices[v], sizeof(Vec3));
            if (srcMesh->HasTextureCoords(0))
                ::memcpy(&vertex.uv, &srcMesh->mTextureCoords[0][v], sizeof(Vec2));
            if (srcMesh->HasNormals())
                ::memcpy(&vertex.normal, &srcMesh->mNormals[v], sizeof(Vec3));

            mesh->vertices.push_back(vertex);
        }

        for (uint32 f = 0; f < srcMesh->mNumFaces; f++)
        {
            aiFace& face = srcMesh->mFaces[f];
            for (uint32 k = 0; k < face.mNumIndices; k++)
                mesh->indices.push_back(face.mIndices[k] + startVertex);
        }
    }

    _meshes.push_back(mesh);
}

3️⃣ 바이너리 파일로 저장하기

모델의 구조가 방대하므로 XML이 아닌 바이너리 형태로 저장해 효율적인 로딩을 지원합니다. 이 과정은 FileUtils를 통해 추상화되어 있습니다.

void Converter::WriteModelFile(wstring finalPath)
{
    shared_ptr<FileUtils> file = make_shared<FileUtils>();
    file->Open(finalPath, FileMode::Write);

    file->Write<uint32>(_bones.size());
    for (auto& bone : _bones)
    {
        file->Write<int32>(bone->index);
        file->Write<string>(bone->name);
        file->Write<int32>(bone->parent);
        file->Write<Matrix>(bone->transform);
    }

    file->Write<uint32>(_meshes.size());
    for (auto& mesh : _meshes)
    {
        file->Write<string>(mesh->name);
        file->Write<int32>(mesh->boneIndex);
        file->Write<string>(mesh->materialName);
        file->Write<uint32>(mesh->vertices.size());
        file->Write(&mesh->vertices[0], sizeof(VertexType) * mesh->vertices.size());
        file->Write<uint32>(mesh->indices.size());
        file->Write(&mesh->indices[0], sizeof(uint32) * mesh->indices.size());
    }
}

4️⃣ 모델 메모리 로딩 및 렌더링

이제 저장된 .mesh 파일을 메모리로 불러오고 렌더링을 위한 컴포넌트 기반 시스템에 연결합니다.

  • Model 클래스에서 Load & Cache 수행
  • ModelRenderer를 통해 Mesh + Material + Transform을 GPU에 전송

🎮 실제 렌더링을 위한 주요 로직:

void ModelRenderer::Update()
{
    if (_model == nullptr) return;

    RENDER->PushTransformData(TransformDesc{ GetTransform()->GetWorldMatrix() });

    for (auto& mesh : _model->GetMeshes())
    {
        if (mesh->material)
            mesh->material->Update();

        DC->IASetVertexBuffers(0, 1, mesh->vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
        DC->IASetIndexBuffer(mesh->indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);

        _shader->DrawIndexed(0, _pass, mesh->indexBuffer->GetCount(), 0, 0);
    }
}

5️⃣ 텍스처 자동 처리 (WriteTexture)

FBX에 포함된 또는 외부 텍스처를 자동 복사하여 최종 텍스처 폴더로 이동시켜 줍니다. 텍스처가 내장되어 있는 경우 처리도 가능하지만, 일반적으로 외부 텍스처를 사용하는 것이 관리가 편리합니다.

string Converter::WriteTexture(string saveFolder, string file)
{
    string fileName = filesystem::path(file).filename().string();
    string folderName = filesystem::path(saveFolder).filename().string();

    const aiTexture* srcTexture = _scene->GetEmbeddedTexture(file.c_str());

    if (srcTexture == nullptr)
    {
        string originStr = (filesystem::path(_assetPath) / folderName / file).string();
        string pathStr = (filesystem::path(saveFolder) / fileName).string();
        Utils::Replace(originStr, "\\", "/");
        Utils::Replace(pathStr, "\\", "/");

        ::CopyFileA(originStr.c_str(), pathStr.c_str(), false);
    }

    return fileName;
}

profile
李家네_공부방

0개의 댓글