Fixed GLTF json error, and added textures

This commit is contained in:
Alexandre 2026-04-18 01:46:23 +02:00
parent ddb6f97646
commit 3732a493ce
25 changed files with 4655 additions and 66 deletions

View File

@ -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

Binary file not shown.

4479
models/scene.gltf Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

BIN
textures/texture.ktx2 Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 MiB

BIN
textures/top_mat_normal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB