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>
160 lines
4.8 KiB
JavaScript
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');
|
|
})();
|