Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rendering

Flint uses wgpu 23 for cross-platform GPU rendering, providing physically-based rendering (PBR) with a Cook-Torrance BRDF, cascaded shadow mapping, and full glTF mesh support.

PBR Shading

The renderer implements a metallic-roughness PBR workflow based on the Cook-Torrance specular BRDF:

  • Base color — the surface albedo, optionally sampled from a texture
  • Roughness — controls specular highlight spread (0.0 = mirror, 1.0 = diffuse)
  • Metallic — interpolates between dielectric and metallic response
  • Emissive — self-illumination for light sources and glowing objects

Materials are defined in scene TOML via the material component, matching the fields in schemas/components/material.toml.

Shadow Mapping

Directional lights cast shadows via cascaded shadow maps. Multiple shadow cascades cover different distance ranges from the camera, giving high-resolution shadows close up and broader coverage at distance.

Toggle shadows at runtime with F4.

Camera Modes

The renderer supports two camera modes that share the same view/projection math:

ModeUsageControls
OrbitScene viewer (serve)Left-drag to orbit, right-drag to pan, scroll to zoom
First-personPlayer (play)WASD to move, mouse to look, Space to jump, Shift to sprint

The camera mode is determined by the entry point: serve uses orbit, play uses first-person. Both produce the same view and projection matrices.

glTF Mesh Rendering

Imported glTF models are rendered with their full mesh geometry and materials. The flint-import crate extracts meshes, materials, and textures from .glb/.gltf files, which the renderer draws with PBR shading.

Skinned Mesh Pipeline

For skeletal animation, the renderer provides a separate GPU pipeline that applies bone matrix skinning in the vertex shader. This avoids the 32-byte overhead of bone data on static geometry.

How it works:

  1. flint-import extracts joint indices and weights from glTF skins alongside the mesh data
  2. flint-animation evaluates keyframes and computes bone matrices each frame (local pose -> global hierarchy -> inverse bind matrix)
  3. The renderer uploads bone matrices to a storage buffer and applies them in the vertex shader

Key types:

  • SkinnedVertex — extends the standard vertex with joint_indices: [u32; 4] and joint_weights: [f32; 4] (6 attributes total vs. 4 for static geometry)
  • GpuSkinnedMesh — holds the vertex/index buffers, material, and a bone matrix storage buffer with its bind group
  • Skinned pipeline uses bind groups 0–3: transform, material, lights, and bones (storage buffer, read-only, vertex-visible)

Skinned meshes also cast shadows through a dedicated vs_skinned_shadow shader entry point that applies bone transforms before depth rendering.

Billboard Sprites

Billboard sprites are camera-facing quads used for 2D elements in 3D space — enemies, pickups, particle effects, and environmental details. They always face the camera, like classic Doom-style sprites.

The BillboardPipeline is a separate rendering pipeline from PBR, optimized for flat textured quads:

  • No vertex buffer — quad positions are generated procedurally from vertex_index (4 vertices per sprite)
  • Per-sprite uniform buffer — each sprite gets its own instance data (position, size, frame, anchor)
  • Binary alpha — the fragment shader uses discard for transparent pixels (avoids order-independent transparency complexity)
  • Sprite sheet animation — supports multi-frame sprite sheets via frame, frames_x, and frames_y fields
  • Render order — billboard sprites render after skinned meshes in the pipeline

Sprite Component

Attach a sprite to any entity with the sprite component:

[entities.imp]
archetype = "enemy"

[entities.imp.transform]
position = [10, 0, 5]

[entities.imp.sprite]
texture = "imp_spritesheet"
width = 1.5
height = 2.0
frames_x = 4
frames_y = 1
frame = 0
anchor_y = 0.0
fullbright = true
FieldTypeDefaultDescription
texturestring""Sprite sheet texture name (from sprites/ directory)
widthf321.0World-space width of the quad
heightf321.0World-space height of the quad
framei320Current frame index in the sprite sheet
frames_xi321Number of columns in the sprite sheet
frames_yi321Number of rows in the sprite sheet
anchor_yf320.0Vertical anchor point (0.0 = bottom, 0.5 = center)
fullbrightbooltrueIf true, bypasses PBR lighting (always fully lit)
visiblebooltrueWhether the sprite is rendered

Design Decisions

Billboard sprites use a separate pipeline rather than extending the PBR pipeline. This keeps the PBR shaders clean and allows sprites to opt out of lighting entirely (fullbright = true). The discard-based alpha approach is simple and avoids the significant complexity of order-independent transparency, at the cost of no partial transparency (pixels are either fully opaque or fully transparent).

Post-Processing

The renderer includes an HDR post-processing pipeline that applies bloom, tonemapping, and vignette as a final pass. See Post-Processing for full details.

When post-processing is active, all scene pipelines (PBR, skinned, billboard, particle, skybox) render to an Rgba16Float HDR intermediate buffer. A composite fullscreen pass then applies exposure, ACES tonemapping, gamma correction, and optional vignette to produce the final sRGB output.

Configure post-processing per-scene via the [post_process] TOML block, or override with CLI flags (--no-postprocess, --bloom-intensity, --bloom-threshold, --exposure).

PBR Materials

PBR material showcase — varying roughness and metallic values

PBR materials with varying roughness and metallic values. Left to right: rough dielectric, smooth dielectric, rough metal, polished metal.

Debug Visualization

The renderer provides six debug visualization modes, cycled with F1:

ModeDescription
PBRStandard Cook-Torrance shading (default)
WireframeEdge lines only, no fill
NormalsWorld-space surface normals mapped to RGB
DepthLinearized depth as grayscale
UV CheckerUV coordinates as a procedural checkerboard
UnlitAlbedo color only, no lighting
Metal/RoughMetallic (red channel) and roughness (green channel)

Additional debug overlays:

  • Wireframe overlay (F2 in viewer, --wireframe-overlay in render) — draws edges on top of solid shading
  • Normal arrows (F3 in viewer, --show-normals in render) — draws face-normal direction arrows

Wireframe debug mode

Wireframe debug mode showing mesh topology.

Normal visualization

Normal debug mode mapping world-space normals to RGB channels.

Viewer vs Headless

The renderer operates in two modes:

Viewer mode (flint serve --watch) opens an interactive window with:

  • Real-time PBR rendering
  • egui inspector panel (entity tree, component editor, constraint overlay)
  • Hot-reload: edit the scene TOML and the viewer updates automatically
  • Debug rendering modes (cycle with F1)

Headless mode (flint render) renders to a PNG file without opening a window — useful for CI pipelines and automated screenshots:

flint render levels/tavern.scene.toml --output preview.png --width 1920 --height 1080

Technology

The rendering stack uses winit 0.30’s ApplicationHandler trait pattern (not the older event-loop closure style). wgpu 23 provides the GPU abstraction, selecting the best available backend (Vulkan, Metal, or DX12) at runtime.

Further Reading