GLTF๋ฅผ ๋ถ๋ฌ์ค๋ ์์ ์ ๋ ๋จ๊ณ๋ก ๋๋ ๊ฒ์ ๋๋ค. ์ฒซ ๋ฒ์งธ๋ ๋ ธ๋์ ๋ฉ์๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒ์ด๊ณ , ๋ ๋ฒ์งธ๋ ๋จธํ ๋ฆฌ์ผ๊ณผ ํ ์ค์ณ๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒ์ ๋๋ค.
๊ธฐ์กด์ GLTF ๋ก๋ฉ ํจ์๋ ์ ๊ฑฐ๋๊ณ , ๋ฉ์ ๋ฟ๋ง ์๋๋ผ ์ฌ ๋ ธ๋๋ฅผ ์ง์ ๋ถ๋ฌ์ฌ ์ ์๋ ์๋ก์ด ํจ์๋ก ๋์ฒด๋ ์์ ์ ๋๋ค. ์ด์ ํจ์๋ ๋ณต์ฌ/๋ถ์ฌ๋ฃ๊ธฐ๋ ๋๋ฒ๊น ์ฉ๋๋ก ๋จ๊ฒจ๋ ์ ์์ง๋ง, ๋ ์ด์ ์ฌ์ฉ๋์ง๋ ์์ ๊ฒ์ ๋๋ค.
๋จผ์ ํด์ผ ํ ์ผ์ LoadedGLTF ํด๋์ค๋ฅผ ์ ์ํ๋ ๊ฒ์ ๋๋ค. ์ด ํด๋์ค๋ ํ๋์ GLTF ํ์ผ์ ํฌํจ๋ ๋ชจ๋ ์์์ ๋ด๊ณ ์์ต๋๋ค. ์ด ํด๋์ค๊ฐ ์๋ฉธ๋ ๋ ๋ฉ์ ๋ฒํผ์ ํ ์ค์ณ๋ ํจ๊ป ํด์ ๋ฉ๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ๋ณ ๋ฆฌ์์ค๋ฅผ ๋ฐ๋ก ๊ด๋ฆฌํ์ง ์๊ณ , ๋ณด๋ค ๊ฐ๋จํ ๋ฐฉ์์ผ๋ก ๊ทธ๋ฃน ๋จ์๋ก ์์์ ๊ด๋ฆฌํ ์ ์๊ฒ ๋ฉ๋๋ค. ์ด ๋ฐฉ์์ ๋ชฉํ๋ GLTF ํ์ผ ํ๋๋ฅผ ์ ์ฒด ๋ ๋ฒจ์ ํ์ํ ํ ์ค์ณ, ๋ฉ์, ๊ฐ์ฒด๋ค์ ํฌํจํ๋ ๋จ์๋ก ๋ถ๋ฌ์ค๋ ๊ฒ์ ๋๋ค. ์ฌ์ฉ์๋ ๋ ๋ค๋ฅธ GLTF ํ์ผ์ ์บ๋ฆญํฐ๋ ์ค๋ธ์ ํธ ์ฉ๋๋ก ๋ถ๋ฌ์ค๊ณ , ์ด๋ฅผ ๊ฒ์ ๋์ค ์ ์งํ ์๋ ์์ต๋๋ค. ํ๋์ ์ฌ์ LoadedGLTF ํด๋์ค ํ๋๋ก ๋ถ๋ฌ์จ๋ค๊ณ ํด์, ๊ทธ ์์ ์๋ ๊ฐ๋ณ ๋ ธ๋๋ค์ Draw() ํจ์๋ฅผ ํธ์ถํ ์ ์๋ค๋ ์๋ฏธ๋ ์๋๋๋ค.
vk_loader.h์ ๋ค์ ํด๋์ค๋ฅผ ์ถ๊ฐํ์ธ์.
struct LoadedGLTF : public IRenderable {
// storage for all the data on a given glTF file
std::unordered_map<std::string, std::shared_ptr<MeshAsset>> meshes;
std::unordered_map<std::string, std::shared_ptr<Node>> nodes;
std::unordered_map<std::string, AllocatedImage> images;
std::unordered_map<std::string, std::shared_ptr<GLTFMaterial>> materials;
// nodes that dont have a parent, for iterating through the file in tree order
std::vector<std::shared_ptr<Node>> topNodes;
std::vector<VkSampler> samplers;
DescriptorAllocatorGrowable descriptorPool;
AllocatedBuffer materialDataBuffer;
VulkanEngine* creator;
~LoadedGLTF() { clearAll(); };
virtual void Draw(const glm::mat4& topMatrix, DrawContext& ctx);
private:
void clearAll();
};
GLTF์ ์ค๋ธ์ ํธ๋ค์ ์ด๋ฆ์ ๊ฐ๊ณ ์์ผ๋ฏ๋ก, ๋ชจ๋ ์ค๋ธ์ ํธ๋ฅผ unordered map์ ์ ์ฅํ ๊ฒ์ ๋๋ค. ๋ชจ๋ ํญ๋ชฉ์ shared_ptr๋ฅผ ํตํด ๊ด๋ฆฌ๋ฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ด๋ฆ์ด ์๋ ๋ ธ๋๊ฐ ์กด์ฌํ๋๋ผ๋ ์์ ํ๊ฒ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ์ ์ฒด GLTF ํ์ผ์ shared_ptr๊ฐ์ ์ฐ๊ฒฐ๋ก ๊ตฌ์ฑ๋ ๊ทธ๋ํ ๊ตฌ์กฐ๋ฅผ ํ์ฑํ๋ฉฐ, ์ฐธ์กฐ๊ฐ ์ ์ง๋๋ ๋์ ์์ฒด์ ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ์ ์ด์์๊ฒ ๋ฉ๋๋ค.
์ฐ์ ๋ฉ์ ๋ฒํผ๋ฅผ ๋ด๋ mesh์ ๋งต์ด ์์ต๋๋ค. ์ด๋ ๋ฉ์๋ง์ ๋ถ๋ฌ์ค๋ ์ด์ ์ฝ๋์์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋์ผํฉ๋๋ค. ๋ค์์ผ๋ก, ํ์ผ ๋ด ๋ณํ ํธ๋ฆฌ๋ก๋ถํฐ ์์ฑ๋ sceneNode ๋งต์ด ์์ผ๋ฉฐ, ๊ฐ๊ฐ์ ๋ ธ๋๋ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ํ์ฑํฉ๋๋ค. ๊ทธ ์ธ์๋ texture, material ๋งต์ด ์์ต๋๋ค. ํ ์ค์ณ์ ๋จธํ ๋ฆฌ์ผ์ ๋ถ๋ฆฌ๋์ด ์๋๋ฐ, ๋์ผํ ํ ์ค์ณ๊ฐ ์ฌ๋ฌ ๋จธํ ๋ฆฌ์ผ์์ ๊ณตํต์ผ๋ก ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง๊ธฐ ๋๋ฌธ์ ๋๋ค. GLTF ํ์ผ ํฌ๋งท ์ญ์ ์ด ๋์ ๋ถ๋ฆฌํ์ฌ ์ ์ํ๊ณ ์์ต๋๋ค.
๋ํ ๋ถ๋ชจ ๋ ธ๋๊ฐ ์๋ ๋ฃจํธ ๋ ธ๋๋ค์ ๋ณ๋์ ๋ฒกํฐ์ ์ ์ฅํฉ๋๋ค. ์ด๋ฅผ ํตํด LoadedGLTF ํด๋์ค์ Draw ํจ์์์ ์ด ๋ฃจํธ ๋ ธ๋๋ค๋ก๋ถํฐ ์ฌ๊ท์ ์ผ๋ก Draw()๋ฅผ ์ํํ ์ ์์ต๋๋ค. ์ด ๋ฐฉ์์ ์๋ํฐ์์ ์ฌ ๋ ธ๋๋ฅผ ๊ณ์ธต์ ์ผ๋ก ํ์ํ ๋๋ ์ ์ฉํฉ๋๋ค.
GLTF ํฌ๋งท์ ๋ง์ถฐ VkSamplers ๋ฐฐ์ด๋ ํฌํจํฉ๋๋ค. ์ด ๋ฐฐ์ด์ ์ผ๋ฐ์ ์ผ๋ก ์์์ ํญ๋ชฉ๋ง ๊ฐ์ง ๊ฒ์ ๋๋ค. ๋ฌผ๋ก ์ํ๋ฌ๋ฅผ ์ ์ญ์ผ๋ก ํด์ฑํ์ฌ ์์ง์์ ๊ณต์ ํ๋๋ก ๊ตฌํํ ์๋ ์์ง๋ง, GLTF ํ๋๊ฐ Vulkan ์์์ ์ ๋ถ ๊ด๋ฆฌํ๋ค๋ ๊ฐ๋ ์ ์ ์งํ๋ฉด ์์ง์ธก ๋ก์ง์ด ๋ ๋จ์ํด์ง๋๋ค.
๊ทธ๋ฆฌ๊ณ GLTF ํ์ผ ์ ์ฉ์ ๋์คํฌ๋ฆฝํฐ ํ๋ ์์ฑํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๊ฐ๋ณ ๋์คํฌ๋ฆฝํฐ ์ ์ ์ง์ ๊ด๋ฆฌํ์ง ์์๋ ๋๋ฉฐ, ํ์ผ์ ์ํ ๋จธํ ๋ฆฌ์ผ์ ๋์คํฌ๋ฆฝํฐ ์ ๋ค์ ํ๊บผ๋ฒ์ ํด์ ํ๋ ๊ฒ๋ ๊ฐ๋จํด์ง๋๋ค.
materialDataBuffer๋ GLTFMetallicRoughness ๋จธํ ๋ฆฌ์ผ ๋ฐ์ดํฐ์ ๊ฐ์ ํ์์ผ๋ก ์ ์ฒด ๋จธํ ๋ฆฌ์ผ ์ ๋ณด๋ฅผ ๋ด์ ๊ฒ์ ๋๋ค. ์ด ๋ฒํผ๋ ๋ชจ๋ ๋จธํ ๋ฆฌ์ผ ๋ฐ์ดํฐ๋ฅผ ํ๋์ ํฐ ๋ฒํผ์ ๋ด์ ์ฒ๋ฆฌํฉ๋๋ค.
๊ทธ ์ธ์๋ ์๋ฉธ์ ํจ์, Draw ํจ์, ๊ทธ๋ฆฌ๊ณ VulkanEngine ์ธ์คํด์ค๋ฅผ ๋ด๋ถ์ ์ ์ฅํ์ฌ clearAll() ํธ์ถ ์ ์์์ ์ฌ๋ฐ๋ฅด๊ฒ ํด์ ํ ์ ์๋๋ก ํฉ๋๋ค. ๋ง์ฝ ํฌ์ธํฐ ์ ์ฅ์ ํผํ๊ณ ์ถ๋ค๋ฉด ์ฑ๊ธํค ํจํด์ ์ฌ์ฉํ๋ ๊ฒ๋ ๊ณ ๋ คํ ์ ์์ต๋๋ค.
vk_loader.cpp์ ์๋ก์ด ์ฝ๋๋ฅผ ์์ฑํฉ์๋ค. ์ฒ์๋ถํฐ ์๋ก ๊ตฌํํ ๊ฒ์ด๋ฉฐ, ๋จธํ ๋ฆฌ์ผ ๊ด๋ จ ์ค์ ์ ์ผ๋จ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋๊ฒ ์ต๋๋ค.
std::optional<std::shared_ptr<LoadedGLTF>> loadGltf(VulkanEngine* engine,std::string_view filePath)
{
fmt::print("Loading GLTF: {}", filePath);
std::shared_ptr<LoadedGLTF> scene = std::make_shared<LoadedGLTF>();
scene->creator = engine;
LoadedGLTF& file = *scene.get();
fastgltf::Parser parser {};
constexpr auto gltfOptions = fastgltf::Options::DontRequireValidAssetMember | fastgltf::Options::AllowDouble | fastgltf::Options::LoadGLBBuffers | fastgltf::Options::LoadExternalBuffers;
// fastgltf::Options::LoadExternalImages;
fastgltf::GltfDataBuffer data;
data.loadFromFile(filePath);
fastgltf::Asset gltf;
std::filesystem::path path = filePath;
auto type = fastgltf::determineGltfFileType(&data);
if (type == fastgltf::GltfType::glTF) {
auto load = parser.loadGLTF(&data, path.parent_path(), gltfOptions);
if (load) {
gltf = std::move(load.get());
} else {
std::cerr << "Failed to load glTF: " << fastgltf::to_underlying(load.error()) << std::endl;
return {};
}
} else if (type == fastgltf::GltfType::GLB) {
auto load = parser.loadBinaryGLTF(&data, path.parent_path(), gltfOptions);
if (load) {
gltf = std::move(load.get());
} else {
std::cerr << "Failed to load glTF: " << fastgltf::to_underlying(load.error()) << std::endl;
return {};
}
} else {
std::cerr << "Failed to determine glTF container" << std::endl;
return {};
}
}
ํ์ผ์ ๋ถ๋ฌ์ค๋ ๊ฒ๋ถํฐ ์์ํ๊ฒ ์ต๋๋ค. ์ด๋ฒ์๋ ๋จ์ํ ๋ฉ์๋ง์ ๋ถ๋ฌ์ค๋ ์ด์ ์ฝ๋๋ณด๋ค ๋ ๋ฒ์ฉ์ ์ธ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง ๊ฒ์ด๊ธฐ ๋๋ฌธ์, GLTF์ GLB๊ฐ ํ์์ ๊ตฌ๋ถํ์ฌ ์ฒ๋ฆฌํ ์ ์๋๋ก ํ์ ํ์ธ ๋ก์ง์ ์ถ๊ฐํฉ๋๋ค. ๊ทธ ์ธ์ ๋๋ถ๋ถ์ ๋ก์ง์ ๊ธฐ์กด์ ๋ฉ์ ์ ์ฉ ์ฝ๋์ ๊ฑฐ์ ๋์ผํฉ๋๋ค.
// we can stimate the descriptors we will need accurately
std::vector<DescriptorAllocatorGrowable::PoolSizeRatio> sizes = { { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3 },
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3 },
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1 } };
file.descriptorPool.init(engine->_device, gltf.materials.size(), sizes);
๋ค์์ผ๋ก๋ ํ์ํ ๋์คํฌ๋ฆฝํฐ์ ๊ฐ์๋ฅผ ๋๋ต์ ์ผ๋ก ์ถ์ฐํ์ฌ ๋์คํฌ๋ฆฝํฐ ํ์ ์ด๊ธฐํํฉ๋๋ค. ํ์ ์ฉ๋์ ์ด๊ณผํ๋ ๊ฒฝ์ฐ์๋ ํ์ฅ ๊ฐ๋ฅํ ํ์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ํ์์ ๋ฐ๋ผ VkDescriptorPool์ ์ถ๊ฐ๋ก ์์ฑํ์ฌ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
์ด์ ์ํ๋ฌ๋ฅผ ๋ถ๋ฌ์ต๋๋ค. GLTF ์ํ๋ฌ๋ Vulkan๊ณผ๋ ํธํ๋์ง ์๋ OpenGL ๊ธฐ๋ฐ์ ์ซ์ ๋ฐ ์์ฑ์ ์ฌ์ฉํ๋ฏ๋ก, ์ด๋ฅผ Vulkan ํ์์ ๋ง๊ฒ ๋ณํํ๋ ํจ์๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
VkFilter extract_filter(fastgltf::Filter filter)
{
switch (filter) {
// nearest samplers
case fastgltf::Filter::Nearest:
case fastgltf::Filter::NearestMipMapNearest:
case fastgltf::Filter::NearestMipMapLinear:
return VK_FILTER_NEAREST;
// linear samplers
case fastgltf::Filter::Linear:
case fastgltf::Filter::LinearMipMapNearest:
case fastgltf::Filter::LinearMipMapLinear:
default:
return VK_FILTER_LINEAR;
}
}
VkSamplerMipmapMode extract_mipmap_mode(fastgltf::Filter filter)
{
switch (filter) {
case fastgltf::Filter::NearestMipMapNearest:
case fastgltf::Filter::LinearMipMapNearest:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case fastgltf::Filter::NearestMipMapLinear:
case fastgltf::Filter::LinearMipMapLinear:
default:
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
}
}
์ด ๋ ํจ์๋ vk_loader.cpp ํ์ผ ๋ด์ ์ ์ญ ํจ์์
๋๋ค. ์ธ๋ถ์์๋ ํ์ํ์ง ์์ ๊ฒ์
๋๋ค. Vulkan์์๋ ์ํ๋ฌ ํํฐ์ ๋ฐ๋งต ๋ชจ๋๊ฐ ๋ถ๋ฆฌ๋์ด ์์ผ๋ฏ๋ก, GLTF์ ํํฐ ์ต์
์ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ๊ฐ์ ๋ฐ๋ก ์ถ์ถํด์ผ ํฉ๋๋ค. extract_filter()
๋ VK_FILTER_NEAREST
ํน์ VK_FILTER_LINEAR
์ ๊ฐ์ ํํฐ๋ง ๋ฐฉ์์ ๋ฐํํฉ๋๋ค. extract_mipmap_mode()
์์๋ VK_SAMPLER_MIPMAP_MODE_NEAREST
ํน์ VK_SAMPLER_MIPMAP_MODE_LINEAR
์ ๊ฐ์ ๋ฐ๋งต ๋ฐฉ์์ ๋ฐํํฉ๋๋ค. NEAREST๋ ๋จ์ผ ๋ฐ๋งต ๋ ๋ฒจ์ ์ ํํ์ฌ ๋ธ๋ ๋ฉ์ด ์์ด ์ฌ์ฉํ๋ ๋ฐฉ์์ด๋ฉฐ, Linear๋ ์ฌ๋ฌ ๋ฐ๋งต ๋ ๋ฒจ์ ๋ธ๋ ๋ฉํ์ฌ ๋ถ๋๋ฝ๊ฒ ์ ํํฉ๋๋ค.
์ด์ GLTF ํ์ผ๋ก๋ถํฐ ์ํ๋ฌ๋ฅผ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค.
// load samplers
for (fastgltf::Sampler& sampler : gltf.samplers) {
VkSamplerCreateInfo sampl = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = nullptr};
sampl.maxLod = VK_LOD_CLAMP_NONE;
sampl.minLod = 0;
sampl.magFilter = extract_filter(sampler.magFilter.value_or(fastgltf::Filter::Nearest));
sampl.minFilter = extract_filter(sampler.minFilter.value_or(fastgltf::Filter::Nearest));
sampl.mipmapMode= extract_mipmap_mode(sampler.minFilter.value_or(fastgltf::Filter::Nearest));
VkSampler newSampler;
vkCreateSampler(engine->_device, &sampl, nullptr, &newSampler);
file.samplers.push_back(newSampler);
}
VkSamplerCreateInfo
๊ตฌ์กฐ์ฒด๋ฅผ ์์ฑํ๊ณ , maxLOD์ minLOD๊ฐ์ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ค์ ํ ๋ค, ์์ ์ ์ํ extract ํจ์๋ฅผ ์ฌ์ฉํด ๊ด๋ จ ์ค์ ์ ์ง์ ํฉ๋๋ค. ์ง๋ ์ฑํฐ์์์ ๊ธฐ๋ณธ ์ํ๋ฌ์๋ ๋ฌ๋ฆฌ, ์ฌ๊ธฐ์๋ LOD ๋ฒ์๋ฅผ ๋ช
์์ ์ผ๋ก ์ง์ ํ์ฌ ๋ฐ๋งต์ ์ฌ์ฉํ ์ ์๋๋ก ํฉ๋๋ค. ์์ฑ๋ VkSamplers๋ค์ LoadedGLTF ๊ตฌ์กฐ์ฒด์ ์ง์ ์ ์ฅ๋ฉ๋๋ค.
์ด์ ๋ฉ์๋ฅผ ๋ถ๋ฌ์ค๊ธฐ ์ ์, ๊ตฌ์กฐ์ฒด๋ฅผ ๋ด๊ธฐ ์ํ ๋ฐฐ์ด๋ค์ ๋จผ์ ์์ฑํ ๊ฒ์ ๋๋ค. GLTF ํ์ผ์์๋ ๋๋ถ๋ถ์ ์์ ์ด ์ธ๋ฑ์ค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ด๋ฃจ์ด์ง๋ฏ๋ก, ์ธ๋ฑ์ค๋ฅผ ํตํด ๊ฐ์ฒด๋ฅผ ์ถ์ ํ๊ณ ์ ์ฅํ ์๋จ์ด ํ์ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ด๋ค ๋ฉ์๋ ธ๋๊ฐ ํน์ ๋ฉ์๋ฅผ ์ฐธ์กฐํ ๋์๋ ๋ฉ์ ์ด๋ฆ์ด ์๋ ์ธ๋ฑ์ค๋ก ์ฐ๊ฒฐ๋ฉ๋๋ค.
// temporal arrays for all the objects to use while creating the GLTF data
std::vector<std::shared_ptr<MeshAsset>> meshes;
std::vector<std::shared_ptr<Node>> nodes;
std::vector<AllocatedImage> images;
std::vector<std::shared_ptr<GLTFMaterial>> materials;
์ด์ ์ฌ๋ฐ๋ฅธ ์์์ ๋ฐ๋ผ ํ์ผ์ ๋ถ๋ฌ์์ผ ํฉ๋๋ค. MeshNode๋ ๋ฉ์์, mesh๋ ๋จธํ ๋ฆฌ์ผ์, ๋จธํ ๋ฆฌ์ผ์ ํ ์ค์ณ์ ์์กดํฉ๋๋ค. ๋ฐ๋ผ์ ํ ์ค์ณ๋ถํฐ ์์ํ์ฌ ์ฌ๋ฐ๋ฅธ ์์๋ก ์์ฑํด์ผ ํฉ๋๋ค. ํ ์ค์ณ๋ถํฐ ์์ํฉ์๋ค. ํ ์ค์ณ์ ๊ฒฝ์ฐ ์ค์ ์ด๋ฏธ์ง๋ ์ถํ ๋ถ๋ฌ์ฌ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์์ง์ ์กด์ฌํ๋ ๊ธฐ๋ณธ ํ ์ค์ฒ๋ฅผ ๋ณต์ฌํด ์ฌ์ฉํ ๊ฒ์ ๋๋ค. ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์ค๋ ๋ฐ ์คํจํ์ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด ์ฒดํฌ๋ณด๋ ํ ์ค์ณ๋ฅผ ์ฌ์ฉํ๊ฒ ์ต๋๋ค.
// load all textures
for (fastgltf::Image& image : gltf.images) {
images.push_back(engine->_errorCheckerboardImage);
}
๋จธํ ๋ฆฌ์ผ์ ๋จธํ ๋ฆฌ์ผ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ด๋ ๋ฒํผ์ ํฌ๊ธฐ๋ฅผ ์ฌ์ ์ ๊ณ์ฐํด์ผ ํฉ๋๋ค. ํ์ฌ๋ ํ๋์ ๋จธํ ๋ฆฌ์ผ ํ์ ๋ง ์ฌ์ฉํ๋ฏ๋ก ํน๋ณํ ๋ฌธ์ ๋ ์์ผ๋ฉฐ, ์ ๋ํผ ๋ฒํผ ๊ตฌ์กฐ์ฒด์ ํฌ๊ธฐ์ ๋จธํ ๋ฆฌ์ผ ๊ฐ์๋ฅผ ๊ณฑํด ํ์ํ ์ ์ฒด ๋ฒํผ ํฌ๊ธฐ๋ฅผ ๊ตฌํ๋ฉด ๋ฉ๋๋ค.
// create buffer to hold the material data
file.materialDataBuffer = engine->create_buffer(sizeof(GLTFMetallic_Roughness::MaterialConstants) * gltf.materials.size(),
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
int data_index = 0;
GLTFMetallic_Roughness::MaterialConstants* sceneMaterialConstants = (GLTFMetallic_Roughness::MaterialConstants*)file.materialDataBuffer.info.pMappedData;
๋งคํ๋ ํฌ์ธํฐ๋ฅผ sceneMaterialConstants์ ์ ์ฅํ์ฌ ๋จธํ ๋ฆฌ์ผ ๋ฐ์ดํฐ๋ฅผ ์ธ ์ ์๋๋ก ํ๊ฒ ์ต๋๋ค.
์ด์ ๋จธํ ๋ฆฌ์ผ์ ๋ถ๋ฌ์ค๋ ๋ฐ๋ณต๋ฌธ์ ์์ฑํ๊ฒ ์ต๋๋ค.
for (fastgltf::Material& mat : gltf.materials) {
std::shared_ptr<GLTFMaterial> newMat = std::make_shared<GLTFMaterial>();
materials.push_back(newMat);
file.materials[mat.name.c_str()] = newMat;
GLTFMetallic_Roughness::MaterialConstants constants;
constants.colorFactors.x = mat.pbrData.baseColorFactor[0];
constants.colorFactors.y = mat.pbrData.baseColorFactor[1];
constants.colorFactors.z = mat.pbrData.baseColorFactor[2];
constants.colorFactors.w = mat.pbrData.baseColorFactor[3];
constants.metal_rough_factors.x = mat.pbrData.metallicFactor;
constants.metal_rough_factors.y = mat.pbrData.roughnessFactor;
// write material parameters to buffer
sceneMaterialConstants[data_index] = constants;
MaterialPass passType = MaterialPass::MainColor;
if (mat.alphaMode == fastgltf::AlphaMode::Blend) {
passType = MaterialPass::Transparent;
}
GLTFMetallic_Roughness::MaterialResources materialResources;
// default the material textures
materialResources.colorImage = engine->_whiteImage;
materialResources.colorSampler = engine->_defaultSamplerLinear;
materialResources.metalRoughImage = engine->_whiteImage;
materialResources.metalRoughSampler = engine->_defaultSamplerLinear;
// set the uniform buffer for the material data
materialResources.dataBuffer = file.materialDataBuffer.buffer;
materialResources.dataBufferOffset = data_index * sizeof(GLTFMetallic_Roughness::MaterialConstants);
// grab textures from gltf file
if (mat.pbrData.baseColorTexture.has_value()) {
size_t img = gltf.textures[mat.pbrData.baseColorTexture.value().textureIndex].imageIndex.value();
size_t sampler = gltf.textures[mat.pbrData.baseColorTexture.value().textureIndex].samplerIndex.value();
materialResources.colorImage = images[img];
materialResources.colorSampler = file.samplers[sampler];
}
// build material
newMat->data = engine->metalRoughMaterial.write_material(engine->_device, passType, materialResources, file.descriptorPool);
data_index++;
}
๋จผ์ MaterialConstants๋ฅผ ์ฑ์ฐ๊ณ , GLTF์ ๋จธํ ๋ฆฌ์ผ ์ ๋ณด์์ ๊ธฐ๋ณธ ์์, metallic, roughness ์์๋ฅผ ๋ถ๋ฌ์ต๋๋ค.
์ดํ MaterialResources ๊ตฌ์กฐ์ฒด๋ฅผ ์ฑ์๋๋ค. ํ ์ค์ณ์ ์ํ๋ฌ๋ ๊ธฐ๋ณธ ํฐ์ ํ ์ค์ณ๋ก ์ค์ ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ materialDataBuffer๋ฅผ ์ฌ๋ฐ๋ฅธ ๋ฐ์ดํฐ ์คํ์ ์ผ๋ก ์ฐ๊ฒฐํฉ๋๋ค. GLTF ๋จธํ ๋ฆฌ์ผ์์ ํ ์ค์ณ๋ ์ ํ ์ฌํญ์ด๋ฏ๋ก, ์ค์ ๋์ง ์์ ๊ฒฝ์ฐ ๋ณดํต ํฐ์์ด ๊ธฐ๋ณธ๊ฐ์ ๋๋ค. ๋ง์ฝ ์์ ํ ์ค์ณ๊ฐ ์กด์ฌํ๋ค๋ฉด ํ ์ค์ณ์ ์ํ๋ฌ๋ฅผ ์ธ๋ฑ์ค๋ฅผ ํตํด ์ฐ๊ฒฐํฉ๋๋ค. ๋ํ ๋จธํ ๋ฆฌ์ผ์ด ํฌ๋ช ํ์ง ํ์ธํ ํ, ์ด ๊ฒฝ์ฐ MaterialPass๋ฅผ Transparent๋ก ์ค์ ํฉ๋๋ค.
๋ชจ๋ ์ค๋น๊ฐ ์๋ฃ๋๋ฉด, ์ธ์๋ค์ metalRoughMaterial ํด๋์ค์ ์ ๋ฌํ์ฌ ๋จธํ ๋ฆฌ์ผ์ ์์ฑํฉ๋๋ค.
๋ค์์ ๋ฉ์๋ฅผ ๋ถ๋ฌ์ค๋ ๊ฒ์ ๋๋ค. ์ด์ ์ ๋ฉ์๋ฅผ ๋ถ๋ฌ์ค๋ ์ฝ๋์ ๊ฑฐ์ ์ ์ฌํ์ง๋ง, ์ฐจ์ด์ ์ ๋ฉ์๋ฅผ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ์ ์ฅํ๋ค๋ ์ ์ ๋๋ค.
// use the same vectors for all meshes so that the memory doesnt reallocate as
// often
std::vector<uint32_t> indices;
std::vector<Vertex> vertices;
for (fastgltf::Mesh& mesh : gltf.meshes) {
std::shared_ptr<MeshAsset> newmesh = std::make_shared<MeshAsset>();
meshes.push_back(newmesh);
file.meshes[mesh.name.c_str()] = newmesh;
newmesh->name = mesh.name;
// clear the mesh arrays each mesh, we dont want to merge them by error
indices.clear();
vertices.clear();
for (auto&& p : mesh.primitives) {
GeoSurface newSurface;
newSurface.startIndex = (uint32_t)indices.size();
newSurface.count = (uint32_t)gltf.accessors[p.indicesAccessor.value()].count;
size_t initial_vtx = vertices.size();
// load indexes
{
fastgltf::Accessor& indexaccessor = gltf.accessors[p.indicesAccessor.value()];
indices.reserve(indices.size() + indexaccessor.count);
fastgltf::iterateAccessor<std::uint32_t>(gltf, indexaccessor,
[&](std::uint32_t idx) {
indices.push_back(idx + initial_vtx);
});
}
// load vertex positions
{
fastgltf::Accessor& posAccessor = gltf.accessors[p.findAttribute("POSITION")->second];
vertices.resize(vertices.size() + posAccessor.count);
fastgltf::iterateAccessorWithIndex<glm::vec3>(gltf, posAccessor,
[&](glm::vec3 v, size_t index) {
Vertex newvtx;
newvtx.position = v;
newvtx.normal = { 1, 0, 0 };
newvtx.color = glm::vec4 { 1.f };
newvtx.uv_x = 0;
newvtx.uv_y = 0;
vertices[initial_vtx + index] = newvtx;
});
}
// load vertex normals
auto normals = p.findAttribute("NORMAL");
if (normals != p.attributes.end()) {
fastgltf::iterateAccessorWithIndex<glm::vec3>(gltf, gltf.accessors[(*normals).second],
[&](glm::vec3 v, size_t index) {
vertices[initial_vtx + index].normal = v;
});
}
// load UVs
auto uv = p.findAttribute("TEXCOORD_0");
if (uv != p.attributes.end()) {
fastgltf::iterateAccessorWithIndex<glm::vec2>(gltf, gltf.accessors[(*uv).second],
[&](glm::vec2 v, size_t index) {
vertices[initial_vtx + index].uv_x = v.x;
vertices[initial_vtx + index].uv_y = v.y;
});
}
// load vertex colors
auto colors = p.findAttribute("COLOR_0");
if (colors != p.attributes.end()) {
fastgltf::iterateAccessorWithIndex<glm::vec4>(gltf, gltf.accessors[(*colors).second],
[&](glm::vec4 v, size_t index) {
vertices[initial_vtx + index].color = v;
});
}
if (p.materialIndex.has_value()) {
newSurface.material = materials[p.materialIndex.value()];
} else {
newSurface.material = materials[0];
}
newmesh->surfaces.push_back(newSurface);
}
newmesh->meshBuffers = engine->uploadMesh(indices, vertices);
}
์ฐจ์ด์ ์ ๋ง์ง๋ง์ ๋จธํ ๋ฆฌ์ผ ์ธ๋ฑ์ค๋ฅผ ์ฒ๋ฆฌํ๋ ๋ถ๋ถ์ ๋๋ค. ๋จธํ ๋ฆฌ์ผ์ด ์๋ค๋ฉด ํ์ผ์ ์ฒซ ๋ฒ์งธ ๋จธํ ๋ฆฌ์ผ์ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ฌ์ฉํฉ๋๋ค. ๋น๋ก ๋จธํ ๋ฆฌ์ผ์ด ์ ํ ์ฌํญ์ด์ง๋ง, ๋จธํ ๋ฆฌ์ผ ์ธ๋ฑ์ค๋ฅผ ๊ฐ๊ณ ์์ง ์๋ ๊ฒฝ์ฐ๋ ๋๋ฌผ๊ธฐ ๋๋ฌธ์ ํน๋ณํ ์ ๊ฒฝ ์ฐ์ง ์์๋ ๋ฉ๋๋ค.
์ด์ ๋ ธ๋๋ฅผ ๋ถ๋ฌ์ค๊ฒ ์ต๋๋ค.
// load all nodes and their meshes
for (fastgltf::Node& node : gltf.nodes) {
std::shared_ptr<Node> newNode;
// find if the node has a mesh, and if it does hook it to the mesh pointer and allocate it with the meshnode class
if (node.meshIndex.has_value()) {
newNode = std::make_shared<MeshNode>();
static_cast<MeshNode*>(newNode.get())->mesh = meshes[*node.meshIndex];
} else {
newNode = std::make_shared<Node>();
}
nodes.push_back(newNode);
file.nodes[node.name.c_str()];
std::visit(fastgltf::visitor { [&](fastgltf::Node::TransformMatrix matrix) {
memcpy(&newNode->localTransform, matrix.data(), sizeof(matrix));
},
[&](fastgltf::Node::TRS transform) {
glm::vec3 tl(transform.translation[0], transform.translation[1],
transform.translation[2]);
glm::quat rot(transform.rotation[3], transform.rotation[0], transform.rotation[1],
transform.rotation[2]);
glm::vec3 sc(transform.scale[0], transform.scale[1], transform.scale[2]);
glm::mat4 tm = glm::translate(glm::mat4(1.f), tl);
glm::mat4 rm = glm::toMat4(rot);
glm::mat4 sm = glm::scale(glm::mat4(1.f), sc);
newNode->localTransform = tm * rm * sm;
} },
node.transform);
}
๋ ธ๋๋ฅผ ๋ถ๋ฌ์ค๋ ์์ ์ ๋ ๋จ๊ณ๋ก ๋๋ฉ๋๋ค. ์ฒซ ๋ฒ์งธ๋ ๋ ธ๋๊ฐ ๋ฉ์๋ฅผ ํฌํจํ๋์ง ์ฌ๋ถ์ ๋ฐ๋ผ ๊ธฐ๋ณธ Node ํด๋์ค ๋๋ MeshNode ํด๋์ค๋ก ๋ ธ๋๋ฅผ ์์ฑํฉ๋๋ค. ๊ทธ ํ, GLTF์ ๋ณํ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ ์ด๋ฅผ GLTF์์ ์ฌ์ฉํ๋ ์ต์ข ๋ณํ ํ๋ ฌ๋ก ๋ณํํ์ฌ ๋ก์ปฌ ํ๋ ฌ์ ๊ณ์ฐํฉ๋๋ค.
๋ ธ๋๊ฐ ๋ชจ๋ ๋ถ๋ฌ์์ก๋ค๋ฉด ์ฌ ๊ทธ๋ํ๋ฅผ ๊ตฌ์ฑํ๊ธฐ ์ํ ๋ถ๋ชจ ์์ ๊ด๊ณ๋ฅผ ์ค์ ํด์ผ ํฉ๋๋ค.
// run loop again to setup transform hierarchy
for (int i = 0; i < gltf.nodes.size(); i++) {
fastgltf::Node& node = gltf.nodes[i];
std::shared_ptr<Node>& sceneNode = nodes[i];
for (auto& c : node.children) {
sceneNode->children.push_back(nodes[c]);
nodes[c]->parent = sceneNode;
}
}
// find the top nodes, with no parents
for (auto& node : nodes) {
if (node->parent.lock() == nullptr) {
file.topNodes.push_back(node);
node->refreshTransform(glm::mat4 { 1.f });
}
}
return scene;
๋จผ์ ๋ชจ๋ ๋ ธ๋๋ฅผ ์ํํ์ฌ ์์ ๋ ธ๋๊ฐ ์๋์ง ํ์ธํ๊ณ , ๋ถ๋ชจ/์์ ํฌ์ธํฐ๋ฅผ ์ค์ ํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ๋ค์ ํ๋ฒ ๋ ธ๋๋ฅผ ์ํํ๋ฉด์ ๋ถ๋ชจ๋ฅผ ๊ฐ์ง ์๋ ๋ ธ๋๋ฅผ ์ฐพ์ topNodes ๋ฐฐ์ด์ ์ถ๊ฐํ๊ณ ๋ณํ ์ ๋ณด๋ฅผ ๊ฐฑ์ ํฉ๋๋ค. ์ง๋ ์ฑํฐ์์ ์ค๋ช ํ๋ฏ์ด refreshTransform ํจ์๋ ๋ถ๋ชจ-์์ ๊ด๊ณ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์๋ ํ๋ ฌ์ ์ฌ๊ท์ ์ผ๋ก ๊ณ์ฐํ๊ธฐ ๋๋ฌธ์, ์ด๋ค ๋ ธ๋๋ ๋ถ๋ชจ๊ฐ ์ค์ ๋์ด ์๋ค๋ฉด ๊ทธ ์์ ๋ ธ๋๋ค๋ ํจ๊ป ๊ฐฑ์ ๋ฉ๋๋ค.
์ด์ ์ ์ฒด ์ฌ์ ๋ถ๋ฌ์์ต๋๋ค. ์ด์ ๋ ๋๋งํ๊ธฐ ์ํด Draw() ํจ์๋ฅผ ์ฑ์๋ณด๊ฒ ์ต๋๋ค.
void LoadedGLTF::Draw(const glm::mat4& topMatrix, DrawContext& ctx)
{
// create renderables from the scenenodes
for (auto& n : topNodes) {
n->Draw(topMatrix, ctx);
}
}
Drawํจ์๋ ๋จ์ํ topNodes๋ฅผ ์ํํ๋ฉฐ ๊ฐ ๋ ธ๋์ Draw๋ฅผ ํธ์ถํ ๋ฟ์ ๋๋ค. ์ด ํธ์ถ์ ์์ ๋ ธ๋๋ค์๊ฒ๋ ์ ํ๋ฉ๋๋ค.
clearAll() ํจ์๋ฅผ ์ง๊ธ์ ๋น์๋๊ฒ ์ต๋๋ค. ์์ง์ ํ ์ค์ณ ์ฒ๋ฆฌ๊ฐ ์ ๋๋ก ๊ตฌํ๋์ง ์์์ผ๋ฏ๋ก ๋จ๊ฒจ๋๊ฒ ์ต๋๋ค.
์ด์ ์ด๋ฅผ VulkanEngine ํด๋์ค์ ์ฐ๊ฒฐํฉ๋๋ค.
๋ถ๋ฌ์จ GLTF๋ฅผ ์ด๋ฆ์ ํค๋ก ํ์ฌ unordered map์ ์ ์ฅํ๊ฒ ์ต๋๋ค.
std::unordered_map<std::string, std::shared_ptr<LoadedGLTF>> loadedScenes;
์ด์ ๋ฌด์ธ๊ฐ๋ฅผ ๋ถ๋ฌ์๋ด ์๋ค. ํ๋ก์ ํธ์ ํฌํจ๋์ด ์์ผ๋ฉฐ 1500๊ฐ์ ๋ฉ์๋ฅผ ๊ทธ๋ฆฌ๋ ์ปค๋ค๋ ์ฌ์ ๊ทธ๋ ค๋ณด๊ฒ ์ต๋๋ค. ๋๋ฒ๊ทธ ๋ชจ๋์์๋ ๋ถ๋ฌ์ค๋ ๋ฐ 1์ด์์ 2์ด์ ๋ ์์๋ ์ ์์ผ๋ฏ๋ก, ์ ์ ๊ธฐ๋ค๋ ค ์ฃผ์ธ์.
๋ค์ ์ฝ๋๋ฅผ init() ํจ์์ ๋์ ์ถ๊ฐํ์ธ์.
std::string structurePath = { "..\\..\\assets\\structure.glb" };
auto structureFile = loadGltf(this,structurePath);
assert(structureFile.has_value());
loadedScenes["structure"] = *structureFile;
ํด๋น ์์ ์ ๋ถ๋ฌ์ ์ดํ์ ์ฌ์ฉํ ์ ์๋๋ก ํด์๋งต์ ์ ์ฅํฉ๋๋ค.
์ดํ์๋ update_scene() ํจ์์์ Draw()๋ฅผ ํธ์ถํ ๋ ์ด ์์ ์ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค.
loadedScenes["structure"]->Draw(glm::mat4{ 1.f }, mainDrawContext);
์ด ์ฌ์ ํฌ๊ธฐ๊ฐ ํฌ๊ธฐ ๋๋ฌธ์, ์๋ก ๋ค๋ฅธ ์์น์ ์ฌ๋ฌ ๋ฒ ๊ทธ๋ ค ๋ฒค์น๋งํฌ ์ฉ๋๋ก ํ์ฉํ ์ ์์ต๋๋ค.
์ฌ์ ์ ๋ฆฌํ ๋๋, ์ ์ ํ ์์ ์ ์ด๊ธฐํ ํด์ผ ํฉ๋๋ค. clear()ํจ์์ ๋ค์ ์ฝ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค. vkDeviceWaitIdle ํธ์ถ ์งํ Vulkan ์ ๋ฆฌ ์์ ์ ์ด๋ฐ์ ํด์ ๋งต์ ์ด๊ธฐํํ ๊ฒ์ ๋๋ค. ์ด ์์ ์ ๋ชจ๋ ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฆฌํ๊ฒ ๋ฉ๋๋ค.
// make sure the gpu has stopped doing its things
vkDeviceWaitIdle(_device);
loadedScenes.clear();
์ด์ ํ๋ก์ ํธ๋ฅผ ์คํํด ๋ณด๊ณ ๋งต์ ๋๋ฌ๋ณด์ธ์. ์ปค๋ค๋ ์ฌ์ด์ง๋ง ์ด์ ์นด๋ฉ๋ผ๋ก ๋๋ฌ๋ณผ ์ ์์ต๋๋ค. ์นด๋ฉ๋ผ์ ๊ธฐ๋ณธ ์์น๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๋ณ๊ฒฝํ๋ฉด ์ ์ ํ ์ด๊ธฐ ์์น๊ฐ ๋ฉ๋๋ค.
mainCamera.position = glm::vec3(30.f, -00.f, -085.f);
init()ํจ์์์ ์ด ๊ฐ์ ์ค์ ํด์ฃผ์ธ์.
๋ค์ ๊ธ์์ ํ ์ค์ณ๋ฅผ ๋ถ๋ฌ์ค๋ ๊ฒ์ผ๋ก ์ด ๊ณผ์ ์ ๋ง๋ฌด๋ฆฌํด ๋ณด๊ฒ ์ต๋๋ค.
Next: GLTF Textures