当前位置: 首页 > news >正文

Three.js 径向渐变背景实现教程

前言

在 Three.js 项目中,scene.background通常用来设置纯色背景或天空盒(CubeTexture / Equirectangular 环境贴图)。但有时候我们希望背景是一个从中心向四周辐射的径向渐变(比如深蓝到黑的科技感渐变),而不是单调的纯色。本文将介绍一种轻量高效的实现方案——用<canvas>生成渐变纹理,再通过THREE.CanvasTexture赋给scene.background

效果预览



一、核心原理

整体流程只有三步:

创建 Canvas
绘制径向渐变

生成 CanvasTexture
设置 colorSpace

赋给 scene.background
恢复 renderer.clearColor

  1. <canvas>createRadialGradientAPI 画出渐变图案。
  2. 将 canvas 包装为THREE.CanvasTexture,并设置colorSpace = THREE.SRGBColorSpace保证颜色正确。
  3. 将纹理赋给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.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.visible=false;gradientBg.apply();// 关闭渐变背景时恢复天空skyMesh.visible=true;gradientBg.remove();

注意:渐变背景一旦设置,scene.background将被替换。如果之前有环境贴图同时充当背景和环境光,需要先用PMREMGenerator分离它们。


四、常见问题排查

Q1:颜色看起来不对(太亮、太灰、色差大)

原因CanvasTexturecolorSpace设置不正确。

解决

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 版本均可用
http://www.jsqmd.com/news/1329058/

相关文章:

  • 038、YOLOv11检测头轻量化——通道剪枝设计在解耦头中的即插即用实现与推理加速
  • 5分钟掌握wiliwili:跨平台B站客户端的完整指南
  • iOS/Android设备如何通过DavMail同步Exchange日历?CardDAV/CalDAV设置教程
  • dbt+SQLServer构建数据仓库(2):与传统方案对比及迁移决策
  • 为什么选择JoyAI-VL-Interaction-INT8?8B参数级最全面的开源视频AI模型深度测评
  • AI解题准确率暴跌的真相(MIT+北师大联合实验报告):当输入格式偏差>2.3%,错误率飙升至68%!
  • 洛雪音乐音源终极配置指南:3分钟解决音源选择难题
  • 如何在 macOS 上解锁 Xbox 手柄的完整功能:360Controller 终极指南
  • 版本管理与发布策略:语义化版本、回滚与升级的优雅实践
  • 别只看书了,这些 CTF 工具和实战资源才是通关关键
  • 东方香氛品牌有哪些:需要关注的参数、证据与审核节点 - 香氛评价员
  • 玻璃布胶带与金手指胶带对比:耐高温胶带选型技术指南 - 品牌品鉴馆
  • MAIGateway,魔芋企业级AI网关的智能体协同治理设计
  • Linux文件系统导航秘籍:Linux Upskill Challenge必备的目录操作技巧
  • 如何在VS Code中实现代码与图表的革命性双向联动?
  • 逆向工程实战:深度解析Windows平台消息防撤回工具的实现原理
  • Unity Cinemachine 3 核心配置与实战:从基础跟随到高级镜头系统
  • NPS分析进入“认知智能”阶段:Gartner 2024新评估框架下,5类传统工具正在失效(附迁移路线图PDF)
  • 2026实测18款文字转语音在线工具 年使用成本从0元到199元哪款性价比更高
  • Nintendo Switch文件逆向实战:深度解析hactool解密工具
  • BGA空洞持续超标?五大核心根因与可落地的系统性整改方案
  • PlayIntegrityFix-NEXT终极指南:如何在Android 10-16设备上实现完美Play Integrity认证
  • 2026年配镜避坑:在潘家园眼镜城,如何三步验证镜片**授权与防伪? - 品牌品鉴馆
  • 跨境投放实操!抖音 CID 跳转天猫国际案例 - 米諾
  • ClaudeAPI Key管理制度与技术措施总结
  • 从FNF模组“QT: Rewired”看Haxe游戏开发与工程实践
  • 开源!一家老牌印刷国企的远程运维方案分享
  • Agent 改完代码,仓库里到底该多留下什么
  • 重庆抗UV防腐钢管厂家,重庆垚固管道提供防腐钢管供应服务 - 品牌品鉴馆
  • 上海市奉贤区GEO城市合伙人选型推荐哪家靠谱 - 小随科技