본문 바로가기
그래픽스

Assimp 를 이용하여 모델불러오기

by greenherb 2021. 8. 10.

https://github.com/assimp/assimp

 

GitHub - assimp/assimp: The official Open-Asset-Importer-Library Repository. Loads 40+ 3D-file-formats into one unified and clea

The official Open-Asset-Importer-Library Repository. Loads 40+ 3D-file-formats into one unified and clean data structure. - GitHub - assimp/assimp: The official Open-Asset-Importer-Library Reposit...

github.com

Assimp를 사용하여 모델을 불러오도록 하겠습니다. 사실 이미 Assimp는 링크되어 사용되고있지만 엔진단에서 어떻게 메쉬들을 처리할지에 대해 고민했습니다. Static Mesh 와 Dynamic Mesh 로 나눈다면 전자는 애니메이션이 없는 메쉬를 의미하고 후자는 애니메이션에 움직이는 메쉬로 볼 수 있습니다.

유니티와 언리얼 엔진을 살펴보면서 유니티의경우 Skinned Mesh Renderer 컴포넌트가 메쉬와 하이라키뷰에서 어떤 오브젝트의 트랜스폼에 영향을 받는지 정의되어있습니다. 하이라키뷰에서 해당 트랜스폼에 접근할수도 있구요 언리얼의 경우 스켈레탈 메쉬가 있는데 정확하게 어떻게 작동하는지는 모르지만 모델을 프리뷰로 확인했을때 메쉬의 특정부분에 소켓을 달고 조정을 할 수 있는것처럼 보였습니다.

저에게는 좀더 친숙한 유니티 방식으로 각 메쉬를 가지고있는 노드를 하이라키뷰에 표시될 엔티티로 만들었습니다. 다만 Assimp 에서 모델을 불러왔을때 한노드에서 여러 메쉬를 가질수도있고 메쉬별로 머터리얼 정보가 따로 있어서 이걸 어떻게 하나로 합쳐야하나 고민했습니다. 

메쉬 컴포넌트를 엔티티에 추가하면 무조건적으로 머터리얼 컴포넌트도 추가하게 하고 메쉬 1개당 하나의 머터리얼을 주려면 여러개의 메쉬를 가지는 노드는 여러개의 메쉬 컴포넌트와 그에 대응하는 여러개의 머터리얼 컴포넌트를 가지고 있게해야하나 고민했습니다. 

이런점이 많아서 개인적으로 Assimp를 사용하여 모델을 불러올때 정점정보만 불러오고 텍스쳐정보는 불러오지 않았습니다. 따로 모델정점정보로 에디터에 띄운다음 에디터에서 따로 텍스쳐를 지정해주고 씬을 제가 정의한 형식대로 저장해서 사용했었죠. 

그런데 Sponza 모델을 불러오려고 해보니 상당이 많은 노드로 이루어진 해당 모델을 직접 텍스쳐를 입혀주려고하니 상당히 불편했습니다. Assimp로 불러오면 텍스쳐경로는 각 노드 메쉬의 머터리얼별로 들고있어서 해당 좌표를 사용하면 손쉽기 때문에 assimp로 모델을 불러올때 텍스쳐정보도 불러와야겠다고 생각했습니다.

다만 Assimp로 불러올때 가끔 TexutreType 중 Unknown으로 잡히는부분이있기에 그런부분에 있어서는 적절한 조치가 필요할것으로 보입니다. 

추후에는 머터리얼의 프리팹화를 통해 에셋매니저에서 해당 머터리얼을 사용하는 메쉬들은 하나의 머터리얼만 공유하는 구조로 만들을 것이며 일단 현재는 하나의 메쉬에 하나의 머터리얼을 가지고있도록 바꿔야 할 것같습니다.

일단 첫번째로 Assimp 라이브러리를 프로젝트에 링크하셨다면 이제 실제 폴더안에있는 3d 모델파일을 불러오시면됩니다.

https://learnopengl.com/Model-Loading/Assimp

 

LearnOpenGL - Assimp

