diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a51ef90..c6be392 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,7 +30,7 @@ function(buildHomework HOMEWORK_NAME) # Add optional readme / tutorial file(GLOB README_FILES "${HOMEWORK_FOLDER}/*.md") if(WIN32) - add_executable(${HOMEWORK_NAME} WIN32 ${MAIN_CPP} ${SOURCE} ${MAIN_HEADER} ${SHADERS_GLSL} ${SHADERS_HLSL} ${README_FILES} "render/glTFModel.h") + add_executable(${HOMEWORK_NAME} WIN32 ${MAIN_CPP} ${SOURCE} ${MAIN_HEADER} ${SHADERS_GLSL} ${SHADERS_HLSL} ${README_FILES} "render/glTFModel.h" "render/glTFModel.cpp") target_link_libraries(${HOMEWORK_NAME} base ${Vulkan_LIBRARY} ${WINLIBS}) else(WIN32) add_executable(${HOMEWORK_NAME} ${MAIN_CPP} ${SOURCE} ${MAIN_HEADER} ${SHADERS_GLSL} ${SHADERS_HLSL} ${README_FILES}) diff --git a/src/render/glTFModel.cpp b/src/render/glTFModel.cpp new file mode 100644 index 0000000..9382e89 --- /dev/null +++ b/src/render/glTFModel.cpp @@ -0,0 +1,494 @@ +#pragma once + + +#include "glTFModel.h" + + +/* + glTF loading functions + + The following functions take a glTF input model loaded via tinyglTF and convert all required data into our own structure +*/ + +void VulkanglTFModel::loadImages(tinygltf::Model& input) +{ + // Images can be stored inside the glTF (which is the case for the sample model), so instead of directly + // loading them from disk, we fetch them from the glTF loader and upload the buffers + images.resize(input.images.size()); + for (size_t i = 0; i < input.images.size(); i++) { + tinygltf::Image& glTFImage = input.images[i]; + // Get the image data from the glTF loader + unsigned char* buffer = nullptr; + VkDeviceSize bufferSize = 0; + bool deleteBuffer = false; + // We convert RGB-only images to RGBA, as most devices don't support RGB-formats in Vulkan + if (glTFImage.component == 3) { + 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) { + memcpy(rgba, rgb, sizeof(unsigned char) * 3); + rgba += 4; + rgb += 3; + } + deleteBuffer = true; + } + else { + buffer = &glTFImage.image[0]; + bufferSize = glTFImage.image.size(); + } + // Load texture from image buffer + images[i].texture.fromBuffer(buffer, bufferSize, VK_FORMAT_R8G8B8A8_UNORM, glTFImage.width, glTFImage.height, vulkanDevice, copyQueue); + if (deleteBuffer) { + delete[] buffer; + } + } +} + +void VulkanglTFModel::loadTextures(tinygltf::Model& input) +{ + textures.resize(input.textures.size()); + for (size_t i = 0; i < input.textures.size(); i++) { + textures[i].imageIndex = input.textures[i].source; + } +} + +void VulkanglTFModel::loadAnimations(tinygltf::Model& input) +{ + animations.resize(input.animations.size()); + + for (size_t i = 0; i < input.animations.size(); ++i) + { + auto glTFAnimation = input.animations[i]; + animations[i].name = glTFAnimation.name; + + //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; + + // Read sampler keyframe 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]); + } + // Adjust animation's start and end times + for (auto input : animations[i].samplers[j].inputs) + { + if (input < animations[i].start) + { + animations[i].start = input; + }; + if (input > animations[i].end) + { + animations[i].end = input; + } + } + } + + // Read sampler keyframe output translate/rotate/scale 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)); + } + 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]); + } + break; + } + default: + { + std::cout << "unknown type" << std::endl; + break; + } + } + } + } + + 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); + } + } +} + +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()); + } + } +} + +void VulkanglTFModel::loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFModel::Node* parent, uint32_t nodeIndex, std::vector& indexBuffer, std::vector& vertexBuffer) +{ + VulkanglTFModel::Node* node = new VulkanglTFModel::Node{}; + node->matrix = glm::mat4(1.0f); + node->parent = parent; + node->index = nodeIndex; + + // Get the local node matrix + // It's either made up from translation, rotation, scale or a 4x4 matrix + if (inputNode.translation.size() == 3) { + node->matrix = glm::translate(node->matrix, glm::vec3(glm::make_vec3(inputNode.translation.data()))); + } + if (inputNode.rotation.size() == 4) { + glm::quat q = glm::make_quat(inputNode.rotation.data()); + node->matrix *= glm::mat4(q); + } + if (inputNode.scale.size() == 3) { + node->matrix = glm::scale(node->matrix, glm::vec3(glm::make_vec3(inputNode.scale.data()))); + } + if (inputNode.matrix.size() == 16) { + node->matrix = glm::make_mat4x4(inputNode.matrix.data()); + }; + + // Load node's children + if (inputNode.children.size() > 0) { + for (size_t i = 0; i < inputNode.children.size(); i++) { + loadNode(input.nodes[inputNode.children[i]], input, node, inputNode.children[i], indexBuffer, vertexBuffer); + } + } + + // If the node contains mesh data, we load vertices and indices from the buffers + // In glTF this is done via accessors and buffer views + if (inputNode.mesh > -1) { + const tinygltf::Mesh mesh = input.meshes[inputNode.mesh]; + // Iterate through all primitives of this node's mesh + for (size_t i = 0; i < mesh.primitives.size(); i++) { + const tinygltf::Primitive& glTFPrimitive = mesh.primitives[i]; + uint32_t firstIndex = static_cast(indexBuffer.size()); + uint32_t vertexStart = static_cast(vertexBuffer.size()); + uint32_t indexCount = 0; + // Vertices + { + const float* positionBuffer = nullptr; + const float* normalsBuffer = nullptr; + const float* texCoordsBuffer = nullptr; + const float* tangentsBuffer = nullptr; + size_t vertexCount = 0; + + // Get buffer data for vertex positions + if (glTFPrimitive.attributes.find("POSITION") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("POSITION")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + positionBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + vertexCount = accessor.count; + } + // Get buffer data for vertex normals + if (glTFPrimitive.attributes.find("NORMAL") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("NORMAL")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + normalsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + } + // Get buffer data for vertex texture coordinates + // glTF supports multiple sets, we only load the first one + if (glTFPrimitive.attributes.find("TEXCOORD_0") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TEXCOORD_0")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + texCoordsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + } + + if (glTFPrimitive.attributes.find("TANGENT") != glTFPrimitive.attributes.end()) + { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TANGENT")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + tangentsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + } + + // Append data to model's vertex buffer + for (size_t v = 0; v < vertexCount; v++) { + Vertex vert{}; + vert.pos = glm::vec4(glm::make_vec3(&positionBuffer[v * 3]), 1.0f); + vert.normal = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f))); + vert.uv = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f); + vert.tangent = tangentsBuffer ? glm::normalize(glm::make_vec3(&tangentsBuffer[v * 4])) : glm::vec3(0.0f); + vert.color = glm::vec3(1.0f, 1.0f, nodeIndex);//Temp set index in color attribute + vertexBuffer.push_back(vert); + } + } + // Indices + { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.indices]; + const tinygltf::BufferView& bufferView = input.bufferViews[accessor.bufferView]; + const tinygltf::Buffer& buffer = input.buffers[bufferView.buffer]; + + indexCount += static_cast(accessor.count); + + // glTF supports different component types of indices + switch (accessor.componentType) { + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { + const uint32_t* buf = reinterpret_cast(&buffer.data[accessor.byteOffset + bufferView.byteOffset]); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + break; + } + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: { + const uint16_t* buf = reinterpret_cast(&buffer.data[accessor.byteOffset + bufferView.byteOffset]); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + break; + } + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: { + const uint8_t* buf = reinterpret_cast(&buffer.data[accessor.byteOffset + bufferView.byteOffset]); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + break; + } + default: + std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl; + return; + } + } + Primitive primitive{}; + primitive.firstIndex = firstIndex; + primitive.indexCount = indexCount; + primitive.materialIndex = glTFPrimitive.material; + node->mesh.primitives.push_back(primitive); + } + } + + if (parent) { + parent->children.push_back(node); + } + else { + nodes.push_back(node); + } +} + +VulkanglTFModel::Node* VulkanglTFModel::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; +} + +VulkanglTFModel::Node* VulkanglTFModel::nodeFromIndex(uint32_t index) +{ + Node* nodeFound = nullptr; + for (auto& node : nodes) + { + nodeFound = findNode(node, index); + if (nodeFound) + { + break; + } + } + return nodeFound; +} + +void VulkanglTFModel::updateAnimation(float deltaTime, vks::Buffer& buffer) +{ + constexpr uint32_t activeAnimation = 0; + Animation& animation = animations[activeAnimation]; + animation.currentTime += deltaTime; + if (animation.currentTime > animation.end) + { + animation.currentTime -= animation.end; + } + + 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; + + 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; + } + } + } + } + std::vector nodeMatrics(nodeCount); + for (auto& node : nodes) + { + updateNodeMatrix(node, nodeMatrics); + } + buffer.copyTo(nodeMatrics.data(), nodeCount * sizeof(glm::mat4)); +} + +void VulkanglTFModel::updateNodeMatrix(Node* node, std::vector& nodeMatrics) +{ + nodeMatrics[node->index] = getNodeMatrix(node); + for (auto& child : node->children) + { + updateNodeMatrix(child, nodeMatrics); + } +} + +glm::mat4 VulkanglTFModel::getNodeMatrix(Node* node) +{ + glm::mat4 nodeMatrix = node->getLocalMatrix(); + Node* currentParent = node->parent; + while (currentParent) + { + nodeMatrix = currentParent->getLocalMatrix() * nodeMatrix; + currentParent = currentParent->parent; + } + return nodeMatrix; +} + +/* + glTF rendering functions +*/ + +// Draw a single node including child nodes (if present) +void VulkanglTFModel::drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node* node, bool bPushConstants) +{ + 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) + { + VulkanglTFModel::Texture texture = textures[materials[primitive.materialIndex].baseColorTextureIndex]; + auto normalMap = textures[materials[primitive.materialIndex].normalMapTextureIndex]; + auto roughMetalMap = textures[materials[primitive.materialIndex].matalicRoughTextureIndex]; + + if (materials[primitive.materialIndex].emissiveTextureIndex >= 0) + { + 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, 1, 1, &images[texture.imageIndex].descriptorSet, 0, nullptr); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 2, 1, &images[normalMap.imageIndex].descriptorSet, 0, nullptr); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 3, 1, &images[roughMetalMap.imageIndex].descriptorSet, 0, nullptr); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 5, 1, &materials[primitive.materialIndex].materialData.descriptorSet, 0, nullptr); + } + vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0); + } + } + } + for (auto& child : node->children) { + drawNode(commandBuffer, pipelineLayout, child, bPushConstants); + } +} + +// Draw the glTF scene starting at the top-level-nodes +void VulkanglTFModel::draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, bool flag = true) +{ + // 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 + for (auto& node : nodes) { + drawNode(commandBuffer, pipelineLayout, node, flag); + } +} \ No newline at end of file diff --git a/src/render/glTFModel.h b/src/render/glTFModel.h index bfc5ca7..2db486e 100644 --- a/src/render/glTFModel.h +++ b/src/render/glTFModel.h @@ -1,4 +1,5 @@ #pragma once + #include #include #include @@ -11,13 +12,13 @@ #include #include -#define TINYGLTF_IMPLEMENTATION -#define STB_IMAGE_IMPLEMENTATION -#define TINYGLTF_NO_STB_IMAGE_WRITE + #ifdef VK_USE_PLATFORM_ANDROID_KHR #define TINYGLTF_ANDROID_LOAD_FROM_ASSETS #endif + + #include "tiny_gltf.h" #include "vulkanexamplebase.h" diff --git a/src/render/render.cpp b/src/render/render.cpp index b6d3dbc..89d0226 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -1,3 +1,5 @@ +#pragma once + /* * Vulkan Example - glTF scene loading and rendering * @@ -15,503 +17,27 @@ * * If you are looking for a complete glTF implementation, check out https://github.com/SaschaWillems/Vulkan-glTF-PBR/ */ + +#ifndef TINYGLTF_IMPLEMENTATION +#define TINYGLTF_IMPLEMENTATION +#endif +#ifndef STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#endif + +#ifndef TINYGLTF_NO_STB_IMAGE_WRITE +#define TINYGLTF_NO_STB_IMAGE_WRITE +#endif + + #include "render.h" -#include "glTFModel.h" - /* - glTF loading functions - - The following functions take a glTF input model loaded via tinyglTF and convert all required data into our own structure - */ - - void VulkanglTFModel::loadImages(tinygltf::Model& input) - { - // Images can be stored inside the glTF (which is the case for the sample model), so instead of directly - // loading them from disk, we fetch them from the glTF loader and upload the buffers - images.resize(input.images.size()); - for (size_t i = 0; i < input.images.size(); i++) { - tinygltf::Image& glTFImage = input.images[i]; - // Get the image data from the glTF loader - unsigned char* buffer = nullptr; - VkDeviceSize bufferSize = 0; - bool deleteBuffer = false; - // We convert RGB-only images to RGBA, as most devices don't support RGB-formats in Vulkan - if (glTFImage.component == 3) { - 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) { - memcpy(rgba, rgb, sizeof(unsigned char) * 3); - rgba += 4; - rgb += 3; - } - deleteBuffer = true; - } - else { - buffer = &glTFImage.image[0]; - bufferSize = glTFImage.image.size(); - } - // Load texture from image buffer - images[i].texture.fromBuffer(buffer, bufferSize, VK_FORMAT_R8G8B8A8_UNORM, glTFImage.width, glTFImage.height, vulkanDevice, copyQueue); - if (deleteBuffer) { - delete[] buffer; - } - } - } - - void VulkanglTFModel::loadTextures(tinygltf::Model& input) - { - textures.resize(input.textures.size()); - for (size_t i = 0; i < input.textures.size(); i++) { - textures[i].imageIndex = input.textures[i].source; - } - } - - void VulkanglTFModel::loadAnimations(tinygltf::Model& input) - { - animations.resize(input.animations.size()); - - for (size_t i = 0; i < input.animations.size(); ++i) - { - auto glTFAnimation = input.animations[i]; - animations[i].name = glTFAnimation.name; - - //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; - - // Read sampler keyframe 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]); - } - // Adjust animation's start and end times - for (auto input : animations[i].samplers[j].inputs) - { - if (input < animations[i].start) - { - animations[i].start = input; - }; - if (input > animations[i].end) - { - animations[i].end = input; - } - } - } - - // Read sampler keyframe output translate/rotate/scale 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)); - } - 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]); - } - break; - } - default: - { - std::cout << "unknown type" << std::endl; - break; - } - } - } - } - - 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); - } - } - } - - 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()); - } - } - } - - void VulkanglTFModel::loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFModel::Node* parent, uint32_t nodeIndex,std::vector& indexBuffer, std::vector& vertexBuffer) - { - VulkanglTFModel::Node* node = new VulkanglTFModel::Node{}; - node->matrix = glm::mat4(1.0f); - node->parent = parent; - node->index = nodeIndex; - - // Get the local node matrix - // It's either made up from translation, rotation, scale or a 4x4 matrix - if (inputNode.translation.size() == 3) { - node->matrix = glm::translate(node->matrix, glm::vec3(glm::make_vec3(inputNode.translation.data()))); - } - if (inputNode.rotation.size() == 4) { - glm::quat q = glm::make_quat(inputNode.rotation.data()); - node->matrix *= glm::mat4(q); - } - if (inputNode.scale.size() == 3) { - node->matrix = glm::scale(node->matrix, glm::vec3(glm::make_vec3(inputNode.scale.data()))); - } - if (inputNode.matrix.size() == 16) { - node->matrix = glm::make_mat4x4(inputNode.matrix.data()); - }; - - // Load node's children - if (inputNode.children.size() > 0) { - for (size_t i = 0; i < inputNode.children.size(); i++) { - loadNode(input.nodes[inputNode.children[i]], input , node, inputNode.children[i],indexBuffer, vertexBuffer); - } - } - - // If the node contains mesh data, we load vertices and indices from the buffers - // In glTF this is done via accessors and buffer views - if (inputNode.mesh > -1) { - const tinygltf::Mesh mesh = input.meshes[inputNode.mesh]; - // Iterate through all primitives of this node's mesh - for (size_t i = 0; i < mesh.primitives.size(); i++) { - const tinygltf::Primitive& glTFPrimitive = mesh.primitives[i]; - uint32_t firstIndex = static_cast(indexBuffer.size()); - uint32_t vertexStart = static_cast(vertexBuffer.size()); - uint32_t indexCount = 0; - // Vertices - { - const float* positionBuffer = nullptr; - const float* normalsBuffer = nullptr; - const float* texCoordsBuffer = nullptr; - const float* tangentsBuffer = nullptr; - size_t vertexCount = 0; - - // Get buffer data for vertex positions - if (glTFPrimitive.attributes.find("POSITION") != glTFPrimitive.attributes.end()) { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("POSITION")->second]; - const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; - positionBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - vertexCount = accessor.count; - } - // Get buffer data for vertex normals - if (glTFPrimitive.attributes.find("NORMAL") != glTFPrimitive.attributes.end()) { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("NORMAL")->second]; - const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; - normalsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - } - // Get buffer data for vertex texture coordinates - // glTF supports multiple sets, we only load the first one - if (glTFPrimitive.attributes.find("TEXCOORD_0") != glTFPrimitive.attributes.end()) { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TEXCOORD_0")->second]; - const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; - texCoordsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - } - - if (glTFPrimitive.attributes.find("TANGENT") != glTFPrimitive.attributes.end()) - { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TANGENT")->second]; - const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; - tangentsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - } - - // Append data to model's vertex buffer - for (size_t v = 0; v < vertexCount; v++) { - Vertex vert{}; - vert.pos = glm::vec4(glm::make_vec3(&positionBuffer[v * 3]), 1.0f); - vert.normal = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f))); - vert.uv = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f); - vert.tangent = tangentsBuffer ? glm::normalize(glm::make_vec3(&tangentsBuffer[v * 4])) : glm::vec3(0.0f); - vert.color = glm::vec3(1.0f, 1.0f, nodeIndex);//Temp set index in color attribute - vertexBuffer.push_back(vert); - } - } - // Indices - { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.indices]; - const tinygltf::BufferView& bufferView = input.bufferViews[accessor.bufferView]; - const tinygltf::Buffer& buffer = input.buffers[bufferView.buffer]; - - indexCount += static_cast(accessor.count); - - // glTF supports different component types of indices - switch (accessor.componentType) { - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { - const uint32_t* buf = reinterpret_cast(&buffer.data[accessor.byteOffset + bufferView.byteOffset]); - for (size_t index = 0; index < accessor.count; index++) { - indexBuffer.push_back(buf[index] + vertexStart); - } - break; - } - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: { - const uint16_t* buf = reinterpret_cast(&buffer.data[accessor.byteOffset + bufferView.byteOffset]); - for (size_t index = 0; index < accessor.count; index++) { - indexBuffer.push_back(buf[index] + vertexStart); - } - break; - } - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: { - const uint8_t* buf = reinterpret_cast(&buffer.data[accessor.byteOffset + bufferView.byteOffset]); - for (size_t index = 0; index < accessor.count; index++) { - indexBuffer.push_back(buf[index] + vertexStart); - } - break; - } - default: - std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl; - return; - } - } - Primitive primitive{}; - primitive.firstIndex = firstIndex; - primitive.indexCount = indexCount; - primitive.materialIndex = glTFPrimitive.material; - node->mesh.primitives.push_back(primitive); - } - } - - if (parent) { - parent->children.push_back(node); - } - else { - nodes.push_back(node); - } - } - - VulkanglTFModel::Node* VulkanglTFModel::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; - } - - VulkanglTFModel::Node* VulkanglTFModel::nodeFromIndex(uint32_t index) - { - Node* nodeFound = nullptr; - for (auto& node : nodes) - { - nodeFound = findNode(node, index); - if (nodeFound) - { - break; - } - } - return nodeFound; - } - - void VulkanglTFModel::updateAnimation(float deltaTime, vks::Buffer& buffer) - { - constexpr uint32_t activeAnimation = 0; - Animation& animation = animations[activeAnimation]; - animation.currentTime += deltaTime; - if (animation.currentTime > animation.end) - { - animation.currentTime -= animation.end; - } - - 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; - - 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; - } - } - } - } - std::vector nodeMatrics(nodeCount); - for (auto& node : nodes) - { - updateNodeMatrix(node, nodeMatrics); - } - buffer.copyTo(nodeMatrics.data(), nodeCount * sizeof(glm::mat4)); - } - - void VulkanglTFModel::updateNodeMatrix(Node* node, std::vector& nodeMatrics) - { - nodeMatrics[node->index] = getNodeMatrix(node); - for (auto& child : node->children) - { - updateNodeMatrix(child, nodeMatrics); - } - } - - glm::mat4 VulkanglTFModel::getNodeMatrix(Node* node) - { - glm::mat4 nodeMatrix = node->getLocalMatrix(); - Node* currentParent = node->parent; - while (currentParent) - { - nodeMatrix = currentParent->getLocalMatrix() * nodeMatrix; - currentParent = currentParent->parent; - } - return nodeMatrix; - } - - /* - glTF rendering functions - */ - - // Draw a single node including child nodes (if present) - void VulkanglTFModel::drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node* node, bool bPushConstants) - { - 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) - { - VulkanglTFModel::Texture texture = textures[materials[primitive.materialIndex].baseColorTextureIndex]; - auto normalMap = textures[materials[primitive.materialIndex].normalMapTextureIndex]; - auto roughMetalMap = textures[materials[primitive.materialIndex].matalicRoughTextureIndex]; - - if (materials[primitive.materialIndex].emissiveTextureIndex >= 0) - { - 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, 1, 1, &images[texture.imageIndex].descriptorSet, 0, nullptr); - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 2, 1, &images[normalMap.imageIndex].descriptorSet, 0, nullptr); - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 3, 1, &images[roughMetalMap.imageIndex].descriptorSet, 0, nullptr); - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 5, 1, &materials[primitive.materialIndex].materialData.descriptorSet, 0, nullptr); - } - vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0); - } - } - } - for (auto& child : node->children) { - drawNode(commandBuffer, pipelineLayout, child, bPushConstants); - } - } - - // Draw the glTF scene starting at the top-level-nodes - void VulkanglTFModel::draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, bool flag = true) - { - // 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 - for (auto& node : nodes) { - drawNode(commandBuffer, pipelineLayout, node, flag); - } - } VulkanExample::VulkanExample(): VulkanExampleBase(ENABLE_VALIDATION) { - title = "homework1"; + title = "render"; camera.type = Camera::CameraType::lookat; camera.flipY = true; camera.setPosition(glm::vec3(0.0f, -0.1f, -1.0f)); @@ -671,7 +197,7 @@ vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.pbrLayout, 0, 1, &descriptorSet, 0, nullptr); vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.pbrLayout, 6, 1, &skinDescriptorSet, 0, nullptr); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.solid); - glTFModel.draw(drawCmdBuffers[i], pipelineLayouts.pbrLayout); + glTFModel.draw(drawCmdBuffers[i], pipelineLayouts.pbrLayout,false); vkCmdEndRenderPass(drawCmdBuffers[i]); { diff --git a/src/render/render.h b/src/render/render.h index 1465592..fb7c267 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -1,3 +1,4 @@ +#pragma once /* #include #include @@ -22,6 +23,7 @@ #define TINYGLTF_NO_STB_IMAGE_WRITE #include "tiny_gltf.h" */ + #include "vulkanexamplebase.h" #include "glTFModel.h"