Use when writing ANY rendering code, GPU pipeline code, shader code, or visual output code for the ultimate-flat engine. This includes: creating render passes, writing HLSL shaders, setting up instanced rendering, implementing ambient occlusion, building greedy meshers, shadow maps, or any code that puts pixels on screen. Also activate when the user mentions rendering, visuals, graphics, shaders, GPU, draw calls, or SDL3 GPU API. CRITICAL: Never use SDL_Renderer (2D API) — always use SDL3 GPU API (SDL_gpu.h) with Vulkan/D3D12/Metal backends.
NEVER use SDL_Renderer (SDL_RenderGeometry, SDL_RenderLine, SDL_RenderFillRect, etc.) for 3D visualization. These are 2D-only APIs with no depth buffer, no shaders, no GPU pipeline.
ALWAYS use SDL3 GPU API (SDL_gpu.h): SDL_CreateGPUDevice, SDL_CreateGPUGraphicsPipeline, SDL_BeginGPURenderPass, SDL_DrawGPUIndexedPrimitives, etc.
This engine renders voxels in three states with different strategies:
| State | Method | Draw Calls | When to Use |
|---|---|---|---|
| Intact | Greedy-meshed chunks + baked AO | 1 per chunk | Static voxel terrain/rocks |
| Active fragments | Instanced cubes (24B/instance) | 1 total | Moving debris after fracture |
| Settled debris | Merged into greedy chunk | 1 per chunk | Fragments that stopped moving |
.claude/references/sdl3-gpu-api.md — API overview, capabilities, what backends exist.claude/references/sdl3-gpu-patterns.md — Concrete code patterns: init, buffer upload, instancing, compute→graphics, draw indirect, depth passes. Copy these..claude/references/shader-workflow.md — HLSL→SPIR-V compilation with DXC, CMake integration, binding conventions, example shaders.claude/references/voxel-meshing-algorithms.md — Greedy meshing algorithm, per-vertex AO formula, packed vertex formats, fragment lifecycle.claude/references/voxel-rendering-research.md — Teardown analysis, performance numbers, what matters at RTS distance.claude/references/deferred-pbr-correctness.md — Deferred PBR correctness spec: G-buffer formats, triplanar normals, Cook-Torrance GGX, tonemapping, AO, gamma pipeline. Read this before writing or modifying any shader or render pass.Every mesh face has an exposure attribute: 1 = exterior (weathered), 0 = interior (fresh fracture).
Exterior faces use triplanar projection with a weathered rock texture — dark patina, smooth, color variation. When a rock fractures, the outside pieces KEEP their original exterior texture unchanged.
Interior faces (newly exposed by fracture) use a plain per-material tint — light gray for stone, tan for sandstone, etc. These are the "fresh cut" surfaces.
The shader blends between exterior and interior texture sets based on the exposure attribute. Triplanar projection means no UVs — works on any geometry including fresh fracture faces.
Weathering over game-time (nice-to-have): fresh interior faces gradually accumulate exposure (0.0 → 1.0) and transition to the weathered look. Not needed for MVP.
Shader language: HLSL. Compile to SPIR-V with DXC (already installed on this machine).
Per-frame instance update: Use persistent transfer buffer with cycle=true. Map, write, unmap, upload. SDL handles double-buffering internally.
Uniforms: Push-based (SDL_PushGPUVertexUniformData) for MVP matrix and light params. NO UBO creation needed.
Large data: Storage buffers (SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ), not uniform buffers.
Depth buffer: SDL_GPU_TEXTUREFORMAT_D32_FLOAT. Created once at init, passed to SDL_BeginGPURenderPass as depth-stencil target.
At Anno/Cities Skylines zoom, 1 pixel = 5-10cm. Individual voxel edges are invisible.
High impact: Ambient occlusion, directional shadows, per-voxel color noise, silhouette variation.
Zero impact: Normal maps, PBR roughness detail, smooth mesh extraction (all sub-pixel).
Full spec: .claude/references/deferred-pbr-correctness.md — read before writing or modifying any shader, render pass, or texture loading code.
| Target | Format | Why |
|---|---|---|
| Albedo | R8G8B8A8_UNORM_SRGB | sRGB encoding preserves dark-value precision. Plain UNORM posterizes shadows. |
| Normal | RGBA16F | RGBA8 only gives 256 levels per component — causes banding and kills subtle normal detail. |
| Position | RGBA16F or RGBA32F | Needs range beyond [0,1]. |
| Roughness/Metallic/AO | RGBA8_UNORM | Scalar data, 256 levels is fine. |
The Whiteout blend uses Ben Golus's canonical formulation (from his GitHub reference implementation). Three rules:
lerp(1.0, tn.z, saturate(strength)). This is Unity's standard approach.Reference HLSL in the full spec (Section 2).
Do NOT use Reinhard (color / (color + 1)). It compresses highlights so aggressively that normal-map detail becomes invisible at sun intensity > 3. Use ACES Filmic tonemapping instead (reference code in Section 4 of the spec).
| Type | Load as sRGB? | Consequence of getting wrong |
|---|---|---|
| Albedo | YES (*_SRGB) | Without sRGB: darks appear too bright, washed out |
| Normal map | NO (plain UNORM) | With sRGB: all normals biased, systematic lighting error |
| Roughness/AO | NO (plain UNORM) | With sRGB: material response distorted |
Exactly one sRGB encode across the entire path. Check SDL_GetGPUSwapchainTextureFormat() at runtime:
*_SRGB: output linear values, hardware encodes. NO manual pow(1/2.2).UNORM: apply pow(1/2.2) in fragment shader.All textures used with triplanar mapping MUST have mipmaps. Without them, distant surfaces shimmer and specular averages to a flat sheen. Set num_levels = floor(log2(max(w,h))) + 1. Use SDL_GPU_SAMPLERMIPMAPMODE_LINEAR for trilinear.
IBL is mandatory for PBR to look correct. Without it, surfaces look flat and unnaturally dark in shadow. The engine uses HDRI environment map IBL with analytical fallback:
assets/hdri/outdoor.hdr — equirectangular Radiance HDR, loaded as RGBA16F with mipmap chainroughness * (mipCount - 1) mip LODIBL is scaled by AmbientIntensity for artist control. Shader slot 5 in the lighting pass. Controlled by HasHDRI and HDRIMipCount uniforms.
AO modulates indirect light (IBL) only. Direct sunlight and fill light are NOT affected by AO (they have shadow maps). Applying AO to direct light causes unnatural double-darkening in crevices.
If the render looks flat or washed out: