You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用着色器控制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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 13:45:45