You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
15
8
16
9
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.
17
10
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.
19
12
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.
25
14
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.
27
20
28
21
== Prerequisites
29
22
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.
* 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.
39
32
40
33
link:../GUI/06_conclusion.adoc[Previous: GUI] | link:02_project_setup.adoc[Next: Setting Up the Project]
Copy file name to clipboardExpand all lines: en/Building_a_Simple_Engine/Loading_Models/04_loading_gltf.adoc
+52-9Lines changed: 52 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,13 +1,6 @@
1
-
::pp: {plus}{plus}
1
+
:pp: {plus}{plus}
2
2
3
3
= 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++
11
4
12
5
== Understanding glTF
13
6
@@ -196,7 +189,11 @@ For our engine, we use the .glb format with embedded KTX2 textures. This approac
196
189
197
190
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.
198
191
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.
200
197
201
198
[source,cpp]
202
199
----
@@ -208,7 +205,18 @@ for (size_t i = 0; i < gltfModel.textures.size(); i++) {
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.
211
211
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
+
----
212
220
// Check if the image is embedded as KTX2
213
221
if (image.mimeType == "image/ktx2" && image.bufferView >= 0) {
214
222
// Get the buffer view that contains the KTX2 data
@@ -218,7 +226,18 @@ for (size_t i = 0; i < gltfModel.textures.size(); i++) {
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.
221
234
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
+
----
222
241
// Load the KTX2 texture using KTX-Software library
223
242
ktxTexture2* ktxTexture = nullptr;
224
243
KTX_error_code result = ktxTexture2_CreateFromMemory(
@@ -231,7 +250,18 @@ for (size_t i = 0; i < gltfModel.textures.size(); i++) {
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
234
260
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
+
----
235
265
// If the texture uses Basis Universal compression, transcode it to a GPU-friendly format
236
266
if (ktxTexture->isCompressed && ktxTexture2_NeedsTranscoding(ktxTexture)) {
237
267
// Choose the appropriate format based on GPU capabilities
@@ -253,7 +283,20 @@ for (size_t i = 0; i < gltfModel.textures.size(); i++) {
253
283
continue;
254
284
}
255
285
}
286
+
----
256
287
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
+
----
257
300
// Create Vulkan image, memory, and view
258
301
vk::Format format = static_cast<vk::Format>(ktxTexture2_GetVkFormat(ktxTexture));
Copy file name to clipboardExpand all lines: en/Building_a_Simple_Engine/Loading_Models/05_pbr_rendering.adoc
+6-8Lines changed: 6 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,13 +1,6 @@
1
-
::pp: {plus}{plus}
1
+
:pp: {plus}{plus}
2
2
3
3
= 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++
11
4
12
5
== Applying PBR to glTF Models
13
6
@@ -66,6 +59,11 @@ This uniform buffer includes:
66
59
67
60
==== Push Constants for Materials
68
61
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
+
69
67
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:
0 commit comments