Assimp Model-Loading/Assimp In all the scenes so far we've been extensively playing with our little container friend, but over time, even our best friends can get a little boring. In bigger graphics applications, there are usually lots of complicated and i

learnopengl.com

 

위 이미지가 모델에 대한 정보를 가지고있는 Scene 오브젝트에대한 구조도입니다.

Scene 오브젝트를 보면 루트노드와 메쉬배열과 머터리얼배열이있습니다. 루트노드를 통해 자식노드를 접근할수있으며 자식노드에 자식노드를 접근할수있습니다. 여기서 자식노드의 mMeshes[]는 index를 값을 가진 배열이며 해당 인덱스로Scene 객체의 mMeshes[] 배열에서 인덱스로 활용하여 메쉬에 접근하게됩니다. 그리고 Scene 메쉬배열에서 받은 메쉬들은 정점,노말,텍스쳐좌표,인덱스값 그리고 머터리얼에 대한 인덱스값을 가지고있습니다. 해당 머터리얼에대한 인덱스값을 사용하여 Scene 의 mMaterial[] 배열에서 재질값을 가져오게됩니다.

정리하자면 결국 메쉬를 불러올때 트리구조를 루트노드부터 시작하여 아래로 탐색해야한다는것입니다. 그리고 이러한 탐색을통해 구성되는 트리구조를 저장해둬야 나중에 애니메이션때 작업할수있습니다. (애니메이션이 필요없다면 이런 트리구조는 신경쓰지않아도 됩니다만 (렌더링만 할경우) 엔진을 구성하시는분은 계층구조를 유지하는게 객체관리에 도움이됩니다) 위코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Entity ModelLoader::LoadModelEntity(const std::string& path, const Ref<Scene>& pScene, Entity* parentEntity)
    {
        Assimp::Importer importer;
 
        unsigned int flag;
        flag = aiProcess_Triangulate |
            aiProcess_JoinIdenticalVertices |
            aiProcess_CalcTangentSpace |
            aiProcess_GenNormals |
            aiProcess_MakeLeftHanded |
            aiProcess_FlipWindingOrder;
 
        if (RenderAPI::GetAPI() == RenderAPI::API::DirectX11)
            flag |= aiProcess_FlipUVs;
 
        const aiScene* scene = importer.ReadFile(path,
            flag);
 
        if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
        {
            std::string error = "Error:Assimp:";
            QCAT_ASSERT(false, error + importer.GetErrorString());
        }
 
        return ProcessNodeEntity(path,scene->mRootNode, scene, pScene, parentEntity);
    }
cs

 Assimp::Importer 를 사용하여 경로에있는 모델을 ReadFile 함수를 이용하여 불러옵니다 이때 경로와 플래스변수가 필요한데 플래그에는 불러올때 모델에 원하는 계산처리를 수행하게 됩니다.

현재 위코드에서는 Triangulate는 모든 메쉬를 삼각형으로 만드는것으로 일부 3d모델툴에서는 메쉬가 삼각형이아닌 사각형이 있는경우가 있기때문입니다. CalTangentSpace 플래그는 메쉬의 탄젠트 바이탄젠트 값을 계산하는것으로 이값을 이용하면 TBN 행렬을 만들때 따로 외적연산을 하지않아도됩니다. MakeLeftHanded 는 정점의 좌표들은 왼손좌표계로 만들어주고 FlipWindingOrder도 인덱스데이터의 와인드 방향을 바꾸어줍니다. 

