Skip to content

Commit e3e51ee

Browse files
committed
Update the code snippets to better match the example code. Where it doesn't explain why it doesn't.
1 parent 38f4703 commit e3e51ee

14 files changed

Lines changed: 481 additions & 292 deletions

File tree

en/Advanced_glTF/Debugging_Visual_Auditing/02_debug_drawers.adoc

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ void draw_bone_bodies(
274274
? DYNAMIC_COLOR
275275
: KINEMATIC_COLOR;
276276
277-
glm::mat4 body_transform = pose_to_matrix(pose);
277+
glm::mat4 body_transform = pose.to_matrix();
278278
279279
const ColliderDef& def = bone_body.collider_def;
280280
if (def.shape == ColliderDef::Shape::CAPSULE) {
@@ -293,20 +293,25 @@ void draw_bone_bodies(
293293
}
294294
----
295295

296-
For physics constraints, most physics engines provide a query API to retrieve the constraint's anchor points and axis direction. Drawing a constraint as a line between its two anchor points plus a small indicator for the constraint axis gives you enough information to visually verify that the constraint setup is correct:
296+
For physics constraints, the `PhysicsWorld` interface does not expose a generic constraint query API, since constraint introspection is highly engine-specific in Jolt. The code below is **pseudocode** showing the pattern you would implement by casting your constraint handles to the concrete Jolt type (`JPH::Constraint*`) and reading `mPoint1`, `mHingeAxis1`, etc. directly:
297297

298298
[source,cpp]
299299
----
300+
// PSEUDOCODE — illustrates the pattern; get_constraint_debug_info() is not part
301+
// of the PhysicsWorld interface. Implement by casting to JPH::SwingTwistConstraint*
302+
// or JPH::HingeConstraint* and reading Jolt-specific members directly.
303+
300304
const glm::vec3 CONSTRAINT_COLOR = { 0.0f, 0.5f, 1.0f }; // Blue for constraints
301305
const glm::vec3 LIMIT_COLOR = { 1.0f, 0.0f, 1.0f }; // Magenta for limits
302306
303307
void draw_constraints(
304308
DebugDrawer& drawer,
305-
const PhysicsWorld& physics_world,
306309
const std::vector<void*>& constraint_handles)
307310
{
308311
for (void* constraint : constraint_handles) {
309-
ConstraintDebugInfo info = physics_world.get_constraint_debug_info(constraint);
312+
// Cast to the concrete Jolt type and extract anchor/axis data
313+
// (ConstraintDebugInfo is a project-specific helper struct, not part of Jolt)
314+
ConstraintDebugInfo info = extract_constraint_debug_info(constraint);
310315
311316
// Draw a line connecting the two constraint anchor points
312317
drawer.draw_line(info.anchor_a, info.anchor_b, CONSTRAINT_COLOR);

en/Advanced_glTF/Debugging_Visual_Auditing/03_skinning_heatmaps.adoc

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,61 +15,67 @@ The heatmap is rendered by replacing the standard PBR fragment shader with a dia
1515

1616
[source,slang]
1717
----
18-
// Vertex shader: pass through position and skinning attributes
19-
struct VertexOut {
20-
float4 position : SV_Position;
21-
float4 weights : TEXCOORD0; // The four bone weights
22-
uint4 joints : TEXCOORD1; // The four bone indices
23-
};
18+
// Camera data — declared as a push constant for simplicity.
19+
struct CameraPushConstants { float4x4 view_proj; };
20+
[[vk::push_constant]] CameraPushConstants camera;
2421
25-
// Vertex input mirrors the InputVertex layout
26-
struct InputVertex {
22+
// Vertex data without embedded joint indices/weights —
23+
// joint data is stored in separate parallel buffers (same pattern as skinning.slang).
24+
struct Vertex {
2725
float3 position;
2826
float3 normal;
2927
float4 tangent;
3028
float2 texcoord;
31-
uint4 joint_indices;
32-
float4 joint_weights;
3329
};
3430
35-
[[vk::binding(0, 0)]] StructuredBuffer<InputVertex> vertices;
36-
[[vk::binding(1, 0)]] StructuredBuffer<float4x4> joint_matrices;
37-
[[vk::binding(2, 0)]] StructuredBuffer<float4> joint_colors; // One color per joint
31+
// Five bindings: vertex geometry, joint matrices, joint indices, joint weights, joint colors.
32+
[[vk::binding(0, 0)]] StructuredBuffer<Vertex> vertices;
33+
[[vk::binding(1, 0)]] StructuredBuffer<float4x4> joint_matrices; // For world position
34+
[[vk::binding(2, 0)]] StructuredBuffer<uint4> joint_indices;
35+
[[vk::binding(3, 0)]] StructuredBuffer<float4> joint_weights;
36+
[[vk::binding(4, 0)]] StructuredBuffer<float4> joint_colors; // One RGBA per joint
37+
38+
// Vertex shader passes joint data to the fragment stage for coloring.
39+
struct VertexOut {
40+
float4 position : SV_Position;
41+
float4 weights : TEXCOORD0;
42+
uint4 joints : TEXCOORD1;
43+
};
3844
3945
[shader("vertex")]
4046
VertexOut vertex_main(uint vertex_id : SV_VertexID)
4147
{
42-
InputVertex v = vertices[vertex_id];
48+
Vertex v = vertices[vertex_id];
49+
uint4 j_idx = joint_indices[vertex_id];
50+
float4 j_w = joint_weights[vertex_id];
4351
44-
// Apply skinning to get world position (same as Chapter 3)
52+
// Apply skinning to get world position (same LBS as skinning.slang)
4553
float4x4 skin_matrix =
46-
v.joint_weights.x * joint_matrices[v.joint_indices.x] +
47-
v.joint_weights.y * joint_matrices[v.joint_indices.y] +
48-
v.joint_weights.z * joint_matrices[v.joint_indices.z] +
49-
v.joint_weights.w * joint_matrices[v.joint_indices.w];
54+
j_w.x * joint_matrices[j_idx.x] +
55+
j_w.y * joint_matrices[j_idx.y] +
56+
j_w.z * joint_matrices[j_idx.z] +
57+
j_w.w * joint_matrices[j_idx.w];
5058
5159
VertexOut out;
5260
out.position = mul(camera.view_proj, mul(skin_matrix, float4(v.position, 1.0)));
53-
out.weights = v.joint_weights;
54-
out.joints = v.joint_indices;
61+
out.weights = j_w;
62+
out.joints = j_idx;
5563
return out;
5664
}
5765
58-
// Fragment shader: colorize by dominant joint
59-
// Each joint gets a unique color from a lookup table.
66+
// Colors each pixel by the dominant (highest-weight) joint using a per-joint color table.
6067
[shader("fragment")]
6168
float4 fragment_dominant_bone(VertexOut input) : SV_Target
6269
{
63-
// Find the joint with the highest weight
64-
uint dominant_joint = 0;
65-
float max_weight = input.weights.x;
70+
uint dominant = 0;
71+
float max_w = input.weights.x;
6672
67-
if (input.weights.y > max_weight) { max_weight = input.weights.y; dominant_joint = 1; }
68-
if (input.weights.z > max_weight) { max_weight = input.weights.z; dominant_joint = 2; }
69-
if (input.weights.w > max_weight) { max_weight = input.weights.w; dominant_joint = 3; }
73+
if (input.weights.y > max_w) { max_w = input.weights.y; dominant = 1; }
74+
if (input.weights.z > max_w) { max_w = input.weights.z; dominant = 2; }
75+
if (input.weights.w > max_w) { max_w = input.weights.w; dominant = 3; }
7076
71-
uint actual_joint_idx = input.joints[dominant_joint];
72-
return joint_colors[actual_joint_idx];
77+
uint actual_joint = input.joints[dominant];
78+
return joint_colors[actual_joint];
7379
}
7480
----
7581

en/Advanced_glTF/Morph_Targets_Facial_Animation/02_shape_key_ingestion.adoc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,10 @@ A common pattern is to maintain a per-mesh `std::vector<float>` of morph weights
215215
[source,cpp]
216216
----
217217
struct MorphWeightBlock {
218-
float weights[64]; // Support up to 64 active morph targets
219-
uint32_t active_count;
220-
uint32_t _pad[3];
218+
float weights[24]; // Up to 24 active morph targets
219+
uint32_t activeCount; // How many entries in weights[] are valid
220+
uint32_t applySkinning; // 1 = also apply skeletal skinning, 0 = morph only
221+
uint32_t pad[2]; // Align to 16 bytes
221222
};
222223
----
223224

en/Advanced_glTF/Morph_Targets_Facial_Animation/03_bindless_morph_buffers.adoc

Lines changed: 68 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -191,30 +191,39 @@ Here is the extended shader in Slang. The key additions are the `morphTargets` b
191191

192192
[source,slang]
193193
----
194-
// Bindless array of morph target displacement buffers.
195-
// Each buffer contains one vec3 per vertex in the mesh.
196-
[[vk::binding(0, 1)]]
197-
StructuredBuffer<float3> morphTargets[];
194+
// morph_accumulate.slang — combines morph target accumulation with skeletal skinning.
195+
// Requires: VK_EXT_descriptor_indexing, runtimeDescriptorArray.
198196
199-
// Weights for the currently active morph targets.
197+
// Same C-array struct layout as skinning.slang (matches GPU buffer memory layout).
198+
struct InputVertex { float p[3]; float n[3]; float uv[2]; float t[4]; };
199+
struct OutputVertex { float p[3]; float n[3]; float uv[2]; float t[4]; };
200+
201+
// Set 0: per-mesh buffers (same 5-binding layout as the base skinning shader)
202+
[[vk::binding(0, 0)]] StructuredBuffer<InputVertex> base_vertices;
203+
[[vk::binding(1, 0)]] RWStructuredBuffer<OutputVertex> output_vertices;
204+
[[vk::binding(2, 0)]] StructuredBuffer<float4x4> joint_matrices;
205+
[[vk::binding(3, 0)]] StructuredBuffer<uint4> joint_indices;
206+
[[vk::binding(4, 0)]] StructuredBuffer<float4> joint_weights;
207+
208+
// Set 1: runtime-length array of morph delta buffers (one StructuredBuffer per target).
209+
// MorphDelta uses a C-array to match GPU buffer layout.
210+
struct MorphDelta { float d[3]; };
211+
[[vk::binding(0, 1)]] StructuredBuffer<MorphDelta> morph_targets[];
212+
213+
// MorphWeightBlock must match SkinPushConstants::MorphWeightBlock in renderer_advanced_types.h.
200214
struct MorphWeightBlock {
201-
float weights[64];
202-
uint32_t active_count;
203-
uint32_t pad[3];
215+
float weights[24]; // Blending weights for the active targets
216+
uint32_t active_count; // How many entries in weights[] are valid
217+
uint32_t apply_skinning; // 1 = also apply skeletal skinning, 0 = morph only
218+
uint32_t pad[2];
204219
};
205-
[[vk::push_constant]]
220+
206221
struct PushConstants {
207-
// From Chapter 3: skeleton joint matrix indices
208-
uint32_t vertex_count;
209-
uint32_t joint_count;
210-
// Morph target indices (which slots in morphTargets[] are active)
211-
uint32_t morph_indices[64];
222+
uint32_t vertex_count;
223+
uint32_t morph_indices[24]; // Which descriptor slot each active target lives in
212224
MorphWeightBlock morph_weights;
213-
} pc;
214-
215-
[[vk::binding(0, 0)]] StructuredBuffer<InputVertex> base_vertices;
216-
[[vk::binding(1, 0)]] RWStructuredBuffer<OutputVertex> output_vertices;
217-
[[vk::binding(2, 0)]] StructuredBuffer<float4x4> joint_matrices;
225+
};
226+
[[vk::push_constant]] PushConstants pc;
218227
219228
[shader("compute")]
220229
[numthreads(64, 1, 1)]
@@ -224,39 +233,55 @@ void main(uint3 dispatch_id : SV_DispatchThreadID)
224233
if (vertex_idx >= pc.vertex_count) return;
225234
226235
InputVertex base = base_vertices[vertex_idx];
236+
float3 base_pos = float3(base.p[0], base.p[1], base.p[2]);
237+
float3 base_nrm = float3(base.n[0], base.n[1], base.n[2]);
238+
float4 base_tan = float4(base.t[0], base.t[1], base.t[2], base.t[3]);
227239
228-
// --- Step 1: Apply morph target displacements ---
229-
float3 morphed_position = base.position;
230-
float3 morphed_normal = base.normal;
240+
// --- Pass 1: morph target accumulation ---
241+
float3 morphed_position = base_pos;
242+
float3 morphed_normal = base_nrm;
231243
232244
for (uint m = 0; m < pc.morph_weights.active_count; ++m) {
233-
uint target_slot = pc.morph_indices[m];
234-
float weight = pc.morph_weights.weights[m];
235-
if (weight < 1e-5f) continue;
245+
float weight = pc.morph_weights.weights[m];
246+
if (abs(weight) < 1e-5f) continue;
236247
237-
// NonUniformResourceIndex is required when indexing bindless arrays
238-
// with a non-uniform value (different invocations may use different indices)
239-
float3 delta = morphTargets[NonUniformResourceIndex(target_slot)][vertex_idx];
248+
uint slot = pc.morph_indices[m];
249+
// NonUniformResourceIndex: different threads may use different descriptor slots.
250+
MorphDelta delta_raw = morph_targets[NonUniformResourceIndex(slot)][vertex_idx];
251+
float3 delta = float3(delta_raw.d[0], delta_raw.d[1], delta_raw.d[2]);
240252
morphed_position += weight * delta;
241-
// Normal deltas would go here if you have a second morphNormals[] array
242253
}
243254
244-
// --- Step 2: Skeletal skinning (same as Chapter 3) ---
245-
float4x4 skin_matrix =
246-
base.joint_weights.x * joint_matrices[base.joint_indices.x] +
247-
base.joint_weights.y * joint_matrices[base.joint_indices.y] +
248-
base.joint_weights.z * joint_matrices[base.joint_indices.z] +
249-
base.joint_weights.w * joint_matrices[base.joint_indices.w];
255+
// --- Pass 2: skeletal skinning (same LBS as skinning.slang) ---
256+
uint4 j_idx = joint_indices[vertex_idx];
257+
float4 j_w = joint_weights[vertex_idx];
250258
251-
float4 world_pos = mul(skin_matrix, float4(morphed_position, 1.0));
252-
float3 world_nrm = normalize(mul(float3x3(skin_matrix), morphed_normal));
259+
float4x4 skin_matrix =
260+
j_w.x * joint_matrices[j_idx.x] +
261+
j_w.y * joint_matrices[j_idx.y] +
262+
j_w.z * joint_matrices[j_idx.z] +
263+
j_w.w * joint_matrices[j_idx.w];
264+
265+
float3x3 skin_rot = float3x3(
266+
skin_matrix[0].xyz, skin_matrix[1].xyz, skin_matrix[2].xyz);
267+
268+
float3 final_position = morphed_position;
269+
float3 final_normal = morphed_normal;
270+
float3 final_tangent = base_tan.xyz;
271+
if (pc.morph_weights.apply_skinning != 0u) {
272+
final_position = mul(skin_matrix, float4(morphed_position, 1.0)).xyz;
273+
final_normal = normalize(mul(skin_rot, morphed_normal));
274+
final_tangent = normalize(mul(skin_rot, base_tan.xyz));
275+
}
253276
254-
// Write to output buffer (shared with rasterizer, ray tracing, physics)
277+
// Write to output buffer element-by-element (C-array structs require this).
255278
OutputVertex out;
256-
out.position = world_pos.xyz;
257-
out.normal = world_nrm;
258-
out.tangent = base.tangent;
259-
out.texcoord = base.texcoord;
279+
out.p[0] = final_position.x; out.p[1] = final_position.y; out.p[2] = final_position.z;
280+
out.n[0] = final_normal.x; out.n[1] = final_normal.y; out.n[2] = final_normal.z;
281+
out.t[0] = final_tangent.x; out.t[1] = final_tangent.y;
282+
out.t[2] = final_tangent.z; out.t[3] = base_tan.w;
283+
out.uv[0] = base.uv[0]; out.uv[1] = base.uv[1];
284+
260285
output_vertices[vertex_idx] = out;
261286
}
262287
----
@@ -273,7 +298,7 @@ The morph system integrates into the frame loop naturally:
273298
4. **Dispatch.** Bind the descriptor set (which has all morph target buffers registered), push constants, and dispatch the compute shader. One thread per vertex.
274299
5. **Barrier.** Issue a `VkBufferMemoryBarrier2` on the output buffer (same as Chapter 3) before the rasterizer or ray tracing pass reads from it.
275300

276-
The only part that requires care is the per-frame push constant size. If you have up to 64 active morph targets, the push constants become large—64 indices (256 bytes) plus 64 weights (256 bytes) plus metadata equals over 512 bytes. The Vulkan specification guarantees a minimum of 128 bytes of push constant space, but most desktop GPUs support 256 bytes, and high-end GPUs support 256 bytes or more. If you exceed the available push constant space, move the weight data into a small uniform buffer instead.
301+
The only part that requires care is the per-frame push constant size. With up to 24 active morph targets, `PushConstants` contains 24 indices (96 bytes) plus `MorphWeightBlock` with 24 weights (96 bytes) plus metadata — roughly 200 bytes total. The Vulkan specification guarantees a minimum of 128 bytes of push constant space, but most desktop GPUs support 256 bytes or more. The 24-target cap was chosen to stay within that budget. If you need more simultaneous targets, move the weight data into a small uniform buffer instead.
277302

278303
== Morph Targets and Skeletal Rigs Together
279304

0 commit comments

Comments
 (0)