Skip to main content

forge-depth-and-3d

Set up depth buffer, 3D MVP pipeline, back-face culling, and window resize handling. Use when someone needs to render 3D geometry with correct depth ordering, perspective projection, or a view camera in SDL3 GPU.

Jump to install

Source facts

Repository
Nebulavenus/forge-gpu
Last source activity
March 1, 2026 at 21:31
Detected SKILL.md language
English
Stars
38
Forks
7

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
forge-depth-and-3d
description
Set up depth buffer, 3D MVP pipeline, back-face culling, and window resize handling. Use when someone needs to render 3D geometry with correct depth ordering, perspective projection, or a view camera in SDL3 GPU.
# Depth Buffer & 3D Transforms — MVP Pipeline, Depth Testing, and Culling This skill teaches how to render 3D geometry with the full Model-View-Projection pipeline, depth testing, back-face culling, and window resize handling. It builds on the `uniforms-and-motion` skill (push uniforms) and `first-triangle` skill (vertex buffers, pipelines). ## When to use - Rendering 3D geometry (cubes, meshes, scenes) - Setting up perspective projection with a camera - Enabling depth testing so closer surfaces draw over farther ones - Adding back-face culling for closed meshes - Handling window resize for depth textures - Transitioning from 2D (float2) to 3D (float3) vertex positions ## Key API calls (ordered) 1. `SDL_CreateGPUTexture` — create depth texture with `DEPTH_STENCIL_TARGET` usage 2. `SDL_CreateGPUGraphicsPipeline` — with depth stencil state and back-face culling 3. `mat4_look_at` / `mat4_perspective` / `mat4_multiply` — build MVP matrix per frame 4. `SDL_PushGPUVertexUniformData` — push MVP matrix to vertex shader 5. `SDL_BeginGPURenderPass` — with both color target AND depth target 6. `SDL_DrawGPUIndexedPrimitives` — draw 3D geometry ## Code template ### Depth texture creation ```c static SDL_GPUTexture *create_depth_texture(SDL_GPUDevice *device, Uint32 w, Uint32 h) { SDL_GPUTextureCreateInfo info; SDL_zero(info); info.type = SDL_GPU_TEXTURETYPE_2D; info.format = SDL_GPU_TEXTUREFORMAT_D16_UNORM; info.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET; info.width = w; info.height = h; info.layer_count_or_depth = 1; info.num_levels = 1; SDL_GPUTexture *texture = SDL_CreateGPUTexture(device, &info); if (!texture) { SDL_Log("Failed to create depth texture: %s", SDL_GetError()); } return texture; } ``` ### Pipeline setup with depth and culling ```c SDL_GPUGraphicsPipelineCreateInfo pipeline_info; SDL_zero(pipeline_info); /* ... vertex input, shaders, etc. ... */ /* Back-face culling — skip faces pointing away from camera */ pipeline_info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_BACK; pipeline_info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE; /* Depth testing — closer fragments win */ pipeline_info.depth_stencil_state.enable_depth_test = true; pipeline_info.depth_stencil_state.enable_depth_write = true; pipeline_info.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL; /* Color target */ SDL_GPUColorTargetDescription color_desc; SDL_zero(color_desc); color_desc.format = SDL_GetGPUSwapchainTextureFormat(device, window); pipeline_info.target_info.color_target_descriptions = &color_desc; pipeline_info.target_info.num_color_targets = 1; /* Depth target — MUST declare format in pipeline */ pipeline_info.target_info.has_depth_stencil_target = true; pipeline_info.target_info.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D16_UNORM; ``` ### MVP matrix computation ```c #include "math/forge_math.h" /* Model: position and orient the object */ mat4 model = mat4_multiply( mat4_translate(vec3_create(0.0f, 0.0f, 0.0f)), mat4_multiply(mat4_rotate_y(angle_y), mat4_rotate_x(angle_x)) ); /* View: position and orient the camera */ mat4 view = mat4_look_at( vec3_create(0.0f, 1.5f, 3.0f), /* eye position */ vec3_create(0.0f, 0.0f, 0.0f), /* look-at target */ vec3_create(0.0f, 1.0f, 0.0f) /* world up */ ); /* Projection: perspective with correct aspect ratio */ float fov = 60.0f * FORGE_DEG2RAD; float aspect = (float)window_w / (float)window_h; mat4 proj = mat4_perspective(fov, aspect, 0.1f, 100.0f); /* Compose: MVP = Projection * View * Model */ mat4 mvp = mat4_multiply(mat4_multiply(proj, view), model); ``` ### Render pass with depth target ```c SDL_GPUColorTargetInfo color_target; SDL_zero(color_target); color_target.texture = swapchain; color_target.load_op = SDL_GPU_LOADOP_CLEAR; color_target.store_op = SDL_GPU_STOREOP_STORE; color_target.clear_color = (SDL_FColor){ 0.02f, 0.02f, 0.04f, 1.0f }; SDL_GPUDepthStencilTargetInfo depth_target; SDL_zero(depth_target); depth_target.texture = depth_texture; depth_target.load_op = SDL_GPU_LOADOP_CLEAR; depth_target.store_op = SDL_GPU_STOREOP_DONT_CARE; depth_target.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE; depth_target.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE; depth_target.clear_depth = 1.0f; /* far plane */ SDL_GPURenderPass *pass = SDL_BeginGPURenderPass( cmd, &color_target, 1, &depth_target); ``` ### Window resize handling ```c /* Check each frame — recreate depth texture if window size changed */ int w, h; SDL_GetWindowSizeInPixels(window, &w, &h); if ((Uint32)w != depth_width || (Uint32)h != depth_height) { SDL_ReleaseGPUTexture(device, depth_texture); depth_texture = create_depth_texture(device, (Uint32)w, (Uint32)h); depth_width = (Uint32)w; depth_height = (Uint32)h; } ``` ### Vertex shader (HLSL) ```hlsl cbuffer Uniforms : register(b0, space1) { column_major float4x4 mvp; }; struct VSInput { float3 position : TEXCOORD0; float3 color : TEXCOORD1; }; struct VSOutput { float4 position : SV_Position; float4 color : TEXCOORD0; }; VSOutput main(VSInput input) { VSOutput output; output.position = mul(mvp, float4(input.position, 1.0)); output.color = float4(input.color, 1.0); return output; } ``` ## Vertex format for 3D ```c typedef struct Vertex { vec3 position; /* 3D model-space position (FLOAT3) */ vec3 color; /* RGB per-vertex color (FLOAT3) */ } Vertex; /* Vertex attributes */ attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3; /* position */ attrs[0].offset = offsetof(Vertex, position); attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3; /* color */ attrs[1].offset = offsetof(Vertex, color); ``` ## Uniform struct ```c typedef struct Uniforms { mat4 mvp; /* 64 bytes — naturally 16-byte aligned */ } Uniforms; ``` A single `mat4` is exactly 64 bytes and naturally 16-byte aligned, so no padding is needed (unlike the `time + aspect + pad` structs from earlier lessons). ## HLSL column-major compatibility forge_math.h stores matrices column-major. HLSL defaults to `column_major` for `float4x4`. Use `mul(mvp, float4(pos, 1.0))` — no transpose needed. If you accidentally see an inside-out or mirrored cube, check: 1. Matrix storage order matches shader declaration 2. `mul(matrix, vector)` not `mul(vector, matrix)` 3. Winding order matches `front_face` setting ## Depth format choices | Format | Bits | Precision | Use case | |--------|------|-----------|----------| | `D16_UNORM` | 16 | Low | Simple scenes, universally supported | | `D24_UNORM` | 24 | Medium | Most 3D games | | `D32_FLOAT` | 32 | High | Large worlds, reverse-Z | For learning and simple scenes, `D16_UNORM` is sufficient and universally supported. ## Common mistakes | Mistake | Fix | |---------|-----| | Cube looks inside-out | Enable depth testing (`enable_depth_test = true`) | | No depth target in pipeline | Set `has_depth_stencil_target = true` and `depth_stencil_format` | | Crash on window resize | Recreate depth texture when window size changes | | Cube appears mirrored | Check `mul(mvp, vertex)` order matches column-major storage | | Back faces visible | Set `cull_mode = CULLMODE_BACK` with correct `front_face` | | Depth buffer not clearing | Set `load_op = LOADOP_CLEAR` and `clear_depth = 1.0f` | | Missing perspective | Use `mat4_perspective` not `mat4_orthographic` | ## Cleanup ```c SDL_ReleaseGPUTexture(device, depth_texture); SDL_ReleaseGPUBuffer(device, index_buffer); SDL_ReleaseGPUBuffer(device, vertex_buffer); SDL_ReleaseGPUGraphicsPipeline(device, pipeline); ``` ## Reference - [GPU Lesson 06 — Depth Buffer & 3D Transforms](../../../lessons/gpu/06-depth-and-3d/) — full implementation - [Math Lesson 05 — Matrices](../../../lessons/math/05-matrices/) — transform theory - [uniforms-and-motion skill](../forge-uniforms-and-motion/SKILL.md) — push uniforms - [first-triangle skill](../forge-first-triangle/SKILL.md) — vertex buffers, pipelines - `mat4_perspective()`, `mat4_look_at()` in `common/math/forge_math.h`
View on GitHub