Skip to content

Commit a58b884

Browse files
committed
committing requested changes in stages.
1 parent 5e38540 commit a58b884

File tree

8 files changed

+229
-103
lines changed

8 files changed

+229
-103
lines changed
Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,33 @@
1-
::pp: {plus}{plus}
1+
:pp: {plus}{plus}
22

33
= Loading Models: Introduction
4-
:doctype: book
5-
:sectnums:
6-
:sectnumlevels: 4
7-
:toc: left
8-
:icons: font
9-
:source-highlighter: highlightjs
10-
:source-language: c++
114

125
== Introduction
136

147
Welcome to the "Loading Models" chapter of our "Building a Simple Engine" series! After exploring engine architecture and camera systems in the previous chapters, we're now ready to focus on handling 3D assets within our engine framework.
158

169
In this chapter, we'll set up a robust model loading system that can handle modern 3D assets. Building upon the engine architecture we've established and the camera system we've implemented, we'll now add the ability to load and render complex 3D models. In the link:../../15_GLTF_KTX2_Migration.html[chapter on glTF and KTX2] from the main tutorial, we learned about migrating from OBJ to glTF format and the basics of loading glTF models. Now, we'll integrate that knowledge into our engine structure to create a more complete implementation.
1710

18-
In this chapter, we'll focus on:
11+
This chapter will transform your understanding of 3D asset handling from simple model loading to sophisticated engine-level systems. We'll begin by building a scene graph, which provides the hierarchical organization that complex 3D scenes require. This foundation enables you to group objects logically, apply transformations at different levels, and efficiently manage scene complexity.
1912

20-
* Building a scene graph to organize 3D objects hierarchically
21-
* Implementing animation support for glTF models
22-
* Creating a PBR material system
23-
* Rendering multiple objects with different transformations
24-
* Structuring our code in a more engine-like way
13+
Animation support forms a crucial part of modern 3D applications. We'll implement a system that can handle glTF's skeletal animations, giving life to your 3D models through smooth character movement, object animations, and complex multi-part systems.
2514

26-
This approach will serve as the foundation for our engine and allow us to create more complex scenes with animated models.
15+
The PBR material system we'll create bridges the gap between the lighting concepts from previous chapters and real-world asset integration. You'll see how to map glTF material properties to your shaders seamlessly, creating a workflow that artists can understand and use effectively.
16+
17+
Rendering multiple objects with different transformations presents both technical and organizational challenges. We'll solve these through careful engine architecture that can batch similar objects efficiently while maintaining the flexibility to handle unique materials and transformations per object.
18+
19+
Throughout this implementation, we'll structure our code with engine-level thinking rather than tutorial-style solutions. This approach will serve you well as your projects grow in complexity and scope, providing a solid foundation for creating complex scenes with animated models.
2720

2821
== Prerequisites
2922

30-
Before starting this chapter, you should have completed the main Vulkan tutorial up to at least Chapter 16 (Multiple Objects). You should also be familiar with:
23+
This chapter builds on the foundation established in the main Vulkan tutorial, particularly Chapter 16 (Multiple Objects), as we'll extend those concepts to handle more complex scene organization and asset management. The multiple objects chapter introduced the basic concepts of rendering different geometry, which we'll now scale up to handle complete 3D models with materials and animations.
24+
25+
You'll need solid familiarity with core Vulkan concepts that form the backbone of our model loading system. link:../../03_Drawing_a_triangle/03_Drawing/01_Command_buffers.adoc[Command buffers] become more complex when handling multiple models with different materials, as we'll need to manage descriptor sets and push constants efficiently. Understanding link:../../03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.adoc[graphics pipelines] is crucial since different materials might require different pipeline configurations.
26+
27+
Experience with link:../../04_Vertex_buffers/00_Vertex_input_description.adoc[vertex] and link:../../04_Vertex_buffers/03_Index_buffer.adoc[index buffers] translates directly to model loading, where glTF files contain vertex data in specific formats that we'll need to parse and upload to GPU buffers. link:../../05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.adoc[Uniform buffers] knowledge becomes essential as we'll use them for transformation matrices, lighting information, and material properties.
28+
29+
link:../../06_Texture_mapping/00_Images.adoc[Texture mapping] skills are particularly important since glTF models often include multiple textures per material (diffuse, normal, metallic-roughness, etc.), and we'll need to load and bind these textures efficiently.
3130

