Three.js 径向渐变背景实现教程
前言
在 Three.js 项目中,scene.background通常用来设置纯色背景或天空盒(CubeTexture / Equirectangular 环境贴图)。但有时候我们希望背景是一个从中心向四周辐射的径向渐变(比如深蓝到黑的科技感渐变),而不是单调的纯色。本文将介绍一种轻量高效的实现方案——用<canvas>生成渐变纹理,再通过THREE.CanvasTexture赋给scene.background。
效果预览
一、核心原理
整体流程只有三步:
- 用
<canvas>的createRadialGradientAPI 画出渐变图案。 - 将 canvas 包装为
THREE.CanvasTexture,并设置colorSpace = THREE.SRGBColorSpace保证颜色正确。 - 将纹理赋给
scene.background,Three.js 会自动将其拉伸铺满视口。
为什么不用 Shader 背景?Canvas 方案无需额外编写着色器、不依赖后处理通道,兼容性好,适合快速集成。
二、完整代码实现
2.1 基础版本(可直接复制使用)
/** * Three.js 径向渐变背景管理器 * 使用方式: * 1. 确保 renderer 已创建且 scene 已初始化 * 2. const gradientBg = new GradientBackground(scene, renderer); * 3. gradientBg.setColors('#1a1a2e', '#0f0f1a'); // 内侧色, 外侧色 * 4. gradientBg.apply(); */classGradientBackground{constructor(scene,renderer){this.scene=scene;this.renderer=renderer;// 渐变颜色this.innerColor='#1a1a2e';// 中心颜色(深蓝紫)this.outerColor='#0f0f1a';// 边缘颜色(近黑)// 内部状态this._texture=null;this._canvas=null;this._isActive=false;// 用于恢复的背景引用this._previousBackground=null;this._previousClearColor=null;this._previousClearAlpha=null;}// ---------- 构建渐变纹理 ----------_buildTexture(){// 销毁旧纹理,避免 GPU 内存泄漏this._disposeTexture();constSIZE=256;// 256x256 足够清晰,GPU 自动插值拉伸constcanvas=document.createElement('canvas');canvas.width=SIZE;canvas.height=SIZE;constctx=canvas.getContext('2d');// 径向渐变:从画布中心到对角(覆盖四角)constcx=SIZE/2;constcy=SIZE/2;constmaxRadius=Math.sqrt(cx*cx+cy*cy);constgradient=ctx.createRadialGradient(cx,cy,0,cx,cy,maxRadius);gradient.addColorStop(0,this.innerColor);// 中心gradient.addColorStop(1,this.outerColor);// 边缘ctx.fillStyle=gradient;ctx.fillRect(0,0,SIZE,SIZE);this._canvas=canvas;consttexture=newTHREE.CanvasTexture(canvas);// 关键:设置正确的色彩空间,否则颜色会偏亮/偏灰texture.colorSpace=THREE.SRGBColorSpace;this._texture=texture;returntexture;}_disposeTexture(){if(this._texture){this._texture.dispose();this._texture=null;}this._canvas=null;}// ---------- 保存 & 恢复 ----------_saveCurrentState(){// 保存当前 scene.background(可能是环境贴图或纯色)this._previousBackground=this.scene.background;// 保存当前 renderer 的 clearColor(用于恢复)constc=newTHREE.Color();this.renderer.getClearColor(c);this._previousClearColor=c.getHexString();}_restoreClearColor(){if(!this._previousClearColor)return;constcolor=newTHREE.Color('#'+this._previousClearColor);constalpha=this._previousClearAlpha??1;this.renderer.setClearColor(color,alpha);}// ---------- 公开 API ----------/** * 设置渐变颜色 * @param {string} inner - 中心颜色(如 '#1a1a2e') * @param {string} outer - 边缘颜色(如 '#0f0f1a') */setColors(inner,outer){this.innerColor=inner;this.outerColor=outer;if(this._isActive){this.apply();// 如果已经激活,立即刷新}}/** * 应用渐变背景 */apply(){this._saveCurrentState();consttexture=this._buildTexture();this.scene.background=texture;// 恢复 renderer 的 clearColor 为默认值// 因为 scene.background 会覆盖 clearColor,// 保持 renderer 为默认黑色透明即可this.renderer.setClearColor(newTHREE.Color(0x000000),1);this._isActive=true;}/** * 移除渐变背景,恢复到之前的状态 */remove(){if(!this._isActive)return;this._disposeTexture();// 恢复 backgroundif(this._previousBackground!==undefined){this.scene.background=this._previousBackground;}else{this.scene.background=null;}// 恢复 renderer clearColorthis._restoreClearColor();this._isActive=false;}/** * 切换渐变背景 开/关 */toggle(){if(this._isActive){this.remove();}else{this.apply();}}/** 获取是否激活 */getisActive(){returnthis._isActive;}// ---------- 清理 ----------dispose(){this.remove();this._previousBackground=null;this._previousClearColor=null;}}2.2 使用示例
// ========== 初始化 Three.js 场景 ==========constscene=newTHREE.Scene();constrenderer=newTHREE.WebGLRenderer({antialias:true});renderer.setSize(window.innerWidth,window.innerHeight);renderer.setClearColor(0x000000,1);// 初始黑色背景document.body.appendChild(renderer.domElement);// 添加一些测试物体constgeometry=newTHREE.TorusKnotGeometry(1,0.3,128,32);constmaterial=newTHREE.MeshStandardMaterial({color:0x4488ff,roughness:0.2,metalness:0.8});constmesh=newTHREE.Mesh(geometry,material);scene.add(mesh);// 灯光scene.add(newTHREE.AmbientLight(0xffffff,0.5));scene.add(newTHREE.DirectionalLight(0xffffff,1));// ========== 使用渐变背景 ==========constgradientBg=newGradientBackground(scene,renderer);gradientBg.setColors('#1a1a2e','#0f0f1a');// 深蓝紫 → 近黑gradientBg.apply();// 后续可以随时切换颜色// gradientBg.setColors('#2d1b69', '#0a0a0f'); // 紫色 → 深黑// 或关闭渐变背景// gradientBg.remove();// ========== 渲染循环 ==========functionanimate(){requestAnimationFrame(animate);mesh.rotation.x+=0.005;mesh.rotation.y+=0.01;renderer.render(scene,camera);}animate();三、Renderer 配置说明
3.1 必须的配置
| 配置项 | 说明 |
|---|---|
renderer.setClearColor(...) | 渐变背景通过scene.background实现,会覆盖 clearColor。但建议设置一个"后备" clearColor(如黑色),在移除渐变时作为回退。 |
renderer.outputColorSpace | three.js r152+ 默认为THREE.SRGBColorSpace,一般不需要修改。如果你的项目使用了旧版 three.js 或自定义色彩管线,确保它与其他纹理保持一致。 |
3.2 不需要的配置
| 配置项 | 说明 |
|---|---|
renderer.alpha | 渐变背景不依赖透明度,保持alpha: false(默认)即可。 |
renderer.toneMapping | 不影响背景,保持你的项目已有设置(如ACESFilmicToneMapping)即可。 |
renderer.setPixelRatio | 与渐变背景无关,按项目需要设置。 |
3.3 与天空组件共存
如果你的场景中使用了天空球(Sky Shader / Sky Component),渐变背景与其是互斥的——渐变背景作为"画布底色"会覆盖天空球的效果。建议:
// 开启渐变背景时隐藏天空skyMesh.visible=false;gradientBg.apply();// 关闭渐变背景时恢复天空skyMesh.visible=true;gradientBg.remove();注意:渐变背景一旦设置,
scene.background将被替换。如果之前有环境贴图同时充当背景和环境光,需要先用PMREMGenerator分离它们。
四、常见问题排查
Q1:颜色看起来不对(太亮、太灰、色差大)
原因:CanvasTexture的colorSpace设置不正确。
解决:
texture.colorSpace=THREE.SRGBColorSpace;// ← 必须设置Q2:渐变方向不对(想从上到下而不是从中心)
解决:将createRadialGradient改为createLinearGradient:
// 线性渐变(从上到下)constgradient=ctx.createLinearGradient(0,0,0,SIZE);gradient.addColorStop(0,'#1a1a2e');// 顶部gradient.addColorStop(1,'#0f0f1a');// 底部Q3:移除渐变后场景变黑 / 不显示
原因:移除渐变时没有正确恢复scene.background。
解决:确保在remove()中恢复了原来的scene.background(见上文_previousBackground机制)。
Q4:渐变边缘可以看到明显的色带 / 条纹
原因:Canvas尺寸太小,或浏览器色彩管理导致。
解决:将SIZE从 256 提升到 512 或 1024(256 在实际使用中通常已经够用,因为纹理会被 GPU 平滑采样)。
六、总结
| 优点 | 说明 |
|---|---|
| 轻量 | 只需一个 256×256 Canvas,无额外 Shader 复杂度 |
| 灵活 | 颜色可随时切换,无需重新编译着色器 |
| 易集成 | 不依赖后处理通道(EffectComposer),不改变渲染管线 |
| 兼容 | 所有支持CanvasTexture的 three.js 版本均可用 |
