spaxel/dashboard/js/fxaa.js
jedarden 46b34954f5 feat: use FXAA instead of MSAA on mobile devices
On screens < 1024px width, use FXAA (Fast Approximate Anti-Aliasing)
instead of MSAA for better performance on mobile devices.

Changes:
- Add dashboard/js/fxaa.js: FXAA post-processing module using Three.js
  EffectComposer with FXAA shader pass
- Modify renderer initialization to disable MSAA on mobile
- Initialize FXAA composer on mobile devices after scene setup
- Use FXAA render path in animation loop when active
- Update FXAA resolution on window resize

FXAA provides a good balance between visual quality and performance
on mobile GPUs where MSAA can be prohibitively expensive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 03:33:29 -04:00

160 lines
4.8 KiB
JavaScript

/**
* Spaxel Dashboard - FXAA Post-Processing Module
*
* Fast Approximate Anti-Aliasing for mobile devices.
* Uses Three.js EffectComposer with FXAA shader pass.
* Only active on screens < 1024px width (mobile devices).
*/
(function() {
'use strict';
// ============================================
// State
// ============================================
let composer = null;
let fxaaPass = null;
let renderPass = null;
let scene = null;
let camera = null;
let renderer = null;
let isInitialized = false;
let isActive = false;
/**
* Detect if the current device is a mobile device based on screen width.
* @returns {boolean} True if screen width < 1024px (mobile)
*/
function isMobile() {
return window.innerWidth < 1024;
}
/**
* Initialize the FXAA composer.
* @param {THREE.Scene} sceneRef - The Three.js scene
* @param {THREE.Camera} cameraRef - The Three.js camera
* @param {THREE.WebGLRenderer} rendererRef - The Three.js renderer
*/
async function init(sceneRef, cameraRef, rendererRef) {
scene = sceneRef;
camera = cameraRef;
renderer = rendererRef;
// Only initialize FXAA on mobile devices
if (!isMobile()) {
console.log('[FXAA] Desktop detected, skipping FXAA initialization (MSAA will be used)');
isActive = false;
return;
}
// Dynamic import of post-processing modules
try {
const module = await import('three/examples/jsm/postprocessing/EffectComposer.js');
const EffectComposer = module.EffectComposer;
const renderPassModule = await import('three/examples/jsm/postprocessing/RenderPass.js');
const RenderPass = renderPassModule.RenderPass;
const shaderPassModule = await import('three/examples/jsm/postprocessing/ShaderPass.js');
const ShaderPass = shaderPassModule.ShaderPass;
const fxaaShaderModule = await import('three/examples/jsm/shaders/FXAAShader.js');
const FXAAShader = fxaaShaderModule.FXAAShader;
// Create render pass
renderPass = new RenderPass(scene, camera);
// Create FXAA pass
fxaaPass = new ShaderPass(FXAAShader);
// Set FXAA resolution (must be updated on resize)
const width = window.innerWidth * window.devicePixelRatio;
const height = window.innerHeight * window.devicePixelRatio;
fxaaPass.uniforms['resolution'].value.x = 1 / width;
fxaaPass.uniforms['resolution'].value.y = 1 / height;
// Create composer
composer = new EffectComposer(renderer);
composer.addPass(renderPass);
composer.addPass(fxaaPass);
isInitialized = true;
isActive = true;
console.log('[FXAA] Initialized for mobile device');
} catch (error) {
console.error('[FXAA] Failed to initialize:', error);
isActive = false;
}
}
/**
* Render the scene using FXAA composer (if active) or direct renderer (if not).
*/
function render() {
if (isActive && composer && isInitialized) {
composer.render();
}
}
/**
* Update the FXAA resolution after window resize.
*/
function updateResolution() {
if (!isActive || !fxaaPass || !isInitialized) {
return;
}
const width = window.innerWidth * window.devicePixelRatio;
const height = window.innerHeight * window.devicePixelRatio;
fxaaPass.uniforms['resolution'].value.x = 1 / width;
fxaaPass.uniforms['resolution'].value.y = 1 / height;
// Also update composer size
if (composer) {
composer.setSize(window.innerWidth, window.innerHeight);
}
}
/**
* Check if FXAA is currently active.
* @returns {boolean} True if FXAA is active
*/
function getIsActive() {
return isActive;
}
/**
* Clean up resources.
*/
function dispose() {
if (composer) {
composer.dispose();
composer = null;
}
if (renderPass) {
renderPass.dispose();
renderPass = null;
}
if (fxaaPass) {
fxaaPass.dispose();
fxaaPass = null;
}
isInitialized = false;
isActive = false;
console.log('[FXAA] Disposed');
}
// ============================================
// Public API
// ============================================
window.SpaxelFXAA = {
init: init,
render: render,
updateResolution: updateResolution,
isActive: getIsActive,
dispose: dispose
};
console.log('[FXAA] Module loaded');
})();