저는 OpenGL,DX 둘다 사용하므로 현재사용하는 RednerAPI에 따라 플래그를 다르게 주고있습니다. 만약 ReadFile함수를 통해 scene 포인터가 성공적으로 반환되었는지 확인하려면 Scene 객체의 mFlags 플래그를 검사하거나 루트노드가 제대로있는지 확인하면됩니다. 문제가있을경우 예외처리를 해줍니다.(저는 scene 데이터를 해쉬맵에 저장했다가 나중에 같은 모델을 런타임중에 또 불러올때 바로 사용하려고 저장한적이 있습니다. ReadFile하는데도 시간이 걸려서 생각한 방법인데 똑같은 모델을 불러올떄 미리 저장해둔 scene을 이용해서 작업하려고하니 불완전한 scene 이라며 예외가 걸리더라구요 알아보았더니 scene 포인터는 Importer 객체의 파괴떄 같이 없어집니다 추후에 ReadFile을 하지않기위해 importer 변수를 전역으로 저장하는 실험을 해봐야겠네요)성공적으로 scene 포인터를 얻었을경우 노드탐색을 진행하면됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Entity ModelLoader::ProcessNodeEntity(const std::string& path, aiNode* node, const aiScene* scene, const Ref<Scene>& pScene, Entity* parentEntity)
    {
        // if node has meshes, Node has a meshIndex and Scene has a real mesh so
        // if node want to acess its own meshes , pass the nodes meshIndex to scene->mMeshes
        std::string nodeName = node->mName.C_Str();
        Entity entity = pScene->CreateEntity(nodeName);
        entity.SetParent(parentEntity);
 
        glm::mat4 transform = Utils::ConvertToGlm(node->mTransformation);
        entity.GetComponent<TransformComponent>().SetTransform(transform);
        if (node->mNumMeshes != 0)
        {
            entity.AddComponent<MeshComponent>();
            entity.AddComponent<MaterialComponent>();
        }
        for (uint32_t i = 0; i < node->mNumMeshes; ++i)
        {
            aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
            if (i < 1)
            {
                entity.GetComponent<MeshComponent>().vertexArray = ProcessMesh(node, mesh, scene);
                entity.GetComponent<MaterialComponent>().material = ProcessMaterial(path, scene, mesh);
            }    
            else
            {
                Entity sub = pScene->CreateEntity(mesh->mName.data);
                sub.SetParent(&entity);
                sub.AddComponent<MeshComponent>().vertexArray = ProcessMesh(node, mesh, scene,i);
                sub.AddComponent<MaterialComponent>().material = ProcessMaterial(path, scene, mesh);
            }
        }
        // and also node can have child nodes, do this procedure recursively
        for (uint32_t i = 0; i < node->mNumChildren; ++i)
        {
            ProcessNodeEntity(path,node->mChildren[i], scene, pScene, &entity);
        }
        return entity;
    }
cs

이제 노드별로 접근하여 메쉬작업을 진행해야하는데요 저는 모델에서 불러온 노드를 하나의 엔티티로 취급합니다 entt 라이브러리를 사용하여 현재 씬에다가 엔티티를 추가해줄건데요 node->mName 으로 접근하여 노드의 이름을 얻습니다. 그후 엔티티생성후 부모 엔티티가 있을경우 해당 엔티티를 부모로 지정합니다.

중간에 메쉬와 재질정보를 얻는 함수에서 하나의 노드에는 여러개의 메쉬가 있을수있습니다. 그렇다면 메쉬숫자만큼 재질의 정보도 가지고있다는걸 뜻합니다. 저는 처음에는 노드에 메쉬 배열을 저장하고 그에따른 재질정보도 저장하려고했지만 이건 에디터뷰에서 표시하기 예쁘지않고 직관적이지 않다고 생각했습니다.

여기서 중요한점은 1개이상의 메쉬를 가진 노드는 처음 메쉬는 노드엔티티가 가지도록하고 두번째 메쉬부터는 새로운 엔티티를 생성하여 해당 노드엔티티를 부모로 지정합니다. 이렇게 처리한이유는 assimp에서 노드이름은 정해져있습니다( 정해져있지만 중복의경우는 잘모르겠습니다) 하지만 메쉬이름은 없는경우가 있기때문에 두번쨰 메쉬를 불러오고 해당 메쉬에 이름을 붙일때 (부모노드의 이름 + 인덱스) 형식으로 처리하고있습니다.

