Rendering Overview
Web Engine's high-performance rendering system built on Three.js with WebGL/WebGPU support, advanced culling, and modern graphics features.
The rendering system provides a production-grade graphics pipeline with automatic optimization, GPU-driven rendering, and support for modern graphics APIs. Built on Three.js, it combines ease of use with high-performance rendering capabilities.
Key Features#
WebGL & WebGPU
Dual renderer support with automatic fallback. WebGPU for modern devices, WebGL for compatibility.
GPU Culling
Frustum and occlusion culling executed on GPU for maximum throughput with 100k+ entities.
Render Graph
Multi-frame buffering with automatic pass scheduling and resource management.
Material Instancing
Automatic material sharing and batching to reduce draw calls and state changes.
LOD System
Automatic level-of-detail switching for meshes and instances based on distance.
Post-Processing
HDR bloom, tone mapping, SSAO, depth of field, and custom effect chains.
Architecture#
The rendering system is built on a modern, performance-focused architecture with several key components working together:
Renderer Factory#
The renderer factory creates WebGL or WebGPU renderers with optimal settings based on device capabilities:
import { createRenderer, isWebGPUSupported } from '@web-engine-dev/core/engine/rendering'; // Check WebGPU supportconst hasWebGPU = await isWebGPUSupported(); // Create renderer with automatic backend selectionconst renderer = createRenderer( hasWebGPU ? 'webgpu' : 'webgl', canvasElement, { antialias: true, powerPreference: 'high-performance', alpha: false, stencil: true, });Scene Renderer#
The SceneRenderer orchestrates the entire rendering pipeline, executing passes in the optimal order:
import { getSceneRenderer } from '@web-engine-dev/core/engine/rendering'; // Initialize scene rendererconst sceneRenderer = getSceneRenderer({ enableShadows: true, enableDepthPrepass: true, enableGPUCulling: true, enablePostProcessing: true,}); // In your game loopfunction gameLoop(world, delta, time) { // Render frame with all optimizations sceneRenderer.render(world, delta, time); // Get performance stats const stats = sceneRenderer.getStats(); console.log(`Frame: ${stats.frameTime.toFixed(2)}ms`);}Render Graph#
The render graph manages pass execution with automatic dependency resolution and multi-frame buffering:
import { getRenderGraph } from '@web-engine-dev/core/engine/rendering'; const renderGraph = getRenderGraph(); // Render graph compiles passes in dependency order:// 1. Shadow Pass (produces ShadowMap)// 2. Depth Prepass (produces depth buffer)// 3. Opaque Pass (uses ShadowMap + depth)// 4. Transparent Pass (uses depth for sorting)// 5. Post-Process Pass (uses final color buffer) // Triple buffering: GPU executes frame N while CPU builds N+1renderGraph.beginFrame(world, delta, time);// ... add passes ...renderGraph.endFrame();renderGraph.submitReady();Rendering Pipeline#
The engine executes a series of render passes each frame, optimized for performance and visual quality:
| Pass | Purpose | Output |
|---|---|---|
| Shadow Pass | Render cascaded shadow maps for directional lights | Shadow depth textures |
| Depth Prepass | Early-Z rejection to reduce overdraw | Depth buffer + Hi-Z pyramid |
| Opaque Pass | Render solid objects front-to-back | Color + depth buffer |
| Transparent Pass | Render transparent objects back-to-front | Blended color buffer |
| Post-Process Pass | Apply bloom, tone mapping, effects | Final screen image |
Three.js Integration#
The engine seamlessly integrates with Three.js, allowing you to use any Three.js feature while benefiting from automatic optimization:
Scene Graph Synchronization#
The RenderSystem automatically syncs ECS entities to Three.js scene objects:
// ECS components are automatically synced to Three.jsconst entity = createEntity(world);addComponent(world, entity, Transform);addComponent(world, entity, MeshRenderer); // Transform changes sync to Three.js Object3DTransform.position[entity][0] = 10;Transform.position[entity][1] = 5;Transform.position[entity][2] = 0; // RenderSystem updates Three.js mesh.position automatically// No manual synchronization needed!Custom Three.js Materials#
You can use any Three.js material, including custom shaders:
import * as THREE from 'three';import { MaterialFactory } from '@web-engine-dev/core/engine/materials'; // Use built-in materialsconst standardMat = new THREE.MeshStandardMaterial({ color: 0x00ff00, roughness: 0.5, metalness: 0.8,}); // Or create custom shader materialsconst customMat = new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 }, uColor: { value: new THREE.Color(0xff0000) }, }, vertexShader: /* glsl */ ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: /* glsl */ ` uniform float uTime; uniform vec3 uColor; varying vec2 vUv; void main() { float pulse = sin(uTime * 2.0) * 0.5 + 0.5; gl_FragColor = vec4(uColor * pulse, 1.0); } `,});Performance Targets#
The rendering system is optimized to meet aggressive performance targets:
| Metric | Target | Notes |
|---|---|---|
| Frame time @ 1080p | < 8ms | Allows headroom for 60 FPS |
| Draw calls | < 500 | Through batching and instancing |
| State changes | < 100 | Material and shader reuse |
| GPU memory | < 1 GB | Texture streaming and pooling |
| Culling time | < 0.5ms | GPU frustum + occlusion |
| Entities rendered | 100k+ | With GPU instancing |
Performance Monitoring
Use sceneRenderer.getStats() to monitor frame time, draw calls, and culling efficiency. The engine automatically adjusts LOD and culling settings based on performance.
WebGPU vs WebGL#
The engine supports both rendering backends with automatic fallback:
| Feature | WebGPU | WebGL |
|---|---|---|
| Browser Support | Chrome 113+, Edge 113+ | All modern browsers |
| Compute Shaders | Yes | Limited (transform feedback) |
| GPU Instancing | Unlimited instances | Limited by uniforms |
| Texture Arrays | 16k layers | Limited support |
| Performance | 10-30% faster | Baseline |
| Validation | Excellent errors | Limited errors |
Automatic Backend Selection
The engine automatically selects WebGPU when available and falls back to WebGL for maximum compatibility. Both backends share the same API, so your code works identically on either.
Best Practices#
Reduce Draw Calls#
- Use instanced rendering for repeated objects (trees, rocks, etc.)
- Share materials between meshes when possible
- Enable automatic batching for static geometry
- Use texture atlases instead of individual textures
Optimize Materials#
- Reuse material instances instead of creating new ones
- Use MeshBasicMaterial for unlit objects
- Disable features you don't need (e.g., transparent: false)
- Compress textures using KTX2/Basis Universal
Leverage LOD#
- Create multiple detail levels for complex meshes
- Use impostor billboards for distant objects
- Configure LOD thresholds based on object importance
- Test LOD transitions to avoid pop-in
Manage GPU Memory#
- Dispose unused geometries and materials
- Use texture compression (KTX2, DDS)
- Stream textures for large worlds
- Monitor memory with renderer.info.memory
Mobile Considerations
Mobile GPUs have less memory and bandwidth. Use lower resolution textures, reduce shadow map size, disable post-processing on low-end devices, and prefer simpler materials.
Next Steps#
Now that you understand the rendering architecture, explore specific topics: