Skip to main content

Visual Effects

Hyperscape uses GPU-driven visual effects for resource depletion, respawn animations, and environmental feedback. All effects use TSL (Three Shading Language) shaders that require WebGPU.
Visual effects code lives in packages/shared/src/systems/shared/world/ and uses the DissolveAnimation state machine for smooth transitions.

Tree Dissolve Transparency (March 2026)

New Feature (PR #1101): Depleted trees use screen-door dithering to become ~70% transparent instantly on depletion, then animate back to full opacity over 0.3s on respawn.

Overview

The tree dissolve system provides visual feedback for resource depletion and respawn:
  • Instant Depletion: Tree becomes 70% transparent immediately when depleted
  • Smooth Respawn: Animates from transparent to opaque over 0.3 seconds
  • Performance: Uses screen-door dithering in opaque render pass (no alpha blending overhead)
  • LOD Preservation: Dissolve state carries over during LOD transitions (no visual pops)

Implementation

Dual Encoding Strategy: The system supports both InstancedMesh and BatchedMesh rendering: Shared Animation Module: packages/shared/src/systems/shared/world/DissolveAnimation.ts provides the state machine:
Animation State:

Configuration

Dissolve timing is configured in GPU_VEG_CONFIG:
Configuration Notes:
  • DISSOLVE_DURATION: Animation duration in seconds (0.3s = ~18 frames at 60fps)
  • DISSOLVE_MAX: Animation progress ceiling (always 1.0)
  • DISSOLVE_ALPHA_SCALE: Fraction of fragments discarded via dithering (0.7 = 70% transparent)
Precision Limit: BatchedMesh encodes dissolve in a Uint8 blue channel (~256 levels). At 0.3s duration this provides smooth animation. Increasing DISSOLVE_DURATION significantly (>2s) may cause visible banding. InstancedMesh uses Float32 and has no precision limit.

Shader Implementation

The dissolve effect uses screen-door dithering in the alphaTestNode:
Dithering Pattern: Uses Bayer 4×4 matrix for screen-door transparency:
Fragments are discarded proportional to dissolveVal, creating a stippled transparency effect that keeps trees in the opaque render pass with full early-Z rejection.

Performance Benefits

Opaque Pass Preservation:
  • Trees stay in opaque render pass (no transparency sorting)
  • Full early-Z rejection (fragments behind opaque geometry are culled)
  • No fill-rate cost from alpha blending
  • Consistent performance regardless of dissolve state
Batch Color Channel Layout (BatchedMesh):
  • R channel: Highlight intensity (1.0 = normal, >1.0 = highlighted)
  • G channel: Highlight intensity (same as R)
  • B channel: 1.0 - dissolveVal (1.0 = fully visible, 0.0 = fully dissolved)
The blue channel is reserved for dissolve state. Any code that sets batch colors must preserve the blue channel value or use the applyHighlightColor() helper which reads-modifies-writes correctly.

LOD Transition Handling

Dissolve state is preserved when trees swap between LOD levels:
This prevents visual pops when a dissolving tree transitions between LOD0/LOD1/LOD2.

API Reference

GLBTreeInstancer / GLBTreeBatchedInstancer:
DissolveAnimation Module:

Usage Example


Tree Collision Proxy (March 2026)

Improvement (PR #1100): Tree collision detection now uses actual LOD2 model geometry instead of oversized cylinders.

Problem

The old system used invisible cylinder hitboxes with 0.4 radius factor, which were much larger than the visible tree silhouette. Ground clicks near trees were being intercepted by the collision proxy instead of registering as ground clicks.

Solution

Replace cylinder with actual LOD2 mesh geometry so clicks only register on the visible tree silhouette:

Geometry Caching

Proxy geometries are cached per (sourceGeometries, scale) to avoid redundant merges:
Cache Features:
  • Float Key Safety: Scale rounded to 3 decimal places to prevent floating-point cache misses
  • Multi-Part Merging: Combines bark and leaves into single proxy mesh
  • Defensive Bounding Box: Pre-computes boundingBox and boundingSphere to prevent lazy mutation by Three.js raycaster
  • Memory Management: Cache cleared during world teardown to prevent GPU buffer leaks

API Reference

GLBTreeInstancer / GLBTreeBatchedInstancer:

Benefits

  • Accurate Click Detection: Clicks only register on visible tree silhouette
  • No Ground Click Interception: Ground clicks near trees work correctly
  • Memory Efficient: Cached geometry shared across all trees with same model+scale
  • Graceful Fallback: Uses tighter cylinder (0.25 radius) if LOD unavailable

Screen-Door Dithering

Screen-door dithering is a technique that creates transparency effects while keeping geometry in the opaque render pass:

How It Works

Instead of using alpha blending (which requires transparency sorting and disables early-Z), fragments are discarded based on a dither pattern:

Benefits

Visual Quality: At 0.3s animation duration (60fps = ~18 frames), the dithering pattern is barely noticeable and provides smooth visual feedback without the performance cost of true transparency.

GPU Vegetation Config

All vegetation visual effects are configured via GPU_VEG_CONFIG:

Distance Fade

Trees automatically fade out at distance using the same Bayer dithering pattern:
  • FADE_START (40m): Trees begin to fade
  • FADE_END (60m): Trees fully dissolved (culled)
This provides smooth LOD transitions without pop-in artifacts.

Near Camera Fade

Trees near the camera fade to prevent clipping through the viewport:
  • NEAR_CAMERA_FADE_START (0.1m): Begin fade
  • NEAR_CAMERA_FADE_END (0.05m): Fully transparent