왜냐하면 메쉬라이브러리를 사용하여 한번 로딩한 메쉬는 재사용 하려고합니다 (같은 모델을 2번불러왔을때를 대비하여) 그렇게하려면 해쉬맵에 키값으로 저장해야하는데 모델로딩을 통해 얻을수있는 정보는 이름이기에 문자열값을 키값으로 지정해서 그렇습니다 (추후에는 에셋매니저를 통해 숫자 id값으로 처리하는것도 좋겠지요)  근데 메쉬이름에 접근하다보니 첫번째 메쉬이름과 두번째 메쉬이름이 같게 로딩되는경우가 있던것입니다. 이경우 메쉬의 이름이 겹치기도하고 추후에 씬로딩을통해 같은이름덕분에 본래 다른기하구조를 가지게될 염려가있어 결국에는 메쉬이름을 노드이름으로 지정하고 1개이상일때는 노드이름뒤에 인덱스를 붙여 관리하게 되었습니다.

노드에는 mTransformation 변수가있는데 부모노드에 상대적인 트랜스폼 값을 가지고있습니다. 자식노드의 트랜스폼값은 루트노드에 상대적인 값으로 들어가는것입니다. 노드의 트랜스폼값이 있어야 후에 정점들을 월드공간으로 이동시킬때 적절한 위치에 옮길수 있을것입니다. 그다음으로는 node->mNumMeshes 변수에 해당 노드에 몇개의 메쉬가 있는지 알려줍니다. 만약없다면 메쉬,재질 정보를 가지지않는 단순히 위치값만 가지고있는 노드입니다. (계층구조를 유지하는 노드) 만약 메쉬개수가 0이상이라면 이제 메쉬와 재질에대해 작업을하고 그다음으로는 해당 노드에 자식노드가있을경우 부모엔티티 파라매터에 현재 생성한 엔티티값의 주소를 넣고 재귀함수를 실행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
        std::vector<Vertex> vertices;
        std::vector<uint32_t> indices;
        // Vertex information
        for (uint32_t i = 0; i < mesh->mNumVertices; ++i)
        {
            Vertex vertex;
            glm::vec3 vector;
            // Position
            vector.x = mesh->mVertices[i].x;
            vector.y = mesh->mVertices[i].y;
            vector.z = mesh->mVertices[i].z;
            vertex.position = vector;
            // Normal
            vector.x = mesh->mNormals[i].x;
            vector.y = mesh->mNormals[i].y;
            vector.z = mesh->mNormals[i].z;
            vertex.normal = vector;
 
            // Tangent
            vector.x = mesh->mTangents[i].x;
            vector.y = mesh->mTangents[i].y;
            vector.z = mesh->mTangents[i].z;
            vertex.tangent = vector;
 
            // biTangent
            vector.x = mesh->mBitangents[i].x;
            vector.y = mesh->mBitangents[i].y;
            vector.z = mesh->mBitangents[i].z;
            vertex.bitangent = vector;
 
            // TextureCoord
            // assimp let vertex has 8 max texture 0~7
            if (mesh->mTextureCoords[0])
            {
                glm::vec2 vec;
                vec.x = mesh->mTextureCoords[0][i].x;
                vec.y = mesh->mTextureCoords[0][i].y;
                vertex.texcoords = vec;
            }
            else
                vertex.texcoords = glm::vec2(0.0f, 0.0f);
 
            vertices.emplace_back(vertex);
        }
        // Index information
        for (uint32_t i = 0; i < mesh->mNumFaces; ++i)
        {
            aiFace face = mesh->mFaces[i];
            for (uint32_t j = 0; j < face.mNumIndices; ++j)
                indices.emplace_back(face.mIndices[j]);
        }
cs

 

이제 위에서 전달한 mesh 변수를 사용하여 정점에 접근할것입니다. mesh->mNumVertices에는 메쉬의 총 점점갯수가 있으며 mNumFace에는 총 면의 갯수가있고 각각의 면에 mNumIndices 값에 총인덱스 개수가 저장되어있습니다. 이 인덱스의 값은 Winding 플래그 값에달라질수있으니 주의하세요.

이렇게 정점정보를 다얻었으면 원하시는대로 처리하시면됩니다 Opengl의경우 VBO를 사용하시고 DX의 경우 정점버퍼와 인덱스버퍼 그리고 인풋레이아웃을 만드셔야하는데 이때 정점쉐이더와 맞출 시그니처가 필요하므로 쉐이더정보와함께 인풋레이아웃을 만드시면됩니다.

