Rendering Architecture
Overview
SiliconGhetto’s rendering layer (sg_render) is built on wgpu. WebGPU is the primary browser renderer and the default path; WebGL2 is used only as a fallback when hardware WebGPU is unavailable or unsuitable. All rendering goes through a unified pipeline that works identically in the browser (via WASM) and on native targets (for development and testing).
GPU Initialization
Browser Canvas → wgpu Instance → Surface → Adapter → Device + Queue
- Instance: Created for the selected backend: WebGPU first, WebGL2 fallback only when needed
- Surface: Created from a canvas element (or OffscreenCanvas in worker mode)
- Adapter: Selected based on power preference (high performance by default)
- Device + Queue: The primary interface for all GPU operations
The GpuContext struct in sg_render::gpu owns the device, queue, and surface configuration.
2D Sprite Rendering
The sprite renderer uses instanced quad rendering:
Per-sprite data (instance buffer):
┌──────────────────────────────────────┐
│ position (vec2) │ size (vec2) │
│ color (vec4) │ uv_offset (vec2) │
└──────────────────────────────────────┘
Vertex shader:
1. Read per-vertex quad position (4 vertices)
2. Scale by instance size
3. Translate by instance position
4. Transform by view-projection uniform
Fragment shader:
1. Sample texture (or use solid color)
2. Multiply by instance color
3. Output final color
SpriteBatch
SpriteBatch collects draw calls each frame and uploads them as a single instance buffer:
batch.begin();
for (transform, sprite) in query.iter(&world) {
batch.draw_quad(transform.position, size, sprite.color);
}
batch.upload(&device); // Single buffer upload
batch.draw(&render_pass); // Single draw call
This batches hundreds or thousands of sprites into one GPU draw call.
3D Rendering
The 3D pipeline adds:
- Depth buffer: 32-bit float depth texture for correct occlusion
- Camera: Perspective projection with orbit controls
- Lighting: Directional light with diffuse shading (Blinn-Phong)
- Vertex format: Position, normal, color (extensible to UV, tangent)
Voxel LOD
Voxel terrain uses a demand-streamed, render-distance-aware LOD hierarchy for distant chunks. The current view requests only the LOD levels that can be drawn or prewarmed near a transition boundary; the engine does not eagerly build every resolution for every dirty subchunk. The downsampler evaluates exact block IDs and metadata instead of reducing IDs to a small palette bucket, then applies a visual-salience score so tiny but important features survive at distance: emissive blocks keep skyline and cave glows, water and translucent blocks keep readable shorelines, foliage keeps canopy mass, and high-ID biome materials remain distinct.
The resulting LOD cells expand back into normal chunk-local coordinates before greedy meshing, so the shader and draw paths can choose the highest resident LOD without a special transform. Coarse meshes sample neighbor borders at the same LOD resolution before meshing, which removes faces that would otherwise become distance seams. Both opaque and transparent LOD streams are uploaded, so distant water and glass continue through the same material path instead of being dropped when terrain coarsens.
LOD band boundaries also feed a per-subchunk fade value into the voxel shader. The fragment path uses that value as a world-anchored stochastic haze mask rather than alpha-discarding terrain, which hides mesh-resolution swaps without opening holes in distant mountains or forests.
Adaptive quality can still shift LOD bands under sustained GPU/CPU pressure, but those shifts are hysteretic and rate-limited. The renderer does not move distance bands every frame from noisy frame-time samples, because that reads as background terrain flicker even when the player is standing still.
Water And Underwater Lighting
Water surfaces use animated procedural wave normals for sky reflection, sun glints, aurora reflection, and transparent fog. When the camera is submerged, opaque voxel shading switches to an underwater volume model instead of the standard aerial perspective: warm wavelengths are absorbed with depth, suspended-particle turbidity brightens mid-distance fog, and directional sun shafts appear when the view ray aligns with exposed sunlight through the water surface. The particle system adds capped, drifting plankton/silt motes around the submerged camera so those shafts and turbidity have visible matter in the water column. Caustics remain a local surface effect on upward-facing geometry, so nearby floors sparkle while distant water still disappears into blue-green depth haze.
3D Pipeline Setup
Vertex Buffer → Vertex Shader → Rasterizer → Fragment Shader → Depth Test → Output
↑ ↑
View-Projection Light Direction
Uniform Uniform
Camera System
Camera2D
Orthographic projection for 2D rendering:
- Origin at top-left (screen coordinates)
- View-projection matrix maps pixel coordinates to clip space
- Supports panning and zoom
Camera3D (scene3d demo)
Perspective projection for 3D rendering:
- Configurable FOV, near/far planes
- Orbit camera: rotates around a target point
- View matrix from eye position, target, and up vector
Shader Architecture
All shaders are written in WGSL (WebGPU Shading Language):
crates/sg_render/ → shared pipeline utilities
demos/sprite-lab/ → sprite.wgsl (instanced 2D quads)
demos/scene3d/ → scene3d.wgsl (lit 3D geometry)
Shaders are included at compile time via include_str!() and compiled to GPU shader modules at runtime.
Render Pass Structure
Each frame executes one render pass:
Begin Render Pass (clear color + depth)
├─ Set pipeline
├─ Set bind groups (uniforms)
├─ Set vertex/index buffers
├─ Draw (instanced for sprites, indexed for meshes)
└─ End render pass
Submit command buffer
Present surface texture
Future frames may use multiple render passes for effects like post-processing or shadow maps.
Performance Considerations
- Batching: Sprites are batched to minimize draw calls
- Buffer reuse: Instance buffers are pre-allocated and reused each frame
- Minimal state changes: Pipeline and bind group switches are minimized
- WebGL2 fallback: Some features (compute shaders, storage textures) are unavailable on WebGL2; the engine detects capabilities at init time and uses WebGL2 only when hardware WebGPU is unavailable or unsuitable