/* * Vulkan glTF model and texture loading class based on tinyglTF (https://github.com/syoyo/tinygltf) * * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #define TINYGLTF_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION #define TINYGLTF_NO_STB_IMAGE_WRITE #include "VulkanglTFModel.h" VkDescriptorSetLayout vkglTF::descriptorSetLayoutImage = VK_NULL_HANDLE; VkDescriptorSetLayout vkglTF::descriptorSetLayoutUbo = VK_NULL_HANDLE; VkMemoryPropertyFlags vkglTF::memoryPropertyFlags = 0; uint32_t vkglTF::descriptorBindingFlags = vkglTF::DescriptorBindingFlags::ImageBaseColor; /* We use a custom image loading function with tinyglTF, so we can do custom stuff loading ktx textures */ bool loadImageDataFunc(tinygltf::Image* image, const int imageIndex, std::string* error, std::string* warning, int req_width, int req_height, const unsigned char* bytes, int size, void* userData) { // KTX files will be handled by our own code if (image->uri.find_last_of(".") != std::string::npos) { if (image->uri.substr(image->uri.find_last_of(".") + 1) == "ktx") { return true; } } return tinygltf::LoadImageData(image, imageIndex, error, warning, req_width, req_height, bytes, size, userData); } bool loadImageDataFuncEmpty(tinygltf::Image* image, const int imageIndex, std::string* error, std::string* warning, int req_width, int req_height, const unsigned char* bytes, int size, void* userData) { // This function will be used for samples that don't require images to be loaded return true; } /* glTF texture loading class */ void vkglTF::Texture::updateDescriptor() { descriptor.sampler = sampler; descriptor.imageView = view; descriptor.imageLayout = imageLayout; } void vkglTF::Texture::destroy() { if (device) { vkDestroyImageView(device->logicalDevice, view, nullptr); vkDestroyImage(device->logicalDevice, image, nullptr); vkFreeMemory(device->logicalDevice, deviceMemory, nullptr); vkDestroySampler(device->logicalDevice, sampler, nullptr); } } void vkglTF::Texture::fromglTfImage(tinygltf::Image &gltfimage, std::string path, vks::VulkanDevice *device, VkQueue copyQueue) { this->device = device; bool isKtx = false; // Image points to an external ktx file if (gltfimage.uri.find_last_of(".") != std::string::npos) { if (gltfimage.uri.substr(gltfimage.uri.find_last_of(".") + 1) == "ktx") { isKtx = true; } } VkFormat format; if (!isKtx) { // Texture was loaded using STB_Image unsigned char* buffer = nullptr; VkDeviceSize bufferSize = 0; bool deleteBuffer = false; if (gltfimage.component == 3) { // Most devices don't support RGB only on Vulkan so convert if necessary // TODO: Check actual format support and transform only if required bufferSize = gltfimage.width * gltfimage.height * 4; buffer = new unsigned char[bufferSize]; unsigned char* rgba = buffer; unsigned char* rgb = &gltfimage.image[0]; for (size_t i = 0; i < gltfimage.width * gltfimage.height; ++i) { for (int32_t j = 0; j < 3; ++j) { rgba[j] = rgb[j]; } rgba += 4; rgb += 3; } deleteBuffer = true; } else { buffer = &gltfimage.image[0]; bufferSize = gltfimage.image.size(); } format = VK_FORMAT_R8G8B8A8_UNORM; VkFormatProperties formatProperties; width = gltfimage.width; height = gltfimage.height; mipLevels = static_cast(floor(log2(std::max(width, height))) + 1.0); vkGetPhysicalDeviceFormatProperties(device->physicalDevice, format, &formatProperties); assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT); assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT); VkMemoryAllocateInfo memAllocInfo{}; memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; VkMemoryRequirements memReqs{}; VkBuffer stagingBuffer; VkDeviceMemory stagingMemory; VkBufferCreateInfo bufferCreateInfo{}; bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferCreateInfo.size = bufferSize; bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer)); vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory)); VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0)); uint8_t* data; VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void**)&data)); memcpy(data, buffer, bufferSize); vkUnmapMemory(device->logicalDevice, stagingMemory); VkImageCreateInfo imageCreateInfo{}; imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = format; imageCreateInfo.mipLevels = mipLevels; imageCreateInfo.arrayLayers = 1; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT; imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageCreateInfo.extent = { width, height, 1 }; imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image)); vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory)); VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0)); VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.levelCount = 1; subresourceRange.layerCount = 1; { VkImageMemoryBarrier imageMemoryBarrier{}; imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; imageMemoryBarrier.srcAccessMask = 0; imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; imageMemoryBarrier.image = image; imageMemoryBarrier.subresourceRange = subresourceRange; vkCmdPipelineBarrier(copyCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); } VkBufferImageCopy bufferCopyRegion = {}; bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; bufferCopyRegion.imageSubresource.mipLevel = 0; bufferCopyRegion.imageSubresource.baseArrayLayer = 0; bufferCopyRegion.imageSubresource.layerCount = 1; bufferCopyRegion.imageExtent.width = width; bufferCopyRegion.imageExtent.height = height; bufferCopyRegion.imageExtent.depth = 1; vkCmdCopyBufferToImage(copyCmd, stagingBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion); { VkImageMemoryBarrier imageMemoryBarrier{}; imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; imageMemoryBarrier.image = image; imageMemoryBarrier.subresourceRange = subresourceRange; vkCmdPipelineBarrier(copyCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); } device->flushCommandBuffer(copyCmd, copyQueue, true); vkFreeMemory(device->logicalDevice, stagingMemory, nullptr); vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr); // Generate the mip chain (glTF uses jpg and png, so we need to create this manually) VkCommandBuffer blitCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); for (uint32_t i = 1; i < mipLevels; i++) { VkImageBlit imageBlit{}; imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; imageBlit.srcSubresource.layerCount = 1; imageBlit.srcSubresource.mipLevel = i - 1; imageBlit.srcOffsets[1].x = int32_t(width >> (i - 1)); imageBlit.srcOffsets[1].y = int32_t(height >> (i - 1)); imageBlit.srcOffsets[1].z = 1; imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; imageBlit.dstSubresource.layerCount = 1; imageBlit.dstSubresource.mipLevel = i; imageBlit.dstOffsets[1].x = int32_t(width >> i); imageBlit.dstOffsets[1].y = int32_t(height >> i); imageBlit.dstOffsets[1].z = 1; VkImageSubresourceRange mipSubRange = {}; mipSubRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; mipSubRange.baseMipLevel = i; mipSubRange.levelCount = 1; mipSubRange.layerCount = 1; { VkImageMemoryBarrier imageMemoryBarrier{}; imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; imageMemoryBarrier.srcAccessMask = 0; imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; imageMemoryBarrier.image = image; imageMemoryBarrier.subresourceRange = mipSubRange; vkCmdPipelineBarrier(blitCmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); } vkCmdBlitImage(blitCmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageBlit, VK_FILTER_LINEAR); { VkImageMemoryBarrier imageMemoryBarrier{}; imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; imageMemoryBarrier.image = image; imageMemoryBarrier.subresourceRange = mipSubRange; vkCmdPipelineBarrier(blitCmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); } } subresourceRange.levelCount = mipLevels; imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; { VkImageMemoryBarrier imageMemoryBarrier{}; imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; imageMemoryBarrier.image = image; imageMemoryBarrier.subresourceRange = subresourceRange; vkCmdPipelineBarrier(blitCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); } if (deleteBuffer) { delete[] buffer; } device->flushCommandBuffer(blitCmd, copyQueue, true); } else { // Texture is stored in an external ktx file std::string filename = path + "/" + gltfimage.uri; ktxTexture* ktxTexture; ktxResult result = KTX_SUCCESS; #if defined(__ANDROID__) AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING); if (!asset) { vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1); } size_t size = AAsset_getLength(asset); assert(size > 0); ktx_uint8_t* textureData = new ktx_uint8_t[size]; AAsset_read(asset, textureData, size); AAsset_close(asset); result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture); delete[] textureData; #else if (!vks::tools::fileExists(filename)) { vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1); } result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture); #endif assert(result == KTX_SUCCESS); this->device = device; width = ktxTexture->baseWidth; height = ktxTexture->baseHeight; mipLevels = ktxTexture->numLevels; ktx_uint8_t* ktxTextureData = ktxTexture_GetData(ktxTexture); ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture); // @todo: Use ktxTexture_GetVkFormat(ktxTexture) format = VK_FORMAT_R8G8B8A8_UNORM; // Get device properties for the requested texture format VkFormatProperties formatProperties; vkGetPhysicalDeviceFormatProperties(device->physicalDevice, format, &formatProperties); VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); VkBuffer stagingBuffer; VkDeviceMemory stagingMemory; VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(); bufferCreateInfo.size = ktxTextureSize; // This buffer is used as a transfer source for the buffer copy bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer)); VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); VkMemoryRequirements memReqs; vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory)); VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0)); uint8_t* data; VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void**)&data)); memcpy(data, ktxTextureData, ktxTextureSize); vkUnmapMemory(device->logicalDevice, stagingMemory); std::vector bufferCopyRegions; for (uint32_t i = 0; i < mipLevels; i++) { ktx_size_t offset; KTX_error_code result = ktxTexture_GetImageOffset(ktxTexture, i, 0, 0, &offset); assert(result == KTX_SUCCESS); VkBufferImageCopy bufferCopyRegion = {}; bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; bufferCopyRegion.imageSubresource.mipLevel = i; bufferCopyRegion.imageSubresource.baseArrayLayer = 0; bufferCopyRegion.imageSubresource.layerCount = 1; bufferCopyRegion.imageExtent.width = std::max(1u, ktxTexture->baseWidth >> i); bufferCopyRegion.imageExtent.height = std::max(1u, ktxTexture->baseHeight >> i); bufferCopyRegion.imageExtent.depth = 1; bufferCopyRegion.bufferOffset = offset; bufferCopyRegions.push_back(bufferCopyRegion); } // Create optimal tiled target image VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = format; imageCreateInfo.mipLevels = mipLevels; imageCreateInfo.arrayLayers = 1; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageCreateInfo.extent = { width, height, 1 }; imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image)); vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory)); VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0)); VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = mipLevels; subresourceRange.layerCount = 1; vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange); vkCmdCopyBufferToImage(copyCmd, stagingBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(bufferCopyRegions.size()), bufferCopyRegions.data()); vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange); device->flushCommandBuffer(copyCmd, copyQueue); this->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; vkFreeMemory(device->logicalDevice, stagingMemory, nullptr); vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr); ktxTexture_Destroy(ktxTexture); } VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; samplerInfo.compareOp = VK_COMPARE_OP_NEVER; samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; samplerInfo.maxAnisotropy = 1.0; samplerInfo.anisotropyEnable = VK_FALSE; samplerInfo.maxLod = (float)mipLevels; samplerInfo.maxAnisotropy = 8.0f; samplerInfo.anisotropyEnable = VK_TRUE; VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerInfo, nullptr, &sampler)); VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewInfo.format = format; viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; viewInfo.subresourceRange.layerCount = 1; viewInfo.subresourceRange.levelCount = mipLevels; VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewInfo, nullptr, &view)); descriptor.sampler = sampler; descriptor.imageView = view; descriptor.imageLayout = imageLayout; } /* glTF material */ void vkglTF::Material::createDescriptorSet(VkDescriptorPool descriptorPool, VkDescriptorSetLayout descriptorSetLayout, uint32_t descriptorBindingFlags) { 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, &descriptorSet)); std::vector imageDescriptors{}; std::vector writeDescriptorSets{}; if (descriptorBindingFlags & DescriptorBindingFlags::ImageBaseColor) { imageDescriptors.push_back(baseColorTexture->descriptor); VkWriteDescriptorSet writeDescriptorSet{}; writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; writeDescriptorSet.descriptorCount = 1; writeDescriptorSet.dstSet = descriptorSet; writeDescriptorSet.dstBinding = static_cast(writeDescriptorSets.size()); writeDescriptorSet.pImageInfo = &baseColorTexture->descriptor; writeDescriptorSets.push_back(writeDescriptorSet); } if (normalTexture && descriptorBindingFlags & DescriptorBindingFlags::ImageNormalMap) { imageDescriptors.push_back(normalTexture->descriptor); VkWriteDescriptorSet writeDescriptorSet{}; writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; writeDescriptorSet.descriptorCount = 1; writeDescriptorSet.dstSet = descriptorSet; writeDescriptorSet.dstBinding = static_cast(writeDescriptorSets.size()); writeDescriptorSet.pImageInfo = &normalTexture->descriptor; writeDescriptorSets.push_back(writeDescriptorSet); } vkUpdateDescriptorSets(device->logicalDevice, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } /* glTF primitive */ void vkglTF::Primitive::setDimensions(glm::vec3 min, glm::vec3 max) { dimensions.min = min; dimensions.max = max; dimensions.size = max - min; dimensions.center = (min + max) / 2.0f; dimensions.radius = glm::distance(min, max) / 2.0f; } /* glTF mesh */ vkglTF::Mesh::Mesh(vks::VulkanDevice *device, glm::mat4 matrix) { this->device = device; this->uniformBlock.matrix = matrix; VK_CHECK_RESULT(device->createBuffer( VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, sizeof(uniformBlock), &uniformBuffer.buffer, &uniformBuffer.memory, &uniformBlock)); VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, uniformBuffer.memory, 0, sizeof(uniformBlock), 0, &uniformBuffer.mapped)); uniformBuffer.descriptor = { uniformBuffer.buffer, 0, sizeof(uniformBlock) }; }; vkglTF::Mesh::~Mesh() { vkDestroyBuffer(device->logicalDevice, uniformBuffer.buffer, nullptr); vkFreeMemory(device->logicalDevice, uniformBuffer.memory, nullptr); for(auto primitive : primitives) { delete primitive; } } /* glTF node */ glm::mat4 vkglTF::Node::localMatrix() { return glm::translate(glm::mat4(1.0f), translation) * glm::mat4(rotation) * glm::scale(glm::mat4(1.0f), scale) * matrix; } glm::mat4 vkglTF::Node::getMatrix() { glm::mat4 m = localMatrix(); vkglTF::Node *p = parent; while (p) { m = p->localMatrix() * m; p = p->parent; } return m; } void vkglTF::Node::update() { if (mesh) { glm::mat4 m = getMatrix(); if (skin) { mesh->uniformBlock.matrix = m; // Update join matrices glm::mat4 inverseTransform = glm::inverse(m); for (size_t i = 0; i < skin->joints.size(); i++) { vkglTF::Node *jointNode = skin->joints[i]; glm::mat4 jointMat = jointNode->getMatrix() * skin->inverseBindMatrices[i]; jointMat = inverseTransform * jointMat; mesh->uniformBlock.jointMatrix[i] = jointMat; } mesh->uniformBlock.jointcount = (float)skin->joints.size(); memcpy(mesh->uniformBuffer.mapped, &mesh->uniformBlock, sizeof(mesh->uniformBlock)); } else { memcpy(mesh->uniformBuffer.mapped, &m, sizeof(glm::mat4)); } } for (auto& child : children) { child->update(); } } vkglTF::Node::~Node() { if (mesh) { delete mesh; } for (auto& child : children) { delete child; } } /* glTF default vertex layout with easy Vulkan mapping functions */ VkVertexInputBindingDescription vkglTF::Vertex::vertexInputBindingDescription; std::vector vkglTF::Vertex::vertexInputAttributeDescriptions; VkPipelineVertexInputStateCreateInfo vkglTF::Vertex::pipelineVertexInputStateCreateInfo; VkVertexInputBindingDescription vkglTF::Vertex::inputBindingDescription(uint32_t binding) { return VkVertexInputBindingDescription({ binding, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX }); } VkVertexInputAttributeDescription vkglTF::Vertex::inputAttributeDescription(uint32_t binding, uint32_t location, VertexComponent component) { switch (component) { case VertexComponent::Position: return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos) }); case VertexComponent::Normal: return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal) }); case VertexComponent::UV: return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv) }); case VertexComponent::Color: return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, color) }); case VertexComponent::Tangent: return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, tangent)} ); case VertexComponent::Joint0: return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, joint0) }); case VertexComponent::Weight0: return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, weight0) }); default: return VkVertexInputAttributeDescription({}); } } std::vector vkglTF::Vertex::inputAttributeDescriptions(uint32_t binding, const std::vector components) { std::vector result; uint32_t location = 0; for (VertexComponent component : components) { result.push_back(Vertex::inputAttributeDescription(binding, location, component)); location++; } return result; } /** @brief Returns the default pipeline vertex input state create info structure for the requested vertex components */ VkPipelineVertexInputStateCreateInfo* vkglTF::Vertex::getPipelineVertexInputState(const std::vector components) { vertexInputBindingDescription = Vertex::inputBindingDescription(0); Vertex::vertexInputAttributeDescriptions = Vertex::inputAttributeDescriptions(0, components); pipelineVertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; pipelineVertexInputStateCreateInfo.vertexBindingDescriptionCount = 1; pipelineVertexInputStateCreateInfo.pVertexBindingDescriptions = &Vertex::vertexInputBindingDescription; pipelineVertexInputStateCreateInfo.vertexAttributeDescriptionCount = static_cast(Vertex::vertexInputAttributeDescriptions.size()); pipelineVertexInputStateCreateInfo.pVertexAttributeDescriptions = Vertex::vertexInputAttributeDescriptions.data(); return &pipelineVertexInputStateCreateInfo; } vkglTF::Texture* vkglTF::Model::getTexture(uint32_t index) { if (index < textures.size()) { return &textures[index]; } return nullptr; } void vkglTF::Model::createEmptyTexture(VkQueue transferQueue) { emptyTexture.device = device; emptyTexture.width = 1; emptyTexture.height = 1; emptyTexture.layerCount = 1; emptyTexture.mipLevels = 1; size_t bufferSize = emptyTexture.width * emptyTexture.height * 4; unsigned char* buffer = new unsigned char[bufferSize]; memset(buffer, 0, bufferSize); VkBuffer stagingBuffer; VkDeviceMemory stagingMemory; VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(); bufferCreateInfo.size = bufferSize; // This buffer is used as a transfer source for the buffer copy bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer)); VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); VkMemoryRequirements memReqs; vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory)); VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0)); // Copy texture data into staging buffer uint8_t* data; VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void**)&data)); memcpy(data, buffer, bufferSize); vkUnmapMemory(device->logicalDevice, stagingMemory); VkBufferImageCopy bufferCopyRegion = {}; bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; bufferCopyRegion.imageSubresource.layerCount = 1; bufferCopyRegion.imageExtent.width = emptyTexture.width; bufferCopyRegion.imageExtent.height = emptyTexture.height; bufferCopyRegion.imageExtent.depth = 1; // Create optimal tiled target image VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; imageCreateInfo.mipLevels = 1; imageCreateInfo.arrayLayers = 1; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageCreateInfo.extent = { emptyTexture.width, emptyTexture.height, 1 }; imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &emptyTexture.image)); vkGetImageMemoryRequirements(device->logicalDevice, emptyTexture.image, &memReqs); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &emptyTexture.deviceMemory)); VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, emptyTexture.image, emptyTexture.deviceMemory, 0)); VkImageSubresourceRange subresourceRange{}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = 1; subresourceRange.layerCount = 1; VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); vks::tools::setImageLayout(copyCmd, emptyTexture.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange); vkCmdCopyBufferToImage(copyCmd, stagingBuffer, emptyTexture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion); vks::tools::setImageLayout(copyCmd, emptyTexture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange); device->flushCommandBuffer(copyCmd, transferQueue); emptyTexture.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; // Clean up staging resources vkFreeMemory(device->logicalDevice, stagingMemory, nullptr); vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr); VkSamplerCreateInfo samplerCreateInfo = vks::initializers::samplerCreateInfo(); samplerCreateInfo.magFilter = VK_FILTER_LINEAR; samplerCreateInfo.minFilter = VK_FILTER_LINEAR; samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER; samplerCreateInfo.maxAnisotropy = 1.0f; VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &emptyTexture.sampler)); VkImageViewCreateInfo viewCreateInfo = vks::initializers::imageViewCreateInfo(); viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; viewCreateInfo.subresourceRange.levelCount = 1; viewCreateInfo.image = emptyTexture.image; VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &emptyTexture.view)); emptyTexture.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; emptyTexture.descriptor.imageView = emptyTexture.view; emptyTexture.descriptor.sampler = emptyTexture.sampler; } /* glTF model loading and rendering class */ vkglTF::Model::~Model() { vkDestroyBuffer(device->logicalDevice, vertices.buffer, nullptr); vkFreeMemory(device->logicalDevice, vertices.memory, nullptr); vkDestroyBuffer(device->logicalDevice, indices.buffer, nullptr); vkFreeMemory(device->logicalDevice, indices.memory, nullptr); for (auto texture : textures) { texture.destroy(); } for (auto node : nodes) { delete node; } for (auto skin : skins) { delete skin; } if (descriptorSetLayoutUbo != VK_NULL_HANDLE) { vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayoutUbo, nullptr); descriptorSetLayoutUbo = VK_NULL_HANDLE; } if (descriptorSetLayoutImage != VK_NULL_HANDLE) { vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayoutImage, nullptr); descriptorSetLayoutImage = VK_NULL_HANDLE; } vkDestroyDescriptorPool(device->logicalDevice, descriptorPool, nullptr); emptyTexture.destroy(); } void vkglTF::Model::loadNode(vkglTF::Node *parent, const tinygltf::Node &node, uint32_t nodeIndex, const tinygltf::Model &model, std::vector& indexBuffer, std::vector& vertexBuffer, float globalscale) { vkglTF::Node *newNode = new Node{}; newNode->index = nodeIndex; newNode->parent = parent; newNode->name = node.name; newNode->skinIndex = node.skin; newNode->matrix = glm::mat4(1.0f); // Generate local node matrix glm::vec3 translation = glm::vec3(0.0f); if (node.translation.size() == 3) { translation = glm::make_vec3(node.translation.data()); newNode->translation = translation; } glm::mat4 rotation = glm::mat4(1.0f); if (node.rotation.size() == 4) { glm::quat q = glm::make_quat(node.rotation.data()); newNode->rotation = glm::mat4(q); } glm::vec3 scale = glm::vec3(1.0f); if (node.scale.size() == 3) { scale = glm::make_vec3(node.scale.data()); newNode->scale = scale; } if (node.matrix.size() == 16) { newNode->matrix = glm::make_mat4x4(node.matrix.data()); if (globalscale != 1.0f) { //newNode->matrix = glm::scale(newNode->matrix, glm::vec3(globalscale)); } }; // Node with children if (node.children.size() > 0) { for (auto i = 0; i < node.children.size(); i++) { loadNode(newNode, model.nodes[node.children[i]], node.children[i], model, indexBuffer, vertexBuffer, globalscale); } } // Node contains mesh data if (node.mesh > -1) { const tinygltf::Mesh mesh = model.meshes[node.mesh]; Mesh *newMesh = new Mesh(device, newNode->matrix); newMesh->name = mesh.name; for (size_t j = 0; j < mesh.primitives.size(); j++) { const tinygltf::Primitive &primitive = mesh.primitives[j]; if (primitive.indices < 0) { continue; } uint32_t indexStart = static_cast(indexBuffer.size()); uint32_t vertexStart = static_cast(vertexBuffer.size()); uint32_t indexCount = 0; uint32_t vertexCount = 0; glm::vec3 posMin{}; glm::vec3 posMax{}; bool hasSkin = false; // Vertices { const float *bufferPos = nullptr; const float *bufferNormals = nullptr; const float *bufferTexCoords = nullptr; const float* bufferColors = nullptr; const float *bufferTangents = nullptr; uint32_t numColorComponents; const uint16_t *bufferJoints = nullptr; const float *bufferWeights = nullptr; // Position attribute is required assert(primitive.attributes.find("POSITION") != primitive.attributes.end()); const tinygltf::Accessor &posAccessor = model.accessors[primitive.attributes.find("POSITION")->second]; const tinygltf::BufferView &posView = model.bufferViews[posAccessor.bufferView]; bufferPos = reinterpret_cast(&(model.buffers[posView.buffer].data[posAccessor.byteOffset + posView.byteOffset])); posMin = glm::vec3(posAccessor.minValues[0], posAccessor.minValues[1], posAccessor.minValues[2]); posMax = glm::vec3(posAccessor.maxValues[0], posAccessor.maxValues[1], posAccessor.maxValues[2]); if (primitive.attributes.find("NORMAL") != primitive.attributes.end()) { const tinygltf::Accessor &normAccessor = model.accessors[primitive.attributes.find("NORMAL")->second]; const tinygltf::BufferView &normView = model.bufferViews[normAccessor.bufferView]; bufferNormals = reinterpret_cast(&(model.buffers[normView.buffer].data[normAccessor.byteOffset + normView.byteOffset])); } if (primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end()) { const tinygltf::Accessor &uvAccessor = model.accessors[primitive.attributes.find("TEXCOORD_0")->second]; const tinygltf::BufferView &uvView = model.bufferViews[uvAccessor.bufferView]; bufferTexCoords = reinterpret_cast(&(model.buffers[uvView.buffer].data[uvAccessor.byteOffset + uvView.byteOffset])); } if (primitive.attributes.find("COLOR_0") != primitive.attributes.end()) { const tinygltf::Accessor& colorAccessor = model.accessors[primitive.attributes.find("COLOR_0")->second]; const tinygltf::BufferView& colorView = model.bufferViews[colorAccessor.bufferView]; // Color buffer are either of type vec3 or vec4 numColorComponents = colorAccessor.type == TINYGLTF_PARAMETER_TYPE_FLOAT_VEC3 ? 3 : 4; bufferColors = reinterpret_cast(&(model.buffers[colorView.buffer].data[colorAccessor.byteOffset + colorView.byteOffset])); } if (primitive.attributes.find("TANGENT") != primitive.attributes.end()) { const tinygltf::Accessor &tangentAccessor = model.accessors[primitive.attributes.find("TANGENT")->second]; const tinygltf::BufferView &tangentView = model.bufferViews[tangentAccessor.bufferView]; bufferTangents = reinterpret_cast(&(model.buffers[tangentView.buffer].data[tangentAccessor.byteOffset + tangentView.byteOffset])); } // Skinning // Joints if (primitive.attributes.find("JOINTS_0") != primitive.attributes.end()) { const tinygltf::Accessor &jointAccessor = model.accessors[primitive.attributes.find("JOINTS_0")->second]; const tinygltf::BufferView &jointView = model.bufferViews[jointAccessor.bufferView]; bufferJoints = reinterpret_cast(&(model.buffers[jointView.buffer].data[jointAccessor.byteOffset + jointView.byteOffset])); } if (primitive.attributes.find("WEIGHTS_0") != primitive.attributes.end()) { const tinygltf::Accessor &uvAccessor = model.accessors[primitive.attributes.find("WEIGHTS_0")->second]; const tinygltf::BufferView &uvView = model.bufferViews[uvAccessor.bufferView]; bufferWeights = reinterpret_cast(&(model.buffers[uvView.buffer].data[uvAccessor.byteOffset + uvView.byteOffset])); } hasSkin = (bufferJoints && bufferWeights); vertexCount = static_cast(posAccessor.count); for (size_t v = 0; v < posAccessor.count; v++) { Vertex vert{}; vert.pos = glm::vec4(glm::make_vec3(&bufferPos[v * 3]), 1.0f); vert.normal = glm::normalize(glm::vec3(bufferNormals ? glm::make_vec3(&bufferNormals[v * 3]) : glm::vec3(0.0f))); vert.uv = bufferTexCoords ? glm::make_vec2(&bufferTexCoords[v * 2]) : glm::vec3(0.0f); if (bufferColors) { switch (numColorComponents) { case 3: vert.color = glm::vec4(glm::make_vec3(&bufferColors[v * 3]), 1.0f); case 4: vert.color = glm::make_vec4(&bufferColors[v * 4]); } } else { vert.color = glm::vec4(1.0f); } vert.tangent = bufferTangents ? glm::vec4(glm::make_vec4(&bufferTangents[v * 4])) : glm::vec4(0.0f); vert.joint0 = hasSkin ? glm::vec4(glm::make_vec4(&bufferJoints[v * 4])) : glm::vec4(0.0f); vert.weight0 = hasSkin ? glm::make_vec4(&bufferWeights[v * 4]) : glm::vec4(0.0f); vertexBuffer.push_back(vert); } } // Indices { const tinygltf::Accessor &accessor = model.accessors[primitive.indices]; const tinygltf::BufferView &bufferView = model.bufferViews[accessor.bufferView]; const tinygltf::Buffer &buffer = model.buffers[bufferView.buffer]; indexCount = static_cast(accessor.count); switch (accessor.componentType) { case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { uint32_t *buf = new uint32_t[accessor.count]; memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint32_t)); for (size_t index = 0; index < accessor.count; index++) { indexBuffer.push_back(buf[index] + vertexStart); } delete[] buf; break; } case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: { uint16_t *buf = new uint16_t[accessor.count]; memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint16_t)); for (size_t index = 0; index < accessor.count; index++) { indexBuffer.push_back(buf[index] + vertexStart); } delete[] buf; break; } case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: { uint8_t *buf = new uint8_t[accessor.count]; memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint8_t)); for (size_t index = 0; index < accessor.count; index++) { indexBuffer.push_back(buf[index] + vertexStart); } delete[] buf; break; } default: std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl; return; } } Primitive *newPrimitive = new Primitive(indexStart, indexCount, primitive.material > -1 ? materials[primitive.material] : materials.back()); newPrimitive->firstVertex = vertexStart; newPrimitive->vertexCount = vertexCount; newPrimitive->setDimensions(posMin, posMax); newMesh->primitives.push_back(newPrimitive); } newNode->mesh = newMesh; } if (parent) { parent->children.push_back(newNode); } else { nodes.push_back(newNode); } linearNodes.push_back(newNode); } void vkglTF::Model::loadSkins(tinygltf::Model &gltfModel) { for (tinygltf::Skin &source : gltfModel.skins) { Skin *newSkin = new Skin{}; newSkin->name = source.name; // Find skeleton root node if (source.skeleton > -1) { newSkin->skeletonRoot = nodeFromIndex(source.skeleton); } // Find joint nodes for (int jointIndex : source.joints) { Node* node = nodeFromIndex(jointIndex); if (node) { newSkin->joints.push_back(nodeFromIndex(jointIndex)); } } // Get inverse bind matrices from buffer if (source.inverseBindMatrices > -1) { const tinygltf::Accessor &accessor = gltfModel.accessors[source.inverseBindMatrices]; const tinygltf::BufferView &bufferView = gltfModel.bufferViews[accessor.bufferView]; const tinygltf::Buffer &buffer = gltfModel.buffers[bufferView.buffer]; newSkin->inverseBindMatrices.resize(accessor.count); memcpy(newSkin->inverseBindMatrices.data(), &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(glm::mat4)); } skins.push_back(newSkin); } } void vkglTF::Model::loadImages(tinygltf::Model &gltfModel, vks::VulkanDevice *device, VkQueue transferQueue) { for (tinygltf::Image &image : gltfModel.images) { vkglTF::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 vkglTF::Model::loadMaterials(tinygltf::Model &gltfModel) { for (tinygltf::Material &mat : gltfModel.materials) { vkglTF::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 vkglTF::Model::loadAnimations(tinygltf::Model &gltfModel) { for (tinygltf::Animation &anim : gltfModel.animations) { vkglTF::Animation animation{}; animation.name = anim.name; if (anim.name.empty()) { animation.name = std::to_string(animations.size()); } // Samplers for (auto &samp : anim.samplers) { vkglTF::AnimationSampler sampler{}; 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 input time values { 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]); } delete[] buf; for (auto input : sampler.inputs) { if (input < animation.start) { animation.start = input; }; if (input > animation.end) { animation.end = input; } } } // Read sampler output T/R/S values { 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: { 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: { std::cout << "unknown type" << std::endl; break; } } } animation.samplers.push_back(sampler); } // Channels for (auto &source: anim.channels) { vkglTF::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 vkglTF::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, vkglTF::descriptorSetLayoutImage, descriptorBindingFlags); } } } } void vkglTF::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; } void vkglTF::Model::drawNode(Node *node, VkCommandBuffer commandBuffer, uint32_t renderFlags, VkPipelineLayout pipelineLayout, uint32_t bindImageSet) { if (node->mesh) { for (Primitive* primitive : node->mesh->primitives) { bool skip = false; const vkglTF::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); } } } for (auto& child : node->children) { drawNode(child, commandBuffer, renderFlags, pipelineLayout, bindImageSet); } } void vkglTF::Model::draw(VkCommandBuffer commandBuffer, uint32_t renderFlags, VkPipelineLayout pipelineLayout, uint32_t bindImageSet) { 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(node, commandBuffer, renderFlags, pipelineLayout, bindImageSet); } } void vkglTF::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 vkglTF::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; } void vkglTF::Model::updateAnimation(uint32_t index, float time) { if (index > static_cast(animations.size()) - 1) { std::cout << "No animation with index " << index << std::endl; return; } Animation &animation = animations[index]; bool updated = false; for (auto& channel : animation.channels) { vkglTF::AnimationSampler &sampler = animation.samplers[channel.samplerIndex]; if (sampler.inputs.size() > sampler.outputsVec4.size()) { continue; } 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 vkglTF::AnimationChannel::PathType::TRANSLATION: { glm::vec4 trans = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], u); channel.node->translation = glm::vec3(trans); break; } case vkglTF::AnimationChannel::PathType::SCALE: { glm::vec4 trans = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], u); channel.node->scale = glm::vec3(trans); break; } case vkglTF::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; } } } } if (updated) { for (auto &node : nodes) { node->update(); } } } /* Helper functions */ vkglTF::Node* vkglTF::Model::findNode(Node *parent, uint32_t index) { Node* nodeFound = nullptr; if (parent->index == index) { return parent; } for (auto& child : parent->children) { nodeFound = findNode(child, index); if (nodeFound) { break; } } return nodeFound; } vkglTF::Node* vkglTF::Model::nodeFromIndex(uint32_t index) { Node* nodeFound = nullptr; for (auto &node : nodes) { nodeFound = findNode(node, index); if (nodeFound) { break; } } return nodeFound; } void vkglTF::Model::prepareNodeDescriptor(vkglTF::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); } }