마지막부분인 재질에대한 정보입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
    Material ModelLoader::ProcessMaterial(const std::string& path, const aiScene* scene, aiMesh* mesh)
    {
        size_t index =  path.find_last_of("/\\")+1;
        std::string dir = path.substr(0, index);
        Material material;
        std::string fullpath;
        aiMaterial* aimaterial = scene->mMaterials[mesh->mMaterialIndex];
        Sampler_Desc desc;
        // Diffuse Texture
        if (aimaterial->GetTextureCount(aiTextureType_DIFFUSE) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_DIFFUSE, 0&path) == AI_SUCCESS)
            {
                fullpath = dir+path.data;
                material.m_DiffuseTexture = TextureLibrary::Load(fullpath, desc);
            }
        }
        // Specular Texture
        if (aimaterial->GetTextureCount(aiTextureType_SPECULAR) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_SPECULAR, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
                material.m_SpecularTexture = TextureLibrary::Load(fullpath, desc);
                material.m_MetallicTexture = TextureLibrary::Load(fullpath, desc);
            }
        }
        // Ambient Texture
        if (aimaterial->GetTextureCount(aiTextureType_AMBIENT) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_AMBIENT, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
                material.m_MetallicTexture = TextureLibrary::Load(fullpath, desc);
            }
        }
        // Emissive Texture
        if (aimaterial->GetTextureCount(aiTextureType_EMISSIVE) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_EMISSIVE, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
            }
        }
        // Height Texture
        if (aimaterial->GetTextureCount(aiTextureType_HEIGHT) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_HEIGHT, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
                material.m_NormalMapTexture = TextureLibrary::Load(fullpath, desc);
            }
        }
        // Normal Texture
        if (aimaterial->GetTextureCount(aiTextureType_NORMALS) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_NORMALS, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
                material.m_NormalMapTexture = TextureLibrary::Load(fullpath, desc);
            }
        }
        // Shininess Texture
        if (aimaterial->GetTextureCount(aiTextureType_SHININESS) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_SHININESS, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
            }
        }
        // Opacity Texture (Transparent)
        if (aimaterial->GetTextureCount(aiTextureType_OPACITY) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_OPACITY, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
            }
        }
        // Displacement Texture
        if (aimaterial->GetTextureCount(aiTextureType_DISPLACEMENT) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_DISPLACEMENT, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
            }
        }
        // LightMap Texture
        if (aimaterial->GetTextureCount(aiTextureType_LIGHTMAP) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_LIGHTMAP, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
            }
        }
        // PBR Texture
        // BaseColor Texture
        if (aimaterial->GetTextureCount(aiTextureType_BASE_COLOR) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_BASE_COLOR, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
            }
        }
        // Emissive Color Texture
        if (aimaterial->GetTextureCount(aiTextureType_EMISSION_COLOR) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_EMISSION_COLOR, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
            }
        }
        // Metalness Texture
        if (aimaterial->GetTextureCount(aiTextureType_METALNESS) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_METALNESS, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
            }
        }
        // Roughness Texture
        if (aimaterial->GetTextureCount(aiTextureType_DIFFUSE_ROUGHNESS) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_DIFFUSE_ROUGHNESS, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
            }
        }
        // Ambient Occlusion Texture
        if (aimaterial->GetTextureCount(aiTextureType_AMBIENT_OCCLUSION) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_AMBIENT_OCCLUSION, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
            }
        }
        // Unknown
        // Occlusion Texture
        if (aimaterial->GetTextureCount(aiTextureType_UNKNOWN) > 0)
        {
            aiString path;
            if (aimaterial->GetTexture(aiTextureType_UNKNOWN, 0&path) == AI_SUCCESS)
            {
                fullpath = dir + path.data;
            }
        }
 
        return material;
    }
cs

