diff --git a/src/render/glTFModel.cpp b/src/render/glTFModel.cpp index 342816d..2fe3648 100644 --- a/src/render/glTFModel.cpp +++ b/src/render/glTFModel.cpp @@ -432,148 +432,190 @@ void glTFModel::Texture::fromglTfImage(tinygltf::Image& gltfImage, std::string p } -/* - -void VulkanglTFModel::loadTextures(tinygltf::Model& input) +void glTFModel::Model::loadImages(tinygltf::Model& gltfModel, vks::VulkanDevice* device, VkQueue transferQueue) { - textures.resize(input.textures.size()); - for (size_t i = 0; i < input.textures.size(); i++) { - textures[i].imageIndex = input.textures[i].source; + for (tinygltf::Image& image : gltfModel.images) { + glTFModel::Texture texture; + texture.fromglTfImage(image, path, device, transferQueue); + textures.push_back(texture); } + // Create an empty texture to be used for empty material images + createEmptyTexture(transferQueue); } -void VulkanglTFModel::loadAnimations(tinygltf::Model& input) + +void glTFModel::Model::loadAnimations(tinygltf::Model& gltfModel) { - animations.resize(input.animations.size()); + for (tinygltf::Animation& anim : gltfModel.animations) { + glTFModel::Animation animation{}; + animation.name = anim.name; + if (anim.name.empty()) { + animation.name = std::to_string(animations.size()); + } - for (size_t i = 0; i < input.animations.size(); ++i) - { - auto glTFAnimation = input.animations[i]; - animations[i].name = glTFAnimation.name; + // Samplers + for (auto& samp : anim.samplers) { + glTFModel::AnimationSampler sampler{}; - //Samplers - animations[i].samplers.resize(glTFAnimation.samplers.size()); - for (size_t j = 0; j < glTFAnimation.samplers.size(); ++j) - { - auto glTFSampler = glTFAnimation.samplers[j]; - auto& dstSampler = animations[i].samplers[j]; - dstSampler.interpolation = glTFSampler.interpolation; + if (samp.interpolation == "LINEAR") { + sampler.interpolation = AnimationSampler::InterpolationType::LINEAR; + } + if (samp.interpolation == "STEP") { + sampler.interpolation = AnimationSampler::InterpolationType::STEP; + } + if (samp.interpolation == "CUBICSPLINE") { + sampler.interpolation = AnimationSampler::InterpolationType::CUBICSPLINE; + } - // Read sampler keyframe input time values + // Read sampler input time values { - const auto& accessor = input.accessors[glTFSampler.input]; - const auto& bufferView = input.bufferViews[accessor.bufferView]; - const auto& buffer = input.buffers[bufferView.buffer]; - const void* dataPtr = &buffer.data[accessor.byteOffset + bufferView.byteOffset]; - const float* buf = static_cast(dataPtr); - for (size_t index = 0; index < accessor.count; ++index) - { - dstSampler.inputs.push_back(buf[index]); + const tinygltf::Accessor& accessor = gltfModel.accessors[samp.input]; + const tinygltf::BufferView& bufferView = gltfModel.bufferViews[accessor.bufferView]; + const tinygltf::Buffer& buffer = gltfModel.buffers[bufferView.buffer]; + + assert(accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); + + float* buf = new float[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(float)); + for (size_t index = 0; index < accessor.count; index++) { + sampler.inputs.push_back(buf[index]); } - // Adjust animation's start and end times - for (auto input : animations[i].samplers[j].inputs) - { - if (input < animations[i].start) - { - animations[i].start = input; + delete[] buf; + for (auto input : sampler.inputs) { + if (input < animation.start) { + animation.start = input; }; - if (input > animations[i].end) - { - animations[i].end = input; + if (input > animation.end) { + animation.end = input; } } } - // Read sampler keyframe output translate/rotate/scale values + // Read sampler output T/R/S values { - const auto& accessor = input.accessors[glTFSampler.output]; - const auto& bufferView = input.bufferViews[accessor.bufferView]; - const auto& buffer = input.buffers[bufferView.buffer]; - const void* dataPtr = &buffer.data[accessor.byteOffset + bufferView.byteOffset]; - switch (accessor.type) - { - case TINYGLTF_TYPE_VEC3: - { - const glm::vec3* buf = static_cast(dataPtr); - for (size_t index = 0; index < accessor.count; index++) - { - dstSampler.outputsVec4.push_back(glm::vec4(buf[index], 0.0f)); + const tinygltf::Accessor& accessor = gltfModel.accessors[samp.output]; + const tinygltf::BufferView& bufferView = gltfModel.bufferViews[accessor.bufferView]; + const tinygltf::Buffer& buffer = gltfModel.buffers[bufferView.buffer]; + + assert(accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); + + switch (accessor.type) { + case TINYGLTF_TYPE_VEC3: { + glm::vec3* buf = new glm::vec3[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(glm::vec3)); + for (size_t index = 0; index < accessor.count; index++) { + sampler.outputsVec4.push_back(glm::vec4(buf[index], 0.0f)); } + delete[] buf; break; } - case TINYGLTF_TYPE_VEC4: - { - const glm::vec4* buf = static_cast(dataPtr); - for (size_t index = 0; index < accessor.count; index++) - { - dstSampler.outputsVec4.push_back(buf[index]); + case TINYGLTF_TYPE_VEC4: { + glm::vec4* buf = new glm::vec4[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(glm::vec4)); + for (size_t index = 0; index < accessor.count; index++) { + sampler.outputsVec4.push_back(buf[index]); } + delete[] buf; break; } - default: - { + default: { std::cout << "unknown type" << std::endl; break; } } } + + animation.samplers.push_back(sampler); } - animations[i].channels.resize(glTFAnimation.channels.size()); - for (size_t j = 0; j < glTFAnimation.channels.size(); ++j) - { - auto glTFChannel = glTFAnimation.channels[j]; - auto& dstChannel = animations[i].channels[j]; - dstChannel.path = glTFChannel.target_path; - dstChannel.samplerIndex = glTFChannel.sampler; - dstChannel.node = nodeFromIndex(glTFChannel.target_node); + // Channels + for (auto& source : anim.channels) { + glTFModel::AnimationChannel channel{}; + + if (source.target_path == "rotation") { + channel.path = AnimationChannel::PathType::ROTATION; + } + if (source.target_path == "translation") { + channel.path = AnimationChannel::PathType::TRANSLATION; + } + if (source.target_path == "scale") { + channel.path = AnimationChannel::PathType::SCALE; + } + if (source.target_path == "weights") { + std::cout << "weights not yet supported, skipping channel" << std::endl; + continue; + } + channel.samplerIndex = source.sampler; + channel.node = nodeFromIndex(source.target_node); + if (!channel.node) { + continue; + } + + animation.channels.push_back(channel); } + + animations.push_back(animation); } } -*/ - -/* -void VulkanglTFModel::loadMaterials(tinygltf::Model& input) -{ - materials.resize(input.materials.size()); - for (size_t i = 0; i < input.materials.size(); i++) { - // We only read the most basic properties required for our sample - tinygltf::Material glTFMaterial = input.materials[i]; - // Get the base color factor - if (glTFMaterial.values.find("baseColorFactor") != glTFMaterial.values.end()) { - materials[i].baseColorFactor = glm::make_vec4(glTFMaterial.values["baseColorFactor"].ColorFactor().data()); - } - // Get base color texture index - if (glTFMaterial.values.find("baseColorTexture") != glTFMaterial.values.end()) { - materials[i].baseColorTextureIndex = glTFMaterial.values["baseColorTexture"].TextureIndex(); - } - if (glTFMaterial.values.find("metallicRoughnessTexture") != glTFMaterial.values.end()) { - materials[i].matalicRoughTextureIndex = glTFMaterial.values["metallicRoughnessTexture"].TextureIndex(); - } - if (glTFMaterial.additionalValues.find("normalTexture") != glTFMaterial.additionalValues.end()) - { - materials[i].normalMapTextureIndex = glTFMaterial.additionalValues["normalTexture"].TextureIndex(); - } - if (glTFMaterial.emissiveTexture.index != -1) - { - materials[i].emissiveTextureIndex = glTFMaterial.emissiveTexture.index; - } - if (glTFMaterial.emissiveFactor.size() == 3) - { - materials[i].materialData.values.emissiveFactor = glm::make_vec3(glTFMaterial.emissiveFactor.data()); - } - - if (glTFMaterial.values.find("baseColorFactor") != glTFMaterial.values.end()) - { - materials[i].materialData.values.baseColorFactor = glm::make_vec4(glTFMaterial.values["baseColorFactor"].ColorFactor().data()); - } - } -} -*/ /* glTF material */ + +void glTFModel::Model::loadMaterials(tinygltf::Model& gltfModel) +{ + + for (tinygltf::Material& mat : gltfModel.materials) { + glTFModel::Material material(device); + if (mat.values.find("baseColorTexture") != mat.values.end()) { + material.baseColorTexture = getTexture(gltfModel.textures[mat.values["baseColorTexture"].TextureIndex()].source); + } + // Metallic roughness workflow + if (mat.values.find("metallicRoughnessTexture") != mat.values.end()) { + material.metallicRoughnessTexture = getTexture(gltfModel.textures[mat.values["metallicRoughnessTexture"].TextureIndex()].source); + } + if (mat.values.find("roughnessFactor") != mat.values.end()) { + material.roughnessFactor = static_cast(mat.values["roughnessFactor"].Factor()); + } + if (mat.values.find("metallicFactor") != mat.values.end()) { + material.metallicFactor = static_cast(mat.values["metallicFactor"].Factor()); + } + if (mat.values.find("baseColorFactor") != mat.values.end()) { + material.baseColorFactor = glm::make_vec4(mat.values["baseColorFactor"].ColorFactor().data()); + } + if (mat.additionalValues.find("normalTexture") != mat.additionalValues.end()) { + material.normalTexture = getTexture(gltfModel.textures[mat.additionalValues["normalTexture"].TextureIndex()].source); + } + else { + material.normalTexture = &emptyTexture; + } + if (mat.additionalValues.find("emissiveTexture") != mat.additionalValues.end()) { + material.emissiveTexture = getTexture(gltfModel.textures[mat.additionalValues["emissiveTexture"].TextureIndex()].source); + } + if (mat.additionalValues.find("occlusionTexture") != mat.additionalValues.end()) { + material.occlusionTexture = getTexture(gltfModel.textures[mat.additionalValues["occlusionTexture"].TextureIndex()].source); + } + if (mat.additionalValues.find("alphaMode") != mat.additionalValues.end()) { + tinygltf::Parameter param = mat.additionalValues["alphaMode"]; + if (param.string_value == "BLEND") { + material.alphaMode = Material::ALPHAMODE_BLEND; + } + if (param.string_value == "MASK") { + material.alphaMode = Material::ALPHAMODE_MASK; + } + } + if (mat.additionalValues.find("alphaCutoff") != mat.additionalValues.end()) { + material.alphaCutoff = static_cast(mat.additionalValues["alphaCutoff"].Factor()); + } + + materials.push_back(material); + } + // Push a default material at the end of the list for meshes with no material assigned + materials.push_back(Material(device)); +} + + + void glTFModel::Material::createDescriptorSet(VkDescriptorPool descriptorPool, VkDescriptorSetLayout descriptorSetLayout, uint32_t descriptorBindingFlags) { VkDescriptorSetAllocateInfo descriptorSetAllocInfo{}; @@ -902,7 +944,6 @@ void glTFModel::Model::loadNode(glTFModel::Node* parent, const tinygltf::Node& n newNode->skinIndex = node.skin; newNode->matrix = glm::mat4(1.0f); newNode->parent = parent; - // Get the local node matrix // It's either made up from translation, rotation, scale or a 4x4 matrix @@ -1112,7 +1153,294 @@ void glTFModel::Model::loadNode(glTFModel::Node* parent, const tinygltf::Node& n linearNodes.push_back(newNode); } -VulkanglTFModel::Node* VulkanglTFModel::findNode(Node* parent, uint32_t index) +// file loader function +void glTFModel::Model::loadFromFile(std::string filename, vks::VulkanDevice* device, VkQueue transferQueue, uint32_t fileLoadingFlags, float scale) +{ + tinygltf::Model gltfModel; + tinygltf::TinyGLTF gltfContext; + if (fileLoadingFlags & FileLoadingFlags::DontLoadImages) { + gltfContext.SetImageLoader(loadImageDataFuncEmpty, nullptr); + } + else { + gltfContext.SetImageLoader(loadImageDataFunc, nullptr); + } +#if defined(__ANDROID__) + // On Android all assets are packed with the apk in a compressed form, so we need to open them using the asset manager + // We let tinygltf handle this, by passing the asset manager of our app + tinygltf::asset_manager = androidApp->activity->assetManager; +#endif + size_t pos = filename.find_last_of('/'); + path = filename.substr(0, pos); + + std::string error, warning; + + this->device = device; + +#if defined(__ANDROID__) + // On Android all assets are packed with the apk in a compressed form, so we need to open them using the asset manager + // We let tinygltf handle this, by passing the asset manager of our app + tinygltf::asset_manager = androidApp->activity->assetManager; +#endif + bool fileLoaded = gltfContext.LoadASCIIFromFile(&gltfModel, &error, &warning, filename); + + std::vector indexBuffer; + std::vector vertexBuffer; + + if (fileLoaded) { + if (!(fileLoadingFlags & FileLoadingFlags::DontLoadImages)) { + loadImages(gltfModel, device, transferQueue); + } + loadMaterials(gltfModel); + const tinygltf::Scene& scene = gltfModel.scenes[gltfModel.defaultScene > -1 ? gltfModel.defaultScene : 0]; + for (size_t i = 0; i < scene.nodes.size(); i++) { + const tinygltf::Node node = gltfModel.nodes[scene.nodes[i]]; + loadNode(nullptr, node, scene.nodes[i], gltfModel, indexBuffer, vertexBuffer, scale); + } + if (gltfModel.animations.size() > 0) { + loadAnimations(gltfModel); + } + loadSkins(gltfModel); + + for (auto node : linearNodes) { + // Assign skins + if (node->skinIndex > -1) { + node->skin = skins[node->skinIndex]; + } + // Initial pose + if (node->mesh) { + node->update(); + } + } + } + else { + // TODO: throw + vks::tools::exitFatal("Could not load glTF file \"" + filename + "\": " + error, -1); + return; + } + + // Pre-Calculations for requested features + if ((fileLoadingFlags & FileLoadingFlags::PreTransformVertices) || (fileLoadingFlags & FileLoadingFlags::PreMultiplyVertexColors) || (fileLoadingFlags & FileLoadingFlags::FlipY)) { + const bool preTransform = fileLoadingFlags & FileLoadingFlags::PreTransformVertices; + const bool preMultiplyColor = fileLoadingFlags & FileLoadingFlags::PreMultiplyVertexColors; + const bool flipY = fileLoadingFlags & FileLoadingFlags::FlipY; + for (Node* node : linearNodes) { + if (node->mesh) { + const glm::mat4 localMatrix = node->getMatrix(); + for (Primitive* primitive : node->mesh->primitives) { + for (uint32_t i = 0; i < primitive->vertexCount; i++) { + Vertex& vertex = vertexBuffer[primitive->firstVertex + i]; + // Pre-transform vertex positions by node-hierarchy + if (preTransform) { + vertex.pos = glm::vec3(localMatrix * glm::vec4(vertex.pos, 1.0f)); + vertex.normal = glm::normalize(glm::mat3(localMatrix) * vertex.normal); + } + // Flip Y-Axis of vertex positions + if (flipY) { + vertex.pos.y *= -1.0f; + vertex.normal.y *= -1.0f; + } + // Pre-Multiply vertex colors with material base color + if (preMultiplyColor) { + vertex.color = primitive->material.baseColorFactor * vertex.color; + } + } + } + } + } + } + + for (auto extension : gltfModel.extensionsUsed) { + if (extension == "KHR_materials_pbrSpecularGlossiness") { + std::cout << "Required extension: " << extension; + metallicRoughnessWorkflow = false; + } + } + + size_t vertexBufferSize = vertexBuffer.size() * sizeof(Vertex); + size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t); + indices.count = static_cast(indexBuffer.size()); + vertices.count = static_cast(vertexBuffer.size()); + + assert((vertexBufferSize > 0) && (indexBufferSize > 0)); + + struct StagingBuffer { + VkBuffer buffer; + VkDeviceMemory memory; + } vertexStaging, indexStaging; + + // Create staging buffers + // Vertex data + VK_CHECK_RESULT(device->createBuffer( + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + vertexBufferSize, + &vertexStaging.buffer, + &vertexStaging.memory, + vertexBuffer.data())); + // Index data + VK_CHECK_RESULT(device->createBuffer( + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + indexBufferSize, + &indexStaging.buffer, + &indexStaging.memory, + indexBuffer.data())); + + // Create device local buffers + // Vertex buffer + VK_CHECK_RESULT(device->createBuffer( + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | memoryPropertyFlags, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + vertexBufferSize, + &vertices.buffer, + &vertices.memory)); + // Index buffer + VK_CHECK_RESULT(device->createBuffer( + VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | memoryPropertyFlags, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + indexBufferSize, + &indices.buffer, + &indices.memory)); + + // Copy from staging buffers + VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + + VkBufferCopy copyRegion = {}; + + copyRegion.size = vertexBufferSize; + vkCmdCopyBuffer(copyCmd, vertexStaging.buffer, vertices.buffer, 1, ©Region); + + copyRegion.size = indexBufferSize; + vkCmdCopyBuffer(copyCmd, indexStaging.buffer, indices.buffer, 1, ©Region); + + device->flushCommandBuffer(copyCmd, transferQueue, true); + + vkDestroyBuffer(device->logicalDevice, vertexStaging.buffer, nullptr); + vkFreeMemory(device->logicalDevice, vertexStaging.memory, nullptr); + vkDestroyBuffer(device->logicalDevice, indexStaging.buffer, nullptr); + vkFreeMemory(device->logicalDevice, indexStaging.memory, nullptr); + + getSceneDimensions(); + + // Setup descriptors + uint32_t uboCount{ 0 }; + uint32_t imageCount{ 0 }; + for (auto node : linearNodes) { + if (node->mesh) { + uboCount++; + } + } + for (auto material : materials) { + if (material.baseColorTexture != nullptr) { + imageCount++; + } + } + std::vector poolSizes = { + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uboCount }, + }; + if (imageCount > 0) { + if (descriptorBindingFlags & DescriptorBindingFlags::ImageBaseColor) { + poolSizes.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, imageCount }); + } + if (descriptorBindingFlags & DescriptorBindingFlags::ImageNormalMap) { + poolSizes.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, imageCount }); + } + } + VkDescriptorPoolCreateInfo descriptorPoolCI{}; + descriptorPoolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descriptorPoolCI.poolSizeCount = static_cast(poolSizes.size()); + descriptorPoolCI.pPoolSizes = poolSizes.data(); + descriptorPoolCI.maxSets = uboCount + imageCount; + VK_CHECK_RESULT(vkCreateDescriptorPool(device->logicalDevice, &descriptorPoolCI, nullptr, &descriptorPool)); + + // Descriptors for per-node uniform buffers + { + // Layout is global, so only create if it hasn't already been created before + if (descriptorSetLayoutUbo == VK_NULL_HANDLE) { + std::vector setLayoutBindings = { + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), + }; + VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{}; + descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + descriptorLayoutCI.bindingCount = static_cast(setLayoutBindings.size()); + descriptorLayoutCI.pBindings = setLayoutBindings.data(); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device->logicalDevice, &descriptorLayoutCI, nullptr, &descriptorSetLayoutUbo)); + } + for (auto node : nodes) { + prepareNodeDescriptor(node, descriptorSetLayoutUbo); + } + } + + // Descriptors for per-material images + { + // Layout is global, so only create if it hasn't already been created before + if (descriptorSetLayoutImage == VK_NULL_HANDLE) { + std::vector setLayoutBindings{}; + if (descriptorBindingFlags & DescriptorBindingFlags::ImageBaseColor) { + setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, static_cast(setLayoutBindings.size()))); + } + if (descriptorBindingFlags & DescriptorBindingFlags::ImageNormalMap) { + setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, static_cast(setLayoutBindings.size()))); + } + VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{}; + descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + descriptorLayoutCI.bindingCount = static_cast(setLayoutBindings.size()); + descriptorLayoutCI.pBindings = setLayoutBindings.data(); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device->logicalDevice, &descriptorLayoutCI, nullptr, &descriptorSetLayoutImage)); + } + for (auto& material : materials) { + if (material.baseColorTexture != nullptr) { + material.createDescriptorSet(descriptorPool, glTFModel::descriptorSetLayoutImage, descriptorBindingFlags); + } + } + } +} + +void glTFModel::Model::bindBuffers(VkCommandBuffer commandBuffer) +{ + const VkDeviceSize offsets[1] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets); + vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32); + buffersBound = true; +} + + +/* + helper function +*/ + +void glTFModel::Model::getNodeDimensions(Node* node, glm::vec3& min, glm::vec3& max) +{ + if (node->mesh) { + for (Primitive* primitive : node->mesh->primitives) { + glm::vec4 locMin = glm::vec4(primitive->dimensions.min, 1.0f) * node->getMatrix(); + glm::vec4 locMax = glm::vec4(primitive->dimensions.max, 1.0f) * node->getMatrix(); + if (locMin.x < min.x) { min.x = locMin.x; } + if (locMin.y < min.y) { min.y = locMin.y; } + if (locMin.z < min.z) { min.z = locMin.z; } + if (locMax.x > max.x) { max.x = locMax.x; } + if (locMax.y > max.y) { max.y = locMax.y; } + if (locMax.z > max.z) { max.z = locMax.z; } + } + } + for (auto child : node->children) { + getNodeDimensions(child, min, max); + } +} + +void glTFModel::Model::getSceneDimensions() +{ + dimensions.min = glm::vec3(FLT_MAX); + dimensions.max = glm::vec3(-FLT_MAX); + for (auto node : nodes) { + getNodeDimensions(node, dimensions.min, dimensions.max); + } + dimensions.size = dimensions.max - dimensions.min; + dimensions.center = (dimensions.min + dimensions.max) / 2.0f; + dimensions.radius = glm::distance(dimensions.min, dimensions.max) / 2.0f; +} + +glTFModel::Node* glTFModel::Model::findNode(Node* parent, uint32_t index) { Node* nodeFound = nullptr; if (parent->index == index) @@ -1130,7 +1458,7 @@ VulkanglTFModel::Node* VulkanglTFModel::findNode(Node* parent, uint32_t index) return nodeFound; } -VulkanglTFModel::Node* VulkanglTFModel::nodeFromIndex(uint32_t index) +glTFModel::Node* glTFModel::Model::nodeFromIndex(uint32_t index) { Node* nodeFound = nullptr; for (auto& node : nodes) @@ -1144,67 +1472,72 @@ VulkanglTFModel::Node* VulkanglTFModel::nodeFromIndex(uint32_t index) return nodeFound; } -void VulkanglTFModel::updateAnimation(float deltaTime, vks::Buffer& buffer) +/* + + gltf update function + +*/ + + +void glTFModel::Model::updateAnimation(uint32_t index, float time) { - constexpr uint32_t activeAnimation = 0; - Animation& animation = animations[activeAnimation]; - animation.currentTime += deltaTime; - if (animation.currentTime > animation.end) - { - animation.currentTime -= animation.end; + if (index > static_cast(animations.size()) - 1) { + std::cout << "No animation with index " << index << std::endl; + return; } + Animation& animation = animations[index]; - for (auto& channel : animation.channels) - { - auto& sampler = animation.samplers[channel.samplerIndex]; - for (size_t i = 0; i < sampler.inputs.size() - 1; ++i) - { - if (sampler.interpolation != "LINEAR") - { - std::cout << "This sample only supports linear interpolations\n"; - continue; - } - if ((animation.currentTime >= sampler.inputs[i]) && (animation.currentTime <= sampler.inputs[i + 1])) - { - float ratio = (animation.currentTime - sampler.inputs[i]) / (sampler.inputs[i + 1] - sampler.inputs[i]); - if (channel.path == "translation") - { - channel.node->translation = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], ratio); - channel.node->bAnimateNode = true; - } - if (channel.path == "rotation") - { - glm::quat q1; - q1.x = sampler.outputsVec4[i].x; - q1.y = sampler.outputsVec4[i].y; - q1.z = sampler.outputsVec4[i].z; - q1.w = sampler.outputsVec4[i].w; + bool updated = false; + for (auto& channel : animation.channels) { + glTFModel::AnimationSampler& sampler = animation.samplers[channel.samplerIndex]; + if (sampler.inputs.size() > sampler.outputsVec4.size()) { + continue; + } - glm::quat q2; - q2.x = sampler.outputsVec4[i + 1].x; - q2.y = sampler.outputsVec4[i + 1].y; - q2.z = sampler.outputsVec4[i + 1].z; - q2.w = sampler.outputsVec4[i + 1].w; - - channel.node->rotation = glm::normalize(glm::slerp(q1, q2, ratio)); - channel.node->bAnimateNode = true; - } - if (channel.path == "scale") - { - channel.node->scale = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], ratio); - channel.node->bAnimateNode = true; + for (auto i = 0; i < sampler.inputs.size() - 1; i++) { + if ((time >= sampler.inputs[i]) && (time <= sampler.inputs[i + 1])) { + float u = std::max(0.0f, time - sampler.inputs[i]) / (sampler.inputs[i + 1] - sampler.inputs[i]); + if (u <= 1.0f) { + switch (channel.path) { + case glTFModel::AnimationChannel::PathType::TRANSLATION: { + glm::vec4 trans = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], u); + channel.node->translation = glm::vec3(trans); + break; + } + case glTFModel::AnimationChannel::PathType::SCALE: { + glm::vec4 trans = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], u); + channel.node->scale = glm::vec3(trans); + break; + } + case glTFModel::AnimationChannel::PathType::ROTATION: { + glm::quat q1; + q1.x = sampler.outputsVec4[i].x; + q1.y = sampler.outputsVec4[i].y; + q1.z = sampler.outputsVec4[i].z; + q1.w = sampler.outputsVec4[i].w; + glm::quat q2; + q2.x = sampler.outputsVec4[i + 1].x; + q2.y = sampler.outputsVec4[i + 1].y; + q2.z = sampler.outputsVec4[i + 1].z; + q2.w = sampler.outputsVec4[i + 1].w; + channel.node->rotation = glm::normalize(glm::slerp(q1, q2, u)); + break; + } + } + updated = true; } } } } - std::vector nodeMatrics(nodeCount); - for (auto& node : nodes) - { - updateNodeMatrix(node, nodeMatrics); + if (updated) { + for (auto& node : nodes) { + node->update(); + } } - buffer.copyTo(nodeMatrics.data(), nodeCount * sizeof(glm::mat4)); + } +/* void VulkanglTFModel::updateNodeMatrix(Node* node, std::vector& nodeMatrics) { nodeMatrics[node->index] = getNodeMatrix(node); @@ -1225,79 +1558,73 @@ glm::mat4 VulkanglTFModel::getNodeMatrix(Node* node) } return nodeMatrix; } +*/ + +void glTFModel::Model::prepareNodeDescriptor(glTFModel::Node* node, VkDescriptorSetLayout descriptorSetLayout) { + if (node->mesh) { + VkDescriptorSetAllocateInfo descriptorSetAllocInfo{}; + descriptorSetAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + descriptorSetAllocInfo.descriptorPool = descriptorPool; + descriptorSetAllocInfo.pSetLayouts = &descriptorSetLayout; + descriptorSetAllocInfo.descriptorSetCount = 1; + VK_CHECK_RESULT(vkAllocateDescriptorSets(device->logicalDevice, &descriptorSetAllocInfo, &node->mesh->uniformBuffer.descriptorSet)); + + VkWriteDescriptorSet writeDescriptorSet{}; + writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + writeDescriptorSet.descriptorCount = 1; + writeDescriptorSet.dstSet = node->mesh->uniformBuffer.descriptorSet; + writeDescriptorSet.dstBinding = 0; + writeDescriptorSet.pBufferInfo = &node->mesh->uniformBuffer.descriptor; + + vkUpdateDescriptorSets(device->logicalDevice, 1, &writeDescriptorSet, 0, nullptr); + } + for (auto& child : node->children) { + prepareNodeDescriptor(child, descriptorSetLayout); + } +} /* glTF rendering functions */ -// Draw a single node including child nodes (if present) -void VulkanglTFModel::drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node* node, bool bPushConstants) +void glTFModel::Model::drawNode(Node* node, VkCommandBuffer commandBuffer, uint32_t renderFlags, VkPipelineLayout pipelineLayout, uint32_t bindImageSet) { - if (node->mesh.primitives.size() > 0) { - // Pass the node's matrix via push constants - // Traverse the node hierarchy to the top-most parent to get the final matrix of the current node - glm::mat4 nodeMatrix = node->matrix; - VulkanglTFModel::Node* currentParent = node->parent; - while (currentParent) { - nodeMatrix = currentParent->matrix * nodeMatrix; - currentParent = currentParent->parent; - } - - for (VulkanglTFModel::Primitive& primitive : node->mesh.primitives) { - if (primitive.indexCount > 0) { - // Get the texture index for this primitive - if (textures.size() > 0) - { - //binding base color texture - if (materials[primitive.materialIndex].baseColorTextureIndex < textures.size()) - { - VulkanglTFModel::Texture texture = textures[materials[primitive.materialIndex].baseColorTextureIndex]; - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &images[texture.imageIndex].descriptorSet, 0, nullptr); - - } - - //normal map binding - if (materials[primitive.materialIndex].normalMapTextureIndex < textures.size()) - { - auto normalMap = textures[materials[primitive.materialIndex].normalMapTextureIndex]; - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 2, 1, &images[normalMap.imageIndex].descriptorSet, 0, nullptr); - } - //rough map binding - if (materials[primitive.materialIndex].matalicRoughTextureIndex < textures.size()) - { - auto roughMetalMap = textures[materials[primitive.materialIndex].matalicRoughTextureIndex]; - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 3, 1, &images[roughMetalMap.imageIndex].descriptorSet, 0, nullptr); - } - - //emissive texture binding - if (materials[primitive.materialIndex].emissiveTextureIndex >= 0 && materials[primitive.materialIndex].emissiveTextureIndex < textures.size()) - { - auto emissiveMap = textures[materials[primitive.materialIndex].emissiveTextureIndex]; - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 4, 1, &images[emissiveMap.imageIndex].descriptorSet, 0, nullptr); - } - - // Bind the descriptor for the current primitive's texture - - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 5, 1, &materials[primitive.materialIndex].materialData.descriptorSet, 0, nullptr); + if (node->mesh) { + for (Primitive* primitive : node->mesh->primitives) { + bool skip = false; + const glTFModel::Material& material = primitive->material; + if (renderFlags & RenderFlags::RenderOpaqueNodes) { + skip = (material.alphaMode != Material::ALPHAMODE_OPAQUE); + } + if (renderFlags & RenderFlags::RenderAlphaMaskedNodes) { + skip = (material.alphaMode != Material::ALPHAMODE_MASK); + } + if (renderFlags & RenderFlags::RenderAlphaBlendedNodes) { + skip = (material.alphaMode != Material::ALPHAMODE_BLEND); + } + if (!skip) { + if (renderFlags & RenderFlags::BindImages) { + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, bindImageSet, 1, &material.descriptorSet, 0, nullptr); } - vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0); + vkCmdDrawIndexed(commandBuffer, primitive->indexCount, 1, primitive->firstIndex, 0, 0); } } } for (auto& child : node->children) { - drawNode(commandBuffer, pipelineLayout, child, bPushConstants); + drawNode(child, commandBuffer, renderFlags, pipelineLayout, bindImageSet); } } // Draw the glTF scene starting at the top-level-nodes -void VulkanglTFModel::draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, bool flag = true) +void glTFModel::Model::draw(VkCommandBuffer commandBuffer, uint32_t renderFlags, VkPipelineLayout pipelineLayout, uint32_t bindImageSet) { - // All vertices and indices are stored in single buffers, so we only need to bind once - VkDeviceSize offsets[1] = { 0 }; - vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets); - vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32); - // Render all nodes at top-level + if (!buffersBound) { + const VkDeviceSize offsets[1] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets); + vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32); + } for (auto& node : nodes) { - drawNode(commandBuffer, pipelineLayout, node, flag); + drawNode(node, commandBuffer, renderFlags, pipelineLayout, bindImageSet); } } \ No newline at end of file