如何使用着色器控制Three.js场景的背景颜色?
如何用片元着色器控制Three.js场景背景色
renderer.setClearColor() 和 scene.background 是Three.js提供的底层清屏/背景设置API,它们只接受静态颜色或纹理,不支持直接传入GLSL代码。要实现自定义着色器背景,你需要通过以下两种常用方式模拟:
方法1:全屏着色器平面(简单场景首选)
创建一个覆盖整个视口的平面几何体,使用自定义ShaderMaterial渲染,将其置于场景最底层(不参与深度测试),以此替代默认背景。
完整实现代码
import * as THREE from 'three'; // 基础场景初始化 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); // 禁用默认不透明清屏,避免覆盖着色器背景 renderer.setClearColor(0x000000, 0); // 1. 定义着色器代码 const vertexShader = ` void main() { gl_Position = vec4(position, 1.0); } `; // 自定义片元着色器(这里可以写任意GLSL逻辑,比如动态渐变) const fragmentShader = ` uniform vec2 resolution; uniform float time; void main() { // 计算屏幕UV坐标 vec2 uv = gl_FragCoord.xy / resolution; // 生成随时间变化的RGB渐变 vec3 color = vec3(uv.x, uv.y, sin(time) * 0.5 + 0.5); gl_FragColor = vec4(color, 1.0); } `; // 2. 创建全屏几何体(覆盖NDC空间,无需相机投影计算) const geometry = new THREE.BufferGeometry(); const vertices = new Float32Array([ -1.0, -1.0, 0.0, 1.0, -1.0, 0.0, -1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0 ]); geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3)); // 3. 创建着色器材质 const material = new THREE.ShaderMaterial({ vertexShader, fragmentShader, uniforms: { resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }, time: { value: 0 } }, depthTest: false, // 不参与深度测试,避免遮挡场景物体 depthWrite: false // 不写入深度缓冲区 }); // 4. 添加背景平面到场景 const backgroundPlane = new THREE.Mesh(geometry, material); scene.add(backgroundPlane); // 添加测试物体验证层级 const cube = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0xff0000 }) ); scene.add(cube); camera.position.z = 5; // 动画循环 function animate() { requestAnimationFrame(animate); // 更新时间uniform material.uniforms.time.value += 0.01; // 实时更新分辨率(窗口resize时同步) material.uniforms.resolution.value.set(window.innerWidth, window.innerHeight); renderer.render(scene, camera); } animate(); // 窗口resize处理 window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); });
方法2:后期处理ShaderPass(复杂场景/已有后期管线首选)
如果你的项目已经使用Three.js的后期处理管线(EffectComposer),可以添加一个自定义ShaderPass作为背景层,再渲染场景内容。
完整实现代码
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'; // 基础场景初始化 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); // 添加测试物体 const cube = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0xff0000 }) ); scene.add(cube); camera.position.z = 5; // 1. 定义背景着色器 const backgroundShader = { uniforms: { resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }, time: { value: 0 } }, vertexShader: ` varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 1.0); } `, fragmentShader: ` uniform vec2 resolution; uniform float time; varying vec2 vUv; void main() { vec3 color = vec3(vUv.x, vUv.y, cos(time) * 0.5 + 0.5); gl_FragColor = vec4(color, 1.0); } ` }; // 2. 初始化后期管线 const composer = new EffectComposer(renderer); // 添加背景着色器Pass(先渲染背景) const backgroundPass = new ShaderPass(backgroundShader); backgroundPass.renderToScreen = false; composer.addPass(backgroundPass); // 添加场景渲染Pass(关闭清屏,保留背景) const renderPass = new RenderPass(scene, camera); renderPass.clear = false; composer.addPass(renderPass); // 动画循环 function animate() { requestAnimationFrame(animate); backgroundShader.uniforms.time.value += 0.01; backgroundShader.uniforms.resolution.value.set(window.innerWidth, window.innerHeight); composer.render(); } animate(); // 窗口resize处理 window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); composer.setSize(window.innerWidth, window.innerHeight); });
关键注意事项
- 保持
scene.background为默认的null,并将renderer.setClearColor的alpha设为0,避免默认清屏覆盖着色器背景。 - 全屏平面必须设置
depthTest: false和depthWrite: false,确保场景物体能正常显示在背景之上。 - 如果需要背景和场景深度交互(比如雾效),可以在片元着色器中结合相机的深度信息进行计算。
内容的提问来源于stack exchange,提问作者Jonas
相关产品推荐
相关产品推荐