mesh->mMaterialIndex 값을 통해 해당 메쉬가 사용하는 머터리얼인덱스에 접근할수있습니다. 해당 인덱스를 사용하여 Scene 객체의 mMaterials 배열에 접근합니다. 만약 3d툴에서 2개의 재질을 사용하는 메쉬가 assimp에 의해 임포트될경우 assimp에서 자동으로 2개의 메쉬로 나눕니다. assimp에서는 고로 무조건 1개의 메쉬에 1개의 머터리얼입니다.

유니티의 경우 Sponza씬을 불러왔을때 하나의 메쉬에 머터리얼이 2개있는것을 발견했습니다. 제가 유니티에 익숙하지않아서 잘모르겠지만 이런경우 어떻게 처리되는지 궁금했습니다. 그리고 해당 씬을 Assimp로 제 프로젝트에 불러와보니 해당 꽃과 화분모델은 2개의 메쉬로 나뉘어서 처리되고 각각 1개의 재질을 가르키도록 처리되었습니다. 유니티의 경우는 한메쉬에 2개의 재질을 가질수있게 처리를한것같은데 assimp를 사용하는 저로써는 한메쉬에 2개의 재질을 가지도록 처리하는건 모델을 불러오고 따로 조절해줘야 가능하지않을까 싶네요.

그다음 메쉬의 재질인덱스로 얻은 aiMaterial 변수를 사용하여 현제 재질이 가지는 텍스쳐경로에 접근해야합니다. GetTextureCount 함수와 적절한 타입을 통해 재질이 해당 타입의 텍스쳐가 몇개있는지 알 수 있습니다. aiTextureType_DIFFUSE 는 디퓨즈 텍스쳐를 의미하며 해당타입의 텍스쳐갯수가 1개이상이라면 GetTexture() 함수를 이용하여 경로값을 얻어옵니다. 성공적으로 얻었을경우 AI_SUCCESS를 반환합니다.

이렇게 얻은 경로를 알맞게 처리하여 텍스쳐를 로딩하면됩니다. 근데 저는 여기서 의문인점이 1개이상의경우 어떻게 처리해줘야하는것입니다. 재질이 2개이상의 텍스쳐를 가지는 경우가 어떤건지 잘 모르겠더라구요. 1개메쉬 1개 재질인데 이미지가 2개이상이면 어떻게 렌더링해야할까요?

이렇게 정점,재질,노드 정보를 다 구성하셨으면 마지막으로 렌더링할차례가 남았습니다. 한줄코드에서는 위에서 얻은 VBO, 바인딩후 메쉬에 해당하는 노드트랜스폼을 쉐이더에 전달후 렌더를 진행하면 되겠네요

스폰자씬을 불러오고 각 노드들을 렌더링하면 멋진 스폰자씬이 만들어집니다. 옆에 프레임을 확인해보면.. 엄청나게 프레임이 낮아진것을 확인할 수 있습니다..아쉽게도 

아쉽게도 스폰자씬에 엄청나게 많은 노드가있습니다.. 대략 300개정도되고 DrawCall이 한프레임당 300번불리고 이전에 구현한 포인트라이트,디렉셔널라이트에의한 기하쉐이더에서 그리는것도 생각하면 포인트라이트 2개 x6 x 300 이면..

3600번의 그리기수행 + cascade 에따른 분할 그리기 6x3 하면 5600번정도 그리는것같네요 물론 순수 드로우콜은 

Shadow Pass에서 디렉셔널라이트 300번 , 포인트라이트2개 해서 600번 더하면 900번

그리고 Shading Pass에서 300번정도이니 1200번정도 드로우콜이 불리네요.. 아직 큰씬을 다뤄보지 못해서 이정도가 많은지는 모르겠지만 DrawCall이 많으면 좋지 않다는것은 알고있습니다.

현재카메라에 보이는 물체만 렌더링하는 뷰컬링에대해서도 적용해볼 여지가 있는것같습니다.  광원의 최적화는 좀 더 구글링을해서 연구해봐야하구요 ㅎㅎ;

그전에 이렇게 많은 노드를 하이라키뷰에서 일일히 접근하기에는 너무 불편한것같습니다. 다음에는 3D Picking 기능을 추가해볼것인데 mouse 레이가아닌 텍스쳐기반 피킹기능을 알아보겠습니다 ^^