Fixed GLTF json error, and added textures
@ -17,7 +17,18 @@ find_package (glm REQUIRED)
|
||||
find_package (Vulkan 1.4.335 REQUIRED) # Require Vulkan SDK version 1.4.335 or higher
|
||||
find_package (tinyobjloader REQUIRED)
|
||||
find_package (tinygltf REQUIRED)
|
||||
find_package (KTX REQUIRED)
|
||||
find_package (nlohmann_json REQUIRED)
|
||||
find_library(KTX_LIBRARY NAMES ktx REQUIRED)
|
||||
find_path(KTX_INCLUDE_DIR NAMES ktx.h REQUIRED)
|
||||
|
||||
message(STATUS "KTX library found at: ${KTX_LIBRARY}")
|
||||
message(STATUS "KTX include dir: ${KTX_INCLUDE_DIR}")
|
||||
|
||||
add_library(KTX::ktx SHARED IMPORTED GLOBAL)
|
||||
set_target_properties(KTX::ktx PROPERTIES
|
||||
IMPORTED_LOCATION "${KTX_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${KTX_INCLUDE_DIR}"
|
||||
)
|
||||
|
||||
# set up Vulkan C++ module only if enabled
|
||||
if(ENABLE_CPP20_MODULE)
|
||||
@ -177,20 +188,38 @@ function (add_chapter CHAPTER_NAME)
|
||||
target_link_libraries (${CHAPTER_NAME} ${CHAPTER_LIBS})
|
||||
endif ()
|
||||
if (DEFINED CHAPTER_MODELS)
|
||||
list(TRANSFORM CHAPTER_MODELS PREPEND "${CMAKE_SOURCE_DIR}/models/")
|
||||
file (COPY ${CHAPTER_MODELS} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/models)
|
||||
set(RESOLVED_MODELS "")
|
||||
foreach(MODEL_PATTERN ${CHAPTER_MODELS})
|
||||
file(GLOB MATCHED_MODELS CONFIGURE_DEPENDS
|
||||
"${CMAKE_SOURCE_DIR}/models/${MODEL_PATTERN}"
|
||||
)
|
||||
list(APPEND RESOLVED_MODELS ${MATCHED_MODELS})
|
||||
endforeach()
|
||||
|
||||
if (RESOLVED_MODELS)
|
||||
file(COPY ${RESOLVED_MODELS} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/models)
|
||||
endif()
|
||||
endif ()
|
||||
if (DEFINED CHAPTER_TEXTURES)
|
||||
list(TRANSFORM CHAPTER_TEXTURES PREPEND "${CMAKE_SOURCE_DIR}/textures/")
|
||||
file (COPY ${CHAPTER_TEXTURES} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/textures)
|
||||
set(RESOLVED_TEXTURES "")
|
||||
foreach(TEX_PATTERN ${CHAPTER_TEXTURES})
|
||||
file(GLOB MATCHED_TEXTURES CONFIGURE_DEPENDS
|
||||
"${CMAKE_SOURCE_DIR}/textures/${TEX_PATTERN}"
|
||||
)
|
||||
list(APPEND RESOLVED_TEXTURES ${MATCHED_TEXTURES})
|
||||
endforeach()
|
||||
|
||||
if (RESOLVED_TEXTURES)
|
||||
file(COPY ${RESOLVED_TEXTURES} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/textures)
|
||||
endif()
|
||||
endif ()
|
||||
endfunction ()
|
||||
|
||||
add_chapter (main
|
||||
SHADER shaders/main
|
||||
TEXTURES texture.jpg
|
||||
TEXTURES viking_room.png
|
||||
MODELS viking_room.obj
|
||||
TEXTURES *
|
||||
MODELS *
|
||||
LIBS nlohmann_json::nlohmann_json KTX::ktx
|
||||
)
|
||||
|
||||
#add_chapter (38_ray_tracing
|
||||
|
||||
BIN
models/scene.bin
Normal file
4479
models/scene.gltf
Normal file
191
src/main.cpp
@ -11,14 +11,12 @@
|
||||
#include <ios>
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <fstream> // to load shaders
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <sys/types.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <cstdlib>
|
||||
#include <unordered_map>
|
||||
// Vulkan Includes
|
||||
#include "vulkan/vulkan.hpp"
|
||||
#include <vulkan/vulkan_core.h>
|
||||
@ -40,17 +38,25 @@ import vulkan_hpp;
|
||||
#include <chrono>
|
||||
// Images
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include <stb_image_write.h>
|
||||
#include <stb_image.h>
|
||||
// OBJ loader
|
||||
#define TINYOBJLOADER_IMPLEMENTATION
|
||||
#include <tiny_obj_loader.h>
|
||||
#include <ktx.h>
|
||||
// Model loader
|
||||
#include <nlohmann/json.hpp>
|
||||
#define TINYGLTF_IMPLEMENTATION
|
||||
#define TINYGLTF_NO_INCLUDE_JSON
|
||||
#define TINYGLTF_NO_INCLUDE_STB_IMAGE
|
||||
#define TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE
|
||||
#define TINYGLTF_USE_CPP14
|
||||
#include <tiny_gltf.h>
|
||||
|
||||
// Consts
|
||||
constexpr uint32_t WIDTH = 800;
|
||||
constexpr uint32_t HEIGHT = 600;
|
||||
constexpr int MAX_FRAMES_IN_FLIGHT = 2;
|
||||
const std::string MODEL_PATH = "models/viking_room.obj";
|
||||
const std::string TEXTURE_PATH = "textures/viking_room.png";
|
||||
const std::string MODEL_PATH = "models/scene.gltf";
|
||||
const std::string TEXTURE_PATH = "textures/texture.ktx2";
|
||||
|
||||
const std::vector<char const*> validationLayers = {
|
||||
"VK_LAYER_KHRONOS_validation"
|
||||
@ -711,15 +717,30 @@ class HelloTriangleApplication {
|
||||
#ifndef NDEBUG
|
||||
std::cout << "Creating a texture image...\n";
|
||||
#endif
|
||||
int texWidth, texHeight, texChannels;
|
||||
stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
|
||||
vk::DeviceSize imageSize = texWidth * texHeight * 4;
|
||||
ktxTexture* kTexture;
|
||||
KTX_error_code result = ktxTexture_CreateFromNamedFile(
|
||||
TEXTURE_PATH.c_str(),
|
||||
KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
|
||||
&kTexture
|
||||
);
|
||||
|
||||
mipLevels = static_cast<uint32_t> (std::floor(std::log2(std::max(texWidth, texHeight)))) + 1;
|
||||
|
||||
if (!pixels) {
|
||||
if (result != KTX_SUCCESS) {
|
||||
throw std::runtime_error("Failed to load texture image!\nAborting...");
|
||||
}
|
||||
|
||||
mipLevels = kTexture->numLevels;
|
||||
//mipLevels = static_cast<uint32_t> (std::floor(std::log2(std::max(texWidth, texHeight)))) + 1;
|
||||
uint32_t texWidth = kTexture->baseWidth;
|
||||
uint32_t texHeight = kTexture->baseHeight;
|
||||
ktx_size_t imageSize = ktxTexture_GetImageSize(kTexture, 0);
|
||||
ktx_uint8_t* ktxTextureData = ktxTexture_GetData(kTexture);
|
||||
|
||||
if (mipLevels == 1) {
|
||||
// Handles the case the texture has no mipMaps
|
||||
mipLevels = static_cast<uint32_t> (std::floor(std::log2(std::max(texWidth, texHeight)))) + 1;
|
||||
}
|
||||
|
||||
|
||||
// Temporary buffers
|
||||
vk::raii::Buffer stagingBuffer( {} );
|
||||
vk::raii::DeviceMemory stagingBufferMemory( {} );
|
||||
@ -732,15 +753,16 @@ class HelloTriangleApplication {
|
||||
);
|
||||
|
||||
void* data = stagingBufferMemory.mapMemory(0, imageSize);
|
||||
memcpy(data, pixels, imageSize);
|
||||
memcpy(data, ktxTextureData, imageSize);
|
||||
stagingBufferMemory.unmapMemory();
|
||||
stbi_image_free(pixels);
|
||||
|
||||
vk::Format textureFormat = vk::Format::eR8G8B8A8Srgb; // Default format, should be determined from KTX metadata
|
||||
|
||||
createImage(
|
||||
texWidth,
|
||||
texHeight,
|
||||
mipLevels,
|
||||
vk::Format::eR8G8B8A8Srgb,
|
||||
textureFormat,
|
||||
vk::ImageTiling::eOptimal,
|
||||
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
|
||||
vk::MemoryPropertyFlagBits::eDeviceLocal,
|
||||
@ -760,12 +782,6 @@ class HelloTriangleApplication {
|
||||
static_cast<uint32_t>(texWidth),
|
||||
static_cast<uint32_t>(texHeight)
|
||||
);
|
||||
transitionImageLayout(
|
||||
textureImage,
|
||||
vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
mipLevels
|
||||
);
|
||||
|
||||
generateMipmaps(
|
||||
textureImage,
|
||||
@ -775,6 +791,8 @@ class HelloTriangleApplication {
|
||||
mipLevels
|
||||
);
|
||||
|
||||
ktxTexture_Destroy(kTexture);
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::cout << "Texture image created !\n";
|
||||
#endif
|
||||
@ -871,40 +889,103 @@ class HelloTriangleApplication {
|
||||
std::cout << "Loading the 3D model...\n";
|
||||
#endif
|
||||
|
||||
tinyobj::attrib_t attrib;
|
||||
std::vector<tinyobj::shape_t> shapes;
|
||||
std::vector<tinyobj::material_t> materials;
|
||||
tinygltf::Model model;
|
||||
tinygltf::TinyGLTF loader;
|
||||
std::string warn, err;
|
||||
|
||||
if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, MODEL_PATH.c_str())) {
|
||||
throw std::runtime_error(warn + err);
|
||||
bool ret = (MODEL_PATH.ends_with(".glb"))
|
||||
? loader.LoadBinaryFromFile(&model, &err, &warn, MODEL_PATH)
|
||||
: loader.LoadASCIIFromFile(&model, &err, &warn, MODEL_PATH);
|
||||
|
||||
if (!warn.empty()) {
|
||||
#ifndef NDEBUG
|
||||
std::cout << "glTF warning: " << warn << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unordered_map<Vertex, uint32_t> uniqueVertices{};
|
||||
if (!err.empty()) {
|
||||
std::cout << "Error while loading the model: " << err << std::endl;
|
||||
}
|
||||
|
||||
for (const auto& shape : shapes) {
|
||||
for (const auto& index : shape.mesh.indices) {
|
||||
if (!ret) {
|
||||
throw std::runtime_error("Failed to load the glTF model");
|
||||
}
|
||||
|
||||
for ( const auto &mesh : model.meshes ) {
|
||||
for ( const auto &primitive : mesh.primitives ) {
|
||||
// Get indices
|
||||
const tinygltf::Accessor &indexAccessor = model.accessors[primitive.indices];
|
||||
const tinygltf::BufferView &indexBufferView = model.bufferViews[indexAccessor.bufferView];
|
||||
const tinygltf::Buffer &indexBuffer = model.buffers[indexBufferView.buffer];
|
||||
|
||||
// Get vertex positions
|
||||
const tinygltf::Accessor &posAccessor = model.accessors[primitive.attributes.at("POSITION")];
|
||||
const tinygltf::BufferView &posBufferView = model.bufferViews[posAccessor.bufferView];
|
||||
const tinygltf::Buffer &posBuffer = model.buffers[posBufferView.buffer];
|
||||
|
||||
// Get texture coordinates if available
|
||||
bool hasTexCoords = primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end();
|
||||
const tinygltf::Accessor *texCoordAccessor = nullptr;
|
||||
const tinygltf::BufferView *texCoordBufferView = nullptr;
|
||||
const tinygltf::Buffer *texCoordBuffer = nullptr;
|
||||
|
||||
if (hasTexCoords) {
|
||||
texCoordAccessor = &model.accessors[primitive.attributes.at("TEXCOORD_0")];
|
||||
texCoordBufferView = &model.bufferViews[texCoordAccessor->bufferView];
|
||||
texCoordBuffer = &model.buffers[texCoordBufferView->buffer];
|
||||
}
|
||||
|
||||
uint32_t baseVertex = static_cast<uint32_t>(vertices.size());
|
||||
|
||||
for (size_t i = 0; i < posAccessor.count; i++) {
|
||||
Vertex vertex{};
|
||||
|
||||
vertex.pos = {
|
||||
attrib.vertices[3 * index.vertex_index + 0],
|
||||
attrib.vertices[3 * index.vertex_index + 1],
|
||||
attrib.vertices[3 * index.vertex_index + 2]
|
||||
};
|
||||
const float *pos = reinterpret_cast<const float *>(&posBuffer.data[posBufferView.byteOffset + posAccessor.byteOffset + i * 12]);
|
||||
// Flip the Y coordinate for glTF <-> Vulkan compatibility
|
||||
vertex.pos = {pos[0], -pos[1], pos[2]};
|
||||
|
||||
vertex.texCoord = {
|
||||
attrib.texcoords[2 * index.texcoord_index + 0],
|
||||
1.0f - attrib.texcoords[2 * index.texcoord_index + 1]
|
||||
};
|
||||
if (hasTexCoords) {
|
||||
const float *texCoord = reinterpret_cast<const float *>(&texCoordBuffer->data[texCoordBufferView->byteOffset + texCoordAccessor->byteOffset + i * 8]);
|
||||
vertex.texCoord = {texCoord[0], texCoord[1]};
|
||||
} else {
|
||||
vertex.texCoord = {0.0f, 0.0f};
|
||||
}
|
||||
|
||||
vertex.color = {1.0f, 1.0f, 1.0f};
|
||||
|
||||
if (uniqueVertices.count(vertex) == 0) {
|
||||
uniqueVertices[vertex] = static_cast<uint32_t>(vertices.size());
|
||||
vertices.push_back(vertex);
|
||||
}
|
||||
|
||||
indices.push_back(uniqueVertices[vertex]);
|
||||
const unsigned char *indexData = &indexBuffer.data[indexBufferView.byteOffset + indexAccessor.byteOffset];
|
||||
size_t indexCount = indexAccessor.count;
|
||||
size_t indexStride = 0;
|
||||
|
||||
// Determine index stride based on component type
|
||||
if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) {
|
||||
indexStride = sizeof(uint16_t);
|
||||
} else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) {
|
||||
indexStride = sizeof(uint32_t);
|
||||
} else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) {
|
||||
indexStride = sizeof(uint8_t);
|
||||
} else {
|
||||
throw std::runtime_error("Unsupported index component type");
|
||||
}
|
||||
|
||||
indices.reserve(indices.size() + indexCount);
|
||||
|
||||
for (size_t i = 0; i < indexCount; i++) {
|
||||
uint32_t index = 0;
|
||||
|
||||
if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) {
|
||||
index = *reinterpret_cast<const uint16_t *>(indexData + i * indexStride);
|
||||
} else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) {
|
||||
index = *reinterpret_cast<const uint32_t *>(indexData + i * indexStride);
|
||||
} else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) {
|
||||
index = *reinterpret_cast<const uint8_t *>(indexData + i * indexStride);
|
||||
}
|
||||
|
||||
indices.push_back(baseVertex + index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1232,7 +1313,7 @@ class HelloTriangleApplication {
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image,
|
||||
.subresourceRange = {
|
||||
.subresourceRange = vk::ImageSubresourceRange {
|
||||
.aspectMask = image_aspect_flags,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
@ -1268,12 +1349,12 @@ class HelloTriangleApplication {
|
||||
.oldLayout = oldLayout,
|
||||
.newLayout = newLayout,
|
||||
.image = image,
|
||||
.subresourceRange = {
|
||||
vk::ImageAspectFlagBits::eColor,
|
||||
0,
|
||||
mipLevels,
|
||||
0,
|
||||
1
|
||||
.subresourceRange = vk::ImageSubresourceRange{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = mipLevels,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
}
|
||||
};
|
||||
|
||||
@ -1645,12 +1726,12 @@ class HelloTriangleApplication {
|
||||
.image = image,
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = format,
|
||||
.subresourceRange = {
|
||||
aspectFlags,
|
||||
0,
|
||||
mipLevels,
|
||||
0,
|
||||
1
|
||||
.subresourceRange = vk::ImageSubresourceRange{
|
||||
.aspectMask = aspectFlags,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = mipLevels,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
BIN
textures/armor_mat_baseColor.jpeg
Normal file
|
After Width: | Height: | Size: 5.2 MiB |
BIN
textures/armor_mat_metallicRoughness.png
Normal file
|
After Width: | Height: | Size: 14 MiB |
BIN
textures/armor_mat_normal.png
Normal file
|
After Width: | Height: | Size: 8.7 MiB |
BIN
textures/body_mat_baseColor.jpeg
Normal file
|
After Width: | Height: | Size: 5.0 MiB |
BIN
textures/body_mat_metallicRoughness.png
Normal file
|
After Width: | Height: | Size: 13 MiB |
BIN
textures/body_mat_normal.png
Normal file
|
After Width: | Height: | Size: 11 MiB |
BIN
textures/details_mat_baseColor.jpeg
Normal file
|
After Width: | Height: | Size: 4.5 MiB |
BIN
textures/details_mat_metallicRoughness.png
Normal file
|
After Width: | Height: | Size: 10 MiB |
BIN
textures/details_mat_normal.png
Normal file
|
After Width: | Height: | Size: 9.9 MiB |
BIN
textures/legs_mat_baseColor.jpeg
Normal file
|
After Width: | Height: | Size: 4.4 MiB |
BIN
textures/legs_mat_metallicRoughness.png
Normal file
|
After Width: | Height: | Size: 14 MiB |
BIN
textures/legs_mat_normal.png
Normal file
|
After Width: | Height: | Size: 11 MiB |
BIN
textures/texture.ktx2
Normal file
BIN
textures/top_mat_baseColor.jpeg
Normal file
|
After Width: | Height: | Size: 5.9 MiB |
BIN
textures/top_mat_emissive.jpeg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
textures/top_mat_metallicRoughness.png
Normal file
|
After Width: | Height: | Size: 17 MiB |
BIN
textures/top_mat_normal.png
Normal file
|
After Width: | Height: | Size: 12 MiB |
BIN
textures/weapon_mat_baseColor.png
Normal file
|
After Width: | Height: | Size: 5.0 MiB |
BIN
textures/weapon_mat_emissive.jpeg
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
textures/weapon_mat_metallicRoughness.png
Normal file
|
After Width: | Height: | Size: 4.1 MiB |
BIN
textures/weapon_mat_normal.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |