Skip to main content

Arena Rendering Optimizations

In February 2026, the duel arena rendering system was completely overhauled to eliminate performance bottlenecks. This document explains the optimizations and their impact.

Performance Improvements

Before vs After

Key Changes

  1. InstancedMesh Conversion - 846 individual meshes → 20 instanced draw calls
  2. PointLight Removal - 28 CPU-animated lights → GPU-driven TSL emissive materials
  3. Fire Particle Rewrite - Enhanced shader with value noise and turbulent motion
  4. Dead Code Removal - Unused functions deleted (createArenaMarker, createAmbientDust, createLobbyBenches)

InstancedMesh Architecture

What is InstancedMesh?

InstancedMesh renders many copies of the same geometry with a single draw call. Each instance can have a unique position, rotation, and scale. Benefits:
  • Fewer draw calls - GPU state changes are expensive
  • Shared geometry - One buffer for all instances
  • Shared material - One shader compilation
  • GPU-friendly - Instancing is a native GPU feature

Arena Instancing Breakdown

Why Some Meshes Aren’t Instanced

Arena Floors (6 individual meshes):
  • Need unique userData.arenaId for raycasting
  • Need layer 0+2 for click-to-move and minimap
  • Sharing one geometry + material is still efficient
Forfeit Pillars (12 individual meshes):
  • Need unique userData.entityId for interaction system
  • Each pillar is a clickable entity
Banner Cloths (12 individual meshes):
  • Only 3 unique colors (4 meshes per material)
  • Already batched by material

TSL Emissive Materials

Replacing PointLights

Problem: 28 PointLights forced expensive per-pixel lighting calculations every frame. Solution: GPU-driven TSL emissive materials with animated flicker.

Brazier Glow Material

Implementation: packages/shared/src/systems/client/DuelArenaVisualsSystem.ts
Key Features:
  • Per-instance phase: Each brazier flickers independently
  • GPU-driven: Zero CPU cost per frame
  • Realistic flicker: Multi-frequency sine + noise matches old PointLight behavior
  • Directional glow: Only top face emits light (fire opening)

Performance Impact

Before (28 PointLights):
  • CPU: Update 28 light intensities every frame
  • GPU: 28 per-pixel lighting passes on surrounding geometry
  • Memory: 28 light objects + shadow maps
After (TSL emissive):
  • CPU: Update one time uniform per frame
  • GPU: Emissive calculation in fragment shader (already running)
  • Memory: One material + one uniform
Result: ~80% CPU reduction, no per-pixel lighting overhead.

Fire Particle Shader Rewrite

Enhanced Fire Preset

The fire particle preset was rewritten with a new fragment shader for better visual quality and additive blending. Old Shader (simple radial glow):
New Shader (value noise + soft falloff):
Improvements:
  • Soft falloff: No hard edges, particles blend smoothly
  • Value noise: Organic flame shapes (not uniform circles)
  • Scrolling noise: Upward motion feel
  • Color gradient: Bright white-yellow core → orange-red tips
  • Additive-friendly: Designed for overlapping particles to merge

Turbulent Vertex Motion

Particles now have per-particle turbulent motion for natural flame flickering:
Effect: Particles jitter and sway like real flames, not just rising straight up.

Torch Preset Removed

The torch preset was removed and unified with the enhanced fire preset. Migration:

TSL Procedural Materials

Sandstone Block Pattern

Arena fences use a GPU-computed sandstone block pattern with:
  • Running bond layout - Offset rows for realistic masonry
  • Per-block color variation - Warm sandstone range (0.62-0.72 R, 0.52-0.60 G, 0.38-0.46 B)
  • Mortar grooves - Dark earth brown (0.35, 0.28, 0.2)
  • Bevel effect - Blocks appear raised with edge darkening
  • Surface grain - Fine noise texture for stone detail
World-Space UVs: Uses positionWorld.xz for horizontal and positionWorld.y for vertical, ensuring seamless tiling on any wall orientation.

Floor Tile Pattern

Arena floors use a square flagstone pattern with:
  • 1.2m tiles - Large flagstones with thin grout lines
  • Per-tile color variation - Sand-earth range (0.68-0.80 R, 0.54-0.64 G, 0.36-0.44 B)
  • Grout lines - Dark grout (0.4, 0.32, 0.22)
  • Bevel effect - Subtle tile edge darkening
  • Surface grain - Noise texture for worn stone
World-Space UVs: Uses positionWorld.xz so each arena looks unique despite sharing the same material.

Code Structure

File Organization

Main File: packages/shared/src/systems/client/DuelArenaVisualsSystem.ts Key Methods:
  • createSharedMaterials() - Creates all TSL materials once
  • buildFenceInstances() - Builds fence InstancedMesh (posts, caps, rails)
  • buildPillarInstances() - Builds pillar InstancedMesh (bases, shafts, capitals)
  • buildBrazierInstances() - Builds brazier InstancedMesh with TSL glow
  • buildBorderInstances() - Builds floor border InstancedMesh
  • buildBannerPoleInstances() - Builds banner pole InstancedMesh
  • createArenaFloors() - Creates individual floor meshes (need unique userData)
  • createForfeitPillars() - Creates individual forfeit pillars (need unique entityId)
  • createBannerCloths() - Creates individual banner cloths (3 materials)

Material Caching

All materials are created once and shared:
Benefits:
  • One shader compilation per material
  • Shared GPU resources
  • Easier cleanup (dispose once)

Performance Metrics

Draw Call Reduction

Before:
After:

Lighting Overhead Elimination

Before (28 PointLights):
After (TSL emissive):
Result: No per-pixel lighting calculations, no CPU light updates.

Visual Quality Improvements

Fire Particles

Old Fire:
  • Simple radial glow (uniform circles)
  • Hard edges (visible particle boundaries)
  • Straight upward motion (no turbulence)
  • Uniform color (no gradient)
New Fire:
  • Value noise (organic flame shapes)
  • Soft falloff (smooth blending)
  • Turbulent motion (visible flicker and sway)
  • Color gradient (white-yellow core → orange-red tips)
  • Scrolling noise (upward motion feel)
Particle Count: Increased from 18 to 28 per emitter (more particles, zero CPU cost).

Brazier Glow

Old Braziers:
  • Static emissive material (no animation)
  • PointLight for glow (expensive)
  • Uniform brightness (no flicker)
New Braziers:
  • Animated TSL emissive (GPU-driven flicker)
  • No PointLight (zero lighting cost)
  • Multi-frequency flicker (realistic fire)
  • Per-instance phase offset (each brazier unique)

Implementation Guide

Creating InstancedMesh

Creating TSL Emissive Material

Migration Guide

Updating Existing Arena Code

If you have custom arena modifications, update them to use InstancedMesh: Before:
After:

Replacing PointLights with TSL Emissive

Before:
After:

Debugging

Verify Instancing

Verify TSL Emissive

Performance Profiling

See Also