Skip to main content

Performant Three.js Animated Textures

·510 words·3 mins

Creating beautiful animated backgrounds with WebGL and Three.js can be visually stunning, but often comes with significant performance challenges, especially on mobile devices. In this article, we'll explore how to create high-performance animated gradient textures that maintain consistent frame rates without degradation over time.

The Performance Problem

Many WebGL animations suffer from common performance issues:

  • Per-pixel calculations - Computing complex patterns for every pixel on screen every frame
  • Memory leaks - Improper resource management leading to degraded performance over time
  • Inefficient shaders - Overly complex fragment shaders with redundant calculations
  • Mobile device limitations - Not accounting for the significant performance gap between desktop and mobile GPUs

We encountered these issues with our original animated gradient implementation. While it looked great initially, it would gradually slow down to unacceptable frame rates, especially on mobile devices. After extensive testing and optimization, we discovered several key techniques to dramatically improve performance.

The Two-Stage Rendering Approach

The most significant improvement came from adopting a two-stage rendering approach:

  1. Pre-render to texture - Render the complex gradient animation to a lower-resolution texture
  2. Display the texture - Map this texture onto a simple full-screen quad

This approach provides several key benefits:

  • Significantly fewer pixels to process (e.g., 512×512 vs. 1920×1080)
  • Reduced shader complexity per frame
  • Decoupled update rates (texture can update less frequently than display)
  • More efficient use of GPU resources

Adaptive Quality Based on Device

Another crucial optimization was implementing device-specific rendering settings:

SettingDesktopMobile
Texture Size512×512256×256
Pixel RatioDevice (max 2.0)60% of device (max 1.0)
Frame Rate Limit60 FPS30 FPS
Texture Updates30 per second15 per second
AntialiasingEnabledDisabled

These adaptive settings ensure smooth performance across a wide range of devices while maintaining visual quality where it matters most.

Optimized GLSL Shader

Our shader optimizations included:

  • Using mediump precision for better performance
  • Limiting the number of noise octaves (typically 2-3)
  • Reusing noise calculations rather than computing multiple independent layers
  • Avoiding expensive operations like pow(), exp(), etc.
  • Using efficient blending techniques with smoothstep()

Proper Memory Management

To prevent memory leaks and ensure stable long-term performance:

  • Implemented a comprehensive cleanup function that properly disposes of all Three.js resources
  • Added page visibility detection to pause rendering when the tab is inactive
  • Tracked and canceled animation frames when appropriate
  • Used named functions for event listeners to enable proper removal
  • Properly disposed of render targets, materials, geometries, and textures

Results

The performance improvements were dramatic:

  • Desktop: Stable 60 FPS indefinitely with minimal GPU usage
  • Mobile: Consistent 30 FPS with no degradation over time
  • Memory Usage: Flat memory profile even after hours of operation
  • Battery Impact: Significantly reduced power consumption

Conclusion

By implementing these techniques, we've created an animated gradient background that's both visually appealing and performant across all devices. The key takeaways are:

  1. Pre-render complex animations to textures whenever possible
  2. Adapt quality settings based on the device
  3. Optimize shader code to minimize unnecessary calculations
  4. Implement proper resource management
  5. Decouple update rates (animation, texture updates, display)

For those interested in the implementation details, the complete source code is available in our GitHub repository.

Reply by Email