32-
* Basic Vulkan concepts:
33-
** link:../../03_Drawing_a_triangle/03_Drawing/01_Command_buffers.adoc[Command buffers]
34-
** link:../../03_Drawing_a_triangle/02_Graphics_pipeline_basics/00_Introduction.adoc[Graphics pipelines]
35-
* link:../../04_Vertex_buffers/00_Vertex_input_description.adoc[Vertex] and link:../../04_Vertex_buffers/03_Index_buffer.adoc[index buffers]
36-
* link:../../05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.adoc[Uniform buffers]
37-
* link:../../06_Texture_mapping/00_Images.adoc[Texture mapping]
38-
* Basic 3D math (matrices, vectors, quaternions) - See link:../../Building_a_Simple_Engine/Camera_Transformations/02_math_foundations.adoc[Camera Transformations] for a refresher on 3D math.
31+
Finally, basic 3D math understanding (matrices, vectors, quaternions) is crucial for handling model transformations, animations, and scene hierarchies. If you need a refresher, see the link:../../Building_a_Simple_Engine/Camera_Transformations/02_math_foundations.adoc[Camera Transformations chapter] for detailed coverage of these mathematical concepts.
3932

4033
link:../GUI/06_conclusion.adoc[Previous: GUI] | link:02_project_setup.adoc[Next: Setting Up the Project]

en/Building_a_Simple_Engine/Loading_Models/04_loading_gltf.adoc

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
::pp: {plus}{plus}
1+
:pp: {plus}{plus}
22

33
= Loading Models: Understanding glTF
4-
:doctype: book
5-
:sectnums:
6-
:sectnumlevels: 4
7-
:toc: left
8-
:icons: font
9-
:source-highlighter: highlightjs
10-
:source-language: c++
114

125
== Understanding glTF
136

@@ -196,7 +189,11 @@ For our engine, we use the .glb format with embedded KTX2 textures. This approac
196189

197190
The glTF specification supports embedded textures through the `bufferView` property of image objects. When using KTX2 textures, the `mimeType` is set to `"image/ktx2"` to indicate the format.
198191

199-
Here's how we extract material data and load embedded KTX2 textures from a glTF file:
192+
The texture loading process involves several complex steps that bridge the gap between glTF's abstract texture references and Vulkan's low-level GPU resources.
193+
194+
=== Texture Loading: glTF Texture Iteration and Metadata Extraction
195+
196+
First, we iterate through the glTF model's texture definitions and extracting the fundamental information needed to locate and identify each texture resource.
200197

