Three.js 径向渐变背景实现教程
前言在 Three.js 项目中scene.background通常用来设置纯色背景或天空盒CubeTexture / Equirectangular 环境贴图。但有时候我们希望背景是一个从中心向四周辐射的径向渐变比如深蓝到黑的科技感渐变而不是单调的纯色。本文将介绍一种轻量高效的实现方案——用canvas生成渐变纹理再通过THREE.CanvasTexture赋给scene.background。效果预览一、核心原理整体流程只有三步创建 Canvas绘制径向渐变生成 CanvasTexture设置 colorSpace赋给 scene.background恢复 renderer.clearColor用canvas的createRadialGradientAPI 画出渐变图案。将 canvas 包装为THREE.CanvasTexture并设置colorSpace THREE.SRGBColorSpace保证颜色正确。将纹理赋给scene.backgroundThree.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.scenescene;this.rendererrenderer;// 渐变颜色this.innerColor#1a1a2e;// 中心颜色深蓝紫this.outerColor#0f0f1a;// 边缘颜色近黑// 内部状态this._texturenull;this._canvasnull;this._isActivefalse;// 用于恢复的背景引用this._previousBackgroundnull;this._previousClearColornull;this._previousClearAlphanull;}// ---------- 构建渐变纹理 ----------_buildTexture(){// 销毁旧纹理避免 GPU 内存泄漏this._disposeTexture();constSIZE256;// 256x256 足够清晰GPU 自动插值拉伸constcanvasdocument.createElement(canvas);canvas.widthSIZE;canvas.heightSIZE;constctxcanvas.getContext(2d);// 径向渐变从画布中心到对角覆盖四角constcxSIZE/2;constcySIZE/2;constmaxRadiusMath.sqrt(cx*cxcy*cy);constgradientctx.createRadialGradient(cx,cy,0,cx,cy,maxRadius);gradient.addColorStop(0,this.innerColor);// 中心gradient.addColorStop(1,this.outerColor);// 边缘ctx.fillStylegradient;ctx.fillRect(0,0,SIZE,SIZE);this._canvascanvas;consttexturenewTHREE.CanvasTexture(canvas);// 关键设置正确的色彩空间否则颜色会偏亮/偏灰texture.colorSpaceTHREE.SRGBColorSpace;this._texturetexture;returntexture;}_disposeTexture(){if(this._texture){this._texture.dispose();this._texturenull;}this._canvasnull;}// ---------- 保存 恢复 ----------_saveCurrentState(){// 保存当前 scene.background可能是环境贴图或纯色this._previousBackgroundthis.scene.background;// 保存当前 renderer 的 clearColor用于恢复constcnewTHREE.Color();this.renderer.getClearColor(c);this._previousClearColorc.getHexString();}_restoreClearColor(){if(!this._previousClearColor)return;constcolornewTHREE.Color(#this._previousClearColor);constalphathis._previousClearAlpha??1;this.renderer.setClearColor(color,alpha);}// ---------- 公开 API ----------/** * 设置渐变颜色 * param {string} inner - 中心颜色如 #1a1a2e * param {string} outer - 边缘颜色如 #0f0f1a */setColors(inner,outer){this.innerColorinner;this.outerColorouter;if(this._isActive){this.apply();// 如果已经激活立即刷新}}/** * 应用渐变背景 */apply(){this._saveCurrentState();consttexturethis._buildTexture();this.scene.backgroundtexture;// 恢复 renderer 的 clearColor 为默认值// 因为 scene.background 会覆盖 clearColor// 保持 renderer 为默认黑色透明即可this.renderer.setClearColor(newTHREE.Color(0x000000),1);this._isActivetrue;}/** * 移除渐变背景恢复到之前的状态 */remove(){if(!this._isActive)return;this._disposeTexture();// 恢复 backgroundif(this._previousBackground!undefined){this.scene.backgroundthis._previousBackground;}else{this.scene.backgroundnull;}// 恢复 renderer clearColorthis._restoreClearColor();this._isActivefalse;}/** * 切换渐变背景 开/关 */toggle(){if(this._isActive){this.remove();}else{this.apply();}}/** 获取是否激活 */getisActive(){returnthis._isActive;}// ---------- 清理 ----------dispose(){this.remove();this._previousBackgroundnull;this._previousClearColornull;}}2.2 使用示例// 初始化 Three.js 场景 constscenenewTHREE.Scene();constrenderernewTHREE.WebGLRenderer({antialias:true});renderer.setSize(window.innerWidth,window.innerHeight);renderer.setClearColor(0x000000,1);// 初始黑色背景document.body.appendChild(renderer.domElement);// 添加一些测试物体constgeometrynewTHREE.TorusKnotGeometry(1,0.3,128,32);constmaterialnewTHREE.MeshStandardMaterial({color:0x4488ff,roughness:0.2,metalness:0.8});constmeshnewTHREE.Mesh(geometry,material);scene.add(mesh);// 灯光scene.add(newTHREE.AmbientLight(0xffffff,0.5));scene.add(newTHREE.DirectionalLight(0xffffff,1));// 使用渐变背景 constgradientBgnewGradientBackground(scene,renderer);gradientBg.setColors(#1a1a2e,#0f0f1a);// 深蓝紫 → 近黑gradientBg.apply();// 后续可以随时切换颜色// gradientBg.setColors(#2d1b69, #0a0a0f); // 紫色 → 深黑// 或关闭渐变背景// gradientBg.remove();// 渲染循环 functionanimate(){requestAnimationFrame(animate);mesh.rotation.x0.005;mesh.rotation.y0.01;renderer.render(scene,camera);}animate();三、Renderer 配置说明3.1 必须的配置配置项说明renderer.setClearColor(...)渐变背景通过scene.background实现会覆盖 clearColor。但建议设置一个后备 clearColor如黑色在移除渐变时作为回退。renderer.outputColorSpacethree.js r152 默认为THREE.SRGBColorSpace一般不需要修改。如果你的项目使用了旧版 three.js 或自定义色彩管线确保它与其他纹理保持一致。3.2 不需要的配置配置项说明renderer.alpha渐变背景不依赖透明度保持alpha: false默认即可。renderer.toneMapping不影响背景保持你的项目已有设置如ACESFilmicToneMapping即可。renderer.setPixelRatio与渐变背景无关按项目需要设置。3.3 与天空组件共存如果你的场景中使用了天空球Sky Shader / Sky Component渐变背景与其是互斥的——渐变背景作为画布底色会覆盖天空球的效果。建议// 开启渐变背景时隐藏天空skyMesh.visiblefalse;gradientBg.apply();// 关闭渐变背景时恢复天空skyMesh.visibletrue;gradientBg.remove();注意渐变背景一旦设置scene.background将被替换。如果之前有环境贴图同时充当背景和环境光需要先用PMREMGenerator分离它们。四、常见问题排查Q1颜色看起来不对太亮、太灰、色差大原因CanvasTexture的colorSpace设置不正确。解决texture.colorSpaceTHREE.SRGBColorSpace;// ← 必须设置Q2渐变方向不对想从上到下而不是从中心解决将createRadialGradient改为createLinearGradient// 线性渐变从上到下constgradientctx.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 或 1024256 在实际使用中通常已经够用因为纹理会被 GPU 平滑采样。六、总结优点说明轻量只需一个 256×256 Canvas无额外 Shader 复杂度灵活颜色可随时切换无需重新编译着色器易集成不依赖后处理通道EffectComposer不改变渲染管线兼容所有支持CanvasTexture的 three.js 版本均可用