Unified Showcase
A comprehensive rendering demonstration combining shadows, terrain, GLTF models, and skybox rendering in a self-contained wgpu application.
cargo run -p unified_showcase --release
Overview
The unified showcase demonstrates:
- Shadow mapping with 2048×2048 depth textures
- GLTF model loading with materials and transforms
- Procedural terrain with multi-texture blending
- HDR skybox rendering
- 4× MSAA antialiasing
- First-person camera controls
This is a standalone renderer example that doesn’t depend on engine render crates—ideal for learning wgpu patterns.
Quick Start
# Run the showcase
cargo run -p unified_showcase --release
# Controls:
# WASD - Move camera
# Mouse - Look around (click to capture)
# Escape - Release mouse
# Space - Move up
# Shift - Move down
Features
Shadow Mapping
Real-time shadow casting with PCF (Percentage Closer Filtering):
#![allow(unused)]
fn main() {
// Shadow texture configuration
let shadow_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Shadow Texture"),
size: wgpu::Extent3d {
width: 2048,
height: 2048,
depth_or_array_layers: 1,
},
format: wgpu::TextureFormat::Depth32Float,
usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
...
});
// Comparison sampler for PCF
let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
compare: Some(wgpu::CompareFunction::Less),
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
...
});
}
GLTF Model Loading
The demo includes a custom GLTF loader for scene objects:
#![allow(unused)]
fn main() {
// Load and render GLTF models
mod gltf_loader;
// Material indices for different model parts
pine_bark_mat: usize,
pine_leaves_mat: usize,
tower_wood_mat: usize,
tower_stone_mat: usize,
}
Supported GLTF features:
- Mesh geometry (positions, normals, UVs)
- Base color textures
- Transform hierarchies
- Multiple materials per model
Terrain Rendering
Procedural terrain with height-based texture blending:
- Grass texture - Low elevation areas
- Rock texture - Steep slopes
- Snow texture - High elevation peaks
- Normal mapping - Surface detail
- Triplanar projection - Seamless texturing on steep surfaces
// terrain.wgsl - height-based texture blending
fn blend_terrain_textures(height: f32, slope: f32) -> vec4<f32> {
let grass_weight = smoothstep(0.0, 0.3, 1.0 - slope);
let rock_weight = smoothstep(0.2, 0.5, slope);
let snow_weight = smoothstep(0.7, 1.0, height);
return grass * grass_weight + rock * rock_weight + snow * snow_weight;
}
Skybox
HDR-based environment rendering:
#![allow(unused)]
fn main() {
// Skybox pipeline - renders at depth 1.0 (far plane)
let sky_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
depth_stencil: Some(wgpu::DepthStencilState {
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::LessEqual,
...
}),
...
});
}
MSAA
4× multisampling for smooth edges:
#![allow(unused)]
fn main() {
multisample: wgpu::MultisampleState {
count: 4,
mask: !0,
alpha_to_coverage_enabled: false,
}
}
Architecture
graph TD
A[ShowcaseApp] --> B[Render Pipelines]
A --> C[Scene Data]
A --> D[Camera System]
B --> E[Main Pipeline]
B --> F[Sky Pipeline]
B --> G[Terrain Pipeline]
B --> H[Shadow Pipeline]
C --> I[Meshes]
C --> J[Materials]
C --> K[Scene Objects]
E --> L[MSAA Texture]
L --> M[Resolve to Swapchain]
Render Pass Order
- Shadow Pass - Render scene from light perspective
- Skybox Pass - Render environment (no depth write)
- Terrain Pass - Render ground with shadows
- Object Pass - Render GLTF models with shadows
- MSAA Resolve - Resolve 4× samples to swapchain
Pipeline Details
Main Render Pipeline
#![allow(unused)]
fn main() {
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[Vertex::desc()],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
}),
multisample: wgpu::MultisampleState { count: 4, ... },
});
}
Bind Group Layouts
The showcase uses 4 main bind group layouts:
| Slot | Name | Contents |
|---|---|---|
| 0 | Camera | View-projection matrix, camera position |
| 1 | Light | Light view-projection, position, color, shadow map |
| 2 | Material | Albedo texture, sampler |
| 3 | Model | Per-object transform matrix |
Vertex Format
#![allow(unused)]
fn main() {
#[repr(C)]
struct Vertex {
position: [f32; 3],
normal: [f32; 3],
uv: [f32; 2],
color: [f32; 4],
tangent: [f32; 4],
}
}
Shaders
Main Shader (shader_v2.wgsl)
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let world_pos = model.model * vec4<f32>(in.position, 1.0);
out.clip_position = camera.view_proj * world_pos;
out.world_position = world_pos.xyz;
out.world_normal = normalize((model.model * vec4<f32>(in.normal, 0.0)).xyz);
out.uv = in.uv;
out.shadow_position = light.view_proj * world_pos;
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let shadow = calculate_shadow(in.shadow_position);
let albedo = textureSample(t_diffuse, s_diffuse, in.uv);
let lighting = calculate_lighting(in.world_normal, light.position, shadow);
return vec4<f32>(albedo.rgb * lighting, albedo.a);
}
Shadow Shader
Minimal shader for depth-only rendering:
@vertex
fn vs_main(in: VertexInput) -> @builtin(position) vec4<f32> {
return light.view_proj * model.model * vec4<f32>(in.position, 1.0);
}
Dependencies
[dependencies]
winit = { workspace = true }
wgpu = { workspace = true }
glam = { workspace = true }
pollster = { workspace = true }
bytemuck = { workspace = true }
image = { version = "0.25", features = ["png", "jpeg", "hdr", "webp"] }
gltf = { version = "1.4", features = ["utils", "KHR_materials_unlit"] }
Note: This example is self-contained and doesn’t use astraweave-render crates. It’s designed as a clean reference implementation.
Asset Requirements
The demo expects assets in the following locations:
assets/
├── models/
│ ├── pine_tree.glb
│ └── watch_tower.glb
├── textures/
│ ├── grass_albedo.png
│ ├── rock_albedo.png
│ └── snow_albedo.png
└── hdri/
└── polyhaven/
└── kloppenheim_02_puresky_2k.hdr
Performance
Typical performance on mid-range hardware:
| Metric | Value |
|---|---|
| Shadow Resolution | 2048×2048 |
| MSAA | 4× |
| Draw Calls | 10-50 (scene dependent) |
| Frame Time | 8-12 ms (60-80+ FPS) |
| VRAM | ~200-500 MB |
Optimization Opportunities
For production use, consider:
- Cascaded Shadow Maps - Multiple shadow cascades for large scenes
- Instanced Rendering - Batch similar objects
- Frustum Culling - Skip off-screen objects
- LOD System - Distance-based mesh simplification
Learning Value
This example teaches:
- wgpu Pipeline Creation - Complete setup from device to swapchain
- Bind Group Management - Proper resource binding patterns
- Shadow Mapping - Light-space rendering and PCF sampling
- GLTF Integration - Loading and rendering real 3D assets
- Multi-Pass Rendering - Coordinating multiple render passes
- MSAA Setup - Multisampling with resolve
Related Documentation
- Rendering System Guide - Engine rendering architecture
- Render API Reference - Complete rendering API
- Performance Optimization - General optimization
Troubleshooting
Black Screen
Symptom: Window opens but shows only black
Solutions:
- Check GPU supports wgpu backends (Vulkan/DX12/Metal)
- Update graphics drivers
- Enable logging:
RUST_LOG=info cargo run -p unified_showcase
Missing Textures
Symptom: Models render as solid colors
Cause: Asset files not found
Solution: Ensure assets/ directory contains required textures
Slow Performance
Symptom: FPS below 30
Solutions:
- Reduce shadow map resolution (edit source)
- Disable MSAA (change count to 1)
- Close other GPU applications
- Try different wgpu backend:
WGPU_BACKEND=dx12
GLTF Load Errors
Symptom: Panic on startup with GLTF error
Solutions:
- Verify GLTF files exist in
assets/models/ - Check GLTF files are valid (test in Blender)
- Ensure GLTF uses supported features only