201198
[source,cpp]
202199
----
@@ -208,7 +205,18 @@ for (size_t i = 0; i < gltfModel.textures.size(); i++) {
208205
209206
Texture tex;
210207
tex.name = image.name.empty() ? "texture_" + std::to_string(i) : image.name;
208+
----
209+
210+
The glTF texture system uses an indirection approach where textures reference images, and images contain the actual pixel data or references to it. This separation allows multiple textures to share the same image data but with different sampling parameters (like different filtering or wrapping modes). Our iteration process builds a comprehensive inventory of all texture resources that materials will eventually reference.
211211

212+
The naming strategy provides essential debugging and asset management capabilities. When artists create textures in their 3D applications, meaningful names help developers identify which textures serve which purposes during development. The fallback naming scheme ensures every texture has a unique identifier even when artists haven't provided descriptive names.
213+
214+
=== Texture Loading: Format Detection and Buffer Access
215+
216+
Next, we need to figure out whether textures are embedded in the glTF file and identify their format, setting up the foundation for appropriate loading strategies.
217+
218+
[source,cpp]
219+
----
212220
// Check if the image is embedded as KTX2
213221
if (image.mimeType == "image/ktx2" && image.bufferView >= 0) {
214222
// Get the buffer view that contains the KTX2 data
@@ -218,7 +226,18 @@ for (size_t i = 0; i < gltfModel.textures.size(); i++) {
218226
// Extract the KTX2 data from the buffer
219227
const uint8_t* ktx2Data = buffer.data.data() + bufferView.byteOffset;
220228
size_t ktx2Size = bufferView.byteLength;
229+
----
230+
231+
The MIME type detection ensures we're working with KTX2 format specifically, which provides several advantages over traditional image formats like PNG or JPEG. KTX2 is designed specifically for GPU textures and supports advanced features like basis universal compression, multiple mipmap levels, and direct GPU format compatibility. The bufferView check confirms that the image data is embedded within the glTF file rather than referenced externally.
232+
233+
The buffer access pattern demonstrates glTF's sophisticated data organization system. Rather than copying data unnecessarily, we obtain direct pointers to the KTX2 data within the loaded glTF buffer. This approach minimizes memory usage and avoids expensive copy operations, which is particularly important when dealing with large texture datasets that can easily consume hundreds of megabytes.
221234

235+
=== Texture Loading: KTX2 Parsing and Validation
236+
237+
Now we need to load the KTX2 texture data using the specialized KTX-Software library and perform initial validation to ensure the texture data is usable.
238+
239+
[source,cpp]
240+
----
222241
// Load the KTX2 texture using KTX-Software library
223242
ktxTexture2* ktxTexture = nullptr;
224243
KTX_error_code result = ktxTexture2_CreateFromMemory(
@@ -231,7 +250,18 @@ for (size_t i = 0; i < gltfModel.textures.size(); i++) {
231250
std::cerr << "Failed to load KTX2 texture: " << ktxErrorString(result) << std::endl;
232251
continue;
233252
}
253+
----
254+
255+
The KTX-Software library provides robust parsing of the complex KTX2 format, handling details like multiple mipmap levels, various pixel formats, and metadata that would be extremely complex to implement correctly from scratch. The `KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT` flag instructs the library to immediately load the actual pixel data into memory, preparing it for subsequent processing steps.
256+
257+
Error handling at this stage is crucial because texture files can become corrupted during asset pipeline processing or file transfer. By continuing with the next texture when one fails to load, we ensure that a single problematic texture doesn't prevent the entire model from loading. This graceful degradation approach is essential for robust production systems where content issues shouldn't crash the application.
258+
259+
=== Texture Loading: Basis Universal Transcoding
234260

261+
Next, we handle the transcoding process that converts Basis Universal compressed textures into GPU-native formats for optimal runtime performance.
262+
263+
[source,cpp]
264+
----
235265
// If the texture uses Basis Universal compression, transcode it to a GPU-friendly format
236266
if (ktxTexture->isCompressed && ktxTexture2_NeedsTranscoding(ktxTexture)) {
237267
// Choose the appropriate format based on GPU capabilities
@@ -253,7 +283,20 @@ for (size_t i = 0; i < gltfModel.textures.size(); i++) {
253283
continue;
254284
}
255285
}
286+
----
256287

288+
Basis Universal represents a revolutionary approach to texture compression that solves a fundamental problem in cross-platform development: different GPUs support different texture compression formats. Traditional approaches required storing multiple texture versions for different platforms, dramatically increasing storage requirements. Basis Universal stores textures in an intermediate format that can be quickly transcoded to any GPU-native format at load time.
289+
290+
The format selection logic (shown in commented form) demonstrates how production systems handle GPU capability differences. Desktop GPUs typically support BC7 compression which provides excellent quality, while mobile GPUs often use ASTC or ETC2 formats. The transcoding process happens at runtime based on the actual capabilities of the target GPU, ensuring optimal performance and quality on every platform.
291+
292+
The transcoding operation itself is computationally intensive but happens only once during asset loading. The resulting GPU-native format provides significantly better performance during rendering compared to uncompressed textures, making the upfront transcoding cost worthwhile. Failed transcoding attempts trigger cleanup of partially processed resources, preventing memory leaks in error conditions.
293+
294+
=== Texture Loading: Vulkan Resource Creation and GPU Upload
295+
296+
Finally, create the Vulkan resources needed for GPU rendering and uploads the processed texture data to video memory.
297+
298+
[source,cpp]
299+
----
257300
// Create Vulkan image, memory, and view
258301
vk::Format format = static_cast<vk::Format>(ktxTexture2_GetVkFormat(ktxTexture));
259302
vk::Extent3D extent{

en/Building_a_Simple_Engine/Loading_Models/05_pbr_rendering.adoc

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
::pp: {plus}{plus}
1+
:pp: {plus}{plus}
22

33
= Loading Models: Implementing PBR for glTF Models
4-
:doctype: book
5-
:sectnums:
6-
:sectnumlevels: 4
7-
:toc: left
8-
:icons: font
9-
:source-highlighter: highlightjs
10-
:source-language: c++
114

125
== Applying PBR to glTF Models
136

@@ -66,6 +59,11 @@ This uniform buffer includes:
6659

6760
==== Push Constants for Materials
6861

62+
[NOTE]
63+
====
64+
We introduced push constants earlier in link:../Lighting_Materials/03_push_constants.adoc[push constants]; here we focus on how the same mechanism carries glTF metallic‑roughness material knobs efficiently per draw.
65+
====
66+
6967
We'll use link:https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#descriptorsets-pushconstant[push constants] to pass material properties to the shader:
7068

7169
[source,cpp]

en/Building_a_Simple_Engine/Loading_Models/06_multiple_objects.adoc

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
::pp: {plus}{plus}
1+
:pp: {plus}{plus}
22

33
= Loading Models: Managing Multiple Objects
4-
:doctype: book
5-
:sectnums:
6-
:sectnumlevels: 4
7-
:toc: left
8-
:icons: font
9-
:source-highlighter: highlightjs
10-
:source-language: c++
114

125
== Managing Multiple Objects in a 3D Scene
136

@@ -471,6 +464,14 @@ vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
471464

472465
With hardware instancing set up, we can modify our rendering loop to draw all instances in a single call:
473466

467+
The same five steps apply here; the difference is in step 4 where we bind the instance buffer and draw N instances:
468+
469+
* Begin and describe attachments
470+
* Begin rendering, bind pipeline, set viewport/scissor
471+
* Update camera UBO (view/projection)
472+
* Bind per‑mesh vertex + index buffers and a per‑mesh instance buffer, then draw instanced
473+
* End rendering and present
474+
474475
[source,cpp]
475476
----
476477
void drawFrame() {

0 commit comments

Comments
 (0)