Three.js技术问询:如何在鼠标区域周边应用着色器效果(参考activetheory.net首页)
Hey there! I’ve spent some time digging into effects like the one on activetheory.net, and here’s a step-by-step breakdown of how to build something similar with Three.js.
Core Concept
The effect you’re seeing is a post-processing shader—it takes the entire rendered scene, applies a dynamic filter based on your mouse position, and outputs the final frame. The key pieces are:
- Tracking mouse position and passing it to the shader
- Calculating the distance from each pixel to the mouse in the fragment shader
- Applying a localized effect (blur, color shift, distortion) that fades out as you move away from the mouse
Step-by-Step Implementation
1. Set Up the Basic Three.js Scene
First, get your core scene, camera, and renderer running. Nothing fancy here—just the foundation:
import * as THREE from 'three'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; // Scene setup const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Add some test content (replace with your own scene) const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); const cube = new THREE.Mesh(geometry, material); scene.add(cube); camera.position.z = 5;
2. Configure Post-Processing Pipeline
This is where the magic happens. We’ll use EffectComposer to chain render passes:
// Create composer and render pass (renders scene to a texture) const composer = new EffectComposer(renderer); const renderPass = new RenderPass(scene, camera); composer.addPass(renderPass); // Custom shader pass for mouse effect const mouseEffectShader = { uniforms: { tDiffuse: { value: null }, // The rendered scene texture mouse: { value: new THREE.Vector2(0, 0) }, // Mouse position in NDC (-1 to 1) effectRadius: { value: 0.3 }, // Size of the effect area effectStrength: { value: 2.0 } // Intensity of the effect }, vertexShader: ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform sampler2D tDiffuse; uniform vec2 mouse; uniform float effectRadius; uniform float effectStrength; varying vec2 vUv; void main() { // Calculate distance from current pixel to mouse float distance = length(vUv - mouse); // Smoothly fade the effect based on distance float strength = smoothstep(effectRadius, 0.0, distance) * effectStrength; // Example effect: Blur + color shift vec2 offset = (vUv - mouse) * strength * 0.01; vec4 color = texture2D(tDiffuse, vUv); vec4 blur1 = texture2D(tDiffuse, vUv + offset); vec4 blur2 = texture2D(tDiffuse, vUv - offset); vec4 finalColor = mix(color, (blur1 + blur2) * 0.5, strength); // Optional: Add a color tint to the effect area finalColor.r += strength * 0.1; finalColor.b -= strength * 0.05; gl_FragColor = finalColor; } ` }; const mouseEffectPass = new ShaderPass(mouseEffectShader); composer.addPass(mouseEffectPass);
3. Track Mouse Position (Convert to NDC)
Screen coordinates (pixels) need to be converted to Normalized Device Coordinates (range: -1 to 1 for both axes) so the shader can use them correctly:
let mouse = new THREE.Vector2(0, 0); window.addEventListener('mousemove', (event) => { // Convert screen position to NDC mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; // Update the shader uniform mouseEffectPass.uniforms.mouse.value = mouse; });
4. Update the Render Loop
Instead of rendering directly with renderer.render(), use the composer to handle post-processing:
function animate() { requestAnimationFrame(animate); // Rotate cube for test motion cube.rotation.x += 0.01; cube.rotation.y += 0.01; // Render with post-processing composer.render(); } animate(); // Handle window resizing window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); composer.setSize(window.innerWidth, window.innerHeight); });
Extra Tips for Polish
- Mouse Easing: Add smooth interpolation to the mouse position so the effect follows the mouse more naturally (instead of jumping instantly). Example:
let targetMouse = new THREE.Vector2(0, 0); let currentMouse = new THREE.Vector2(0, 0); window.addEventListener('mousemove', (event) => { targetMouse.x = (event.clientX / window.innerWidth) * 2 - 1; targetMouse.y = -(event.clientY / window.innerHeight) * 2 + 1; }); // In animate loop: currentMouse.lerp(targetMouse, 0.1); // 0.1 = easing speed mouseEffectPass.uniforms.mouse.value = currentMouse; - Adjust Effect Parameters: Play with
effectRadiusandeffectStrengthto match the feel you want. You can even add GUI controls to tweak these in real-time. - Advanced Effects: For the exact activetheory.net look, experiment with:
- Gaussian blur (more samples for smoother blur)
- Chromatic aberration (split RGB channels based on distance)
- Distortion (warp the texture around the mouse)
Troubleshooting
- If the effect isn’t showing up, double-check that you’re using the latest Three.js post-processing addons (they’re in
three/addons/postprocessing/now). - Make sure your shader uniforms are being updated correctly—console log the mouse values to verify.
内容的提问来源于stack exchange,提问作者Michaël

