游乐游手机版
首页/前端开发/文章详情

Vue3+Three.js实战:不写模型,用代码程序化生成3D世界

时间:2026-06-14 06:54
该项目探索了使用Vue3与Three js通过代码程序化生成3D资产,替代传统建模。核心是将建模逻辑转化为代码,实现参数化实时调整、无限变体生成及轻量化输出。项目采用三层架构,资产层定义可继承的参数化类,支持多种几何构建。通过Vue组件与Three js场景封装,实现了参数面板与3D模型的实时联动交互,并提供了资。

不写模型文件,用代码「捏」出 3D 世界:Vue3 + Three.js 程序化资产生成实战

在构建3D页面时,传统的路径是先用Blender建模,再导出glTF文件,最后用Three.js加载。这条路当然走得通,但实际操作起来,总会遇到几个绕不开的痛点。

比如,想改个尺寸就得重新建模;想要100个变体就得准备100个文件;模型文件动辄几MB,下载体积不小;更别提想用UI滑块联动调整参数有多麻烦了。

那么,有没有一种更灵活、更轻量的方式?答案是肯定的,那就是程序化生成

程序化生成的核心思想,是把建模的逻辑用代码写出来。这样一来,改尺寸就变成了改一个数字,几何体可以实时重建;一套参数数据和生成函数,就能覆盖无限种变体;纯JS计算,代码体积只有KB级别;参数本身就是API,天然就能和表单控件联动。

下面要介绍的这个项目,正是基于这个思路。它已经实现了16种常见物体(如杯子、桌子、衣柜、酒瓶等),并支持通过滑块实时调参、随机生成变体,以及场景组合展示。接下来,我们就从架构到源码,完整地拆解一遍“如何用代码捏出3D模型”。


一、整体架构:清晰的三层设计

整个项目的架构可以清晰地分为三层:视图层、场景管理层和资产层。

视图层由Vue组件构成,例如AllInOne.vueDemo.vue,负责资产选择、参数面板的渲染以及滑块交互事件的响应。

场景管理层的核心是ThreeScene.vue组件和useThreeScene.js这个Composable。它封装了Three.js的Scene、Camera、Renderer和OrbitControls的初始化与生命周期管理,并对外提供了添加、移除物体等简洁的API。

资产层是项目的核心,位于src/assets/目录下。这里存放着每个物体的定义文件(如mug.js, table.js),一个统一的分类注册表(catalog.js),以及一些关键的底层工具,比如资产基类、自定义几何体(assets-utils.js)和用于UV图集打包的算法(bin-packing.js)。

当用户在界面上拖动滑块调参时,数据流是这样的:参数变化触发Vue组件的更新,组件调用当前资产实例的generate()方法并传入新参数,该方法会基于新参数重新计算并生成几何体,最后Three.js渲染器将更新后的模型呈现在画布上。整个过程是实时的。


二、项目目录结构一览

为了更直观地理解,我们先看看项目的核心目录结构:

src/
├── assets/                    # 程序化资产核心
│   ├── assets-utils.js        # Asset 基类、LatheUVGeometry、RoundedBoxGeometry...
│   ├── bin-packing.js         # UV 矩形打包算法
│   ├── mug.js / plate.js ...  # 每个物体一个模块
│   ├── procedural-kit.js      # 【扩展】工厂函数,快速批量造物体
│   ├── catalog.js             # 【扩展】分类目录(家具/兵器/交通...)
│   └── index.js               # 统一导出
├── components/
│   └── ThreeScene.vue         # 3D 画布封装
├── composables/
│   └── useThreeScene.js       # Three.js 生命周期管理
└── views/
    ├── AllInOne.vue           # 全场景 / 单物体展示 + 参数面板
    └── Demo.vue               # 分卡片 Demo

三、核心设计:每个物体都是一个Class

为了实现统一的创建、管理和参数化,项目中所有可生成的3D资产都继承自同一个基类——Asset。这个类本质上是一个THREE.Group,但赋予了资产参数化与随机化的能力。

基类提供了两个非常实用的静态方法:defaults用于从paramData中提取所有参数的默认值;random则能根据每个参数定义的minmaxchance(概率),生成一套合法的随机参数。

3.1 标准资产模板:四件套缺一不可

每个具体的资产文件,比如一个杯子或一张桌子,都遵循一套标准的模板,可以概括为“四件套”:

  1. 静态属性 name:资产的显示名称。
  2. 静态属性 paramData:定义所有可调参数的元数据(Schema),这是驱动UI面板的“契约”。
  3. 构造函数 constructor:接收参数并立即调用generate方法创建几何体。
  4. 核心方法 generate:根据传入的参数,程序化地构建几何体和材质,并将其添加到Group中。
  5. 清理方法 dispose(可选但重要):用于释放旧的几何体GPU内存,防止泄漏。

3.2 paramData:连接几何逻辑与UI面板的契约

paramData是这个设计中最精妙的部分。它不仅仅是一份参数说明,更直接驱动了前端的参数面板自动生成。

例如,一个盘子的厚度参数可能这样定义:

plateHeight: {
  default: 1.6,
  type: 'cm',       // 单位:厘米(内部会用工具函数转为Three.js的米制单位)
  min: 0.5,
  max: 5,
  folder: 'Plate',  // 在UI面板中,这个参数会被归到“Plate”分组下
  name: 'Height'    // 在UI面板中显示为“Height”
}

这样的设计有几个关键要点:

  • 使用cmdeg%等业务单位,而不是直接暴露Three.js的内部坐标,对开发者更友好。
  • foldername属性让UI面板可以自动分组和显示,无需手动编写表单代码。
  • minmax不仅限制了UI滑块的取值范围,更重要的是保证了随机生成或用户输入时,不会产生非法的几何数据。

四、三种几何构建方式,由简到难

根据物体的形状复杂度,项目中主要采用了三种几何构建方式。

4.1 旋转体 (Lathe) —— 适合杯、盘、瓶

原理:在XY平面上定义一组轮廓点,然后让这组点绕Y轴旋转一圈,从而生成一个旋转体。这非常适合制造具有轴对称特性的物体。

以创建一个盘子为例,首先在generate方法中根据参数计算出轮廓点数组,然后使用扩展版的LatheUVGeometry(它在Three.js原生LatheGeometry基础上完善了UV映射)来生成几何体。最后,为几何体赋予材质并添加到场景中。

适用场景:所有具有旋转对称性的物体,如杯子、盘子、酒瓶、花瓶、盾牌等。


4.2 圆角盒子 (RoundedBox) —— 适合箱子、设备外壳

对于现代设计中常见的带圆角的方正物体,直接使用项目封装的RoundedBoxGeometry。它可以通过参数一次性指定长宽高、圆角大小、细分段数,甚至可以选择哪些面需要渲染、哪些边需要倒角,功能非常灵活。

适用场景:音箱、显示器外壳、收纳盒,以及任何“方方正正但有圆角”的物体。


4.3 挤出 + UV 图集 —— 适合复杂家具

像桌子、椅子、衣柜这类家具,形状往往更复杂,可能包含异形截面和曲线路径。这时就需要用到“挤出”技术。

构建过程分为三步:

  1. RoundedShape定义截面形状,比如一个桌腿的横截面轮廓。
  2. 用贝塞尔曲线(CubicBezierCurve3)定义这条桌腿在空间中的生长路径。
  3. 使用SmoothExtrudeGeometry,让截面形状沿着定义好的路径“挤出”,从而形成三维实体。

为什么要用UV图集打包?

复杂家具由多个部件组成,每个部件都有自己的纹理坐标(UV)矩形。如果每个部件单独用一张贴图,会造成巨大的纹理浪费和性能开销。bin-packing.js文件实现了MAXRECTS-BSSF-BNF算法,它能高效地将所有大小不一的UV矩形“打包”进同一张大的纹理贴图(图集)中,极大提升了纹理利用率。这是本项目与普通Three.js Demo在技术深度上的一个显著区别。


五、Three.js 场景的现代化封装

5.1 useThreeScene —— 用Composable管理生命周期

为了将Three.js的初始化、渲染循环和资源管理与Vue3的响应式系统优雅结合,项目创建了useThreeScene这个Composable。它接收一个容器DOM引用,在其内部创建并管理Scene、Camera、Renderer和OrbitControls。它提供了addObjectremoveObjectclearScene等简洁API,并确保了在组件卸载时能正确销毁渲染器和释放控制。

5.2 ThreeScene.vue —— 对外暴露干净API的组件

ThreeScene.vue组件是一个薄薄的包装层。它在内部调用useThreeScene,并在合适的生命周期(onMounted/onUnmounted)触发初始化和清理。最关键的一步是,它通过Vue 3的defineExpose将场景管理器的API暴露出去。

这样,父组件就可以通过模板引用(ref)直接操作3D场景:

// 在父组件中
const sceneRef = ref(null);
// 创建一个杯子
const mug = new Assets.Mug(Assets.Mug.defaults);
// 调整大小并加入场景
mug.scale.setScalar(5);
sceneRef.value.addObject(mug);
// 实时修改参数,重建模型
mug.generate({ ...newParams });

六、Vue参数面板:实现滑块拖动,模型实时变化

实现交互式调整的关键思路是:不要销毁和重建整个场景或物体,只调用物体自身的generate()方法,在原地替换几何体

AllInOne.vue组件中,核心逻辑是维护一个当前参数的响应式对象,以及一个对当前场景中物体的引用。当任何参数滑块被拖动时,会触发onParamInput函数,该函数更新参数并计划一次模型刷新。

这里用到了一个重要的优化技巧:使用requestAnimationFrame进行节流。因为滑块拖动会高频触发input事件,如果每次事件都立即重建几何体,会导致界面卡顿。通过节流,可以确保在每一帧动画中,最多只执行一次generate()调用,从而在保持响应性的同时,极大减轻CPU/GPU的压力。

完整的交互链路如下:

  1. 用户从下拉框选择一种资产(如“杯子”)。
  2. 前端根据该资产的类,用默认参数创建一个实例,并添加到Three.js场景中。
  3. 同时,前端读取该资产类的paramData,动态渲染出对应的滑块、开关等参数控件。
  4. 用户拖动滑块,参数更新,触发generate(newParams),模型在场景中实时变化。
  5. 用户点击“随机生成”按钮,调用AssetClass.random()生成一套随机参数,并应用,一键获得全新造型。

七、模块化扩展:如何从16个物体扩展到70+

当资产数量越来越多时,手动管理每个类会变得困难。项目引入了“工厂+目录”两层抽象来解决这个问题。

7.1 procedural-kit.js —— 资产工厂

这是一个用于快速创建新资产类的工厂函数createAsset。你只需要提供资产名称、参数定义(paramData)和一个构建函数(build),它就会返回一个符合标准的、继承自Asset基类的新类。这个工厂还自动处理了旧几何体清理、物体自动“落地”(使其底部站在y=0平面)等通用逻辑,让开发者只需关注最核心的形状构建代码。

7.2 实战:用30行代码造一把“剑”

利用工厂函数,创建一个新的资产变得非常简洁。例如,定义一把剑只需要约30行代码:指定剑刃长度、宽度、手柄长度等参数,然后在build函数中,用基本的BoxGeometry(剑刃)和CylinderGeometry(剑柄)组合起来即可。新增一个物体,几乎变成了“复制模板 + 修改build函数”的填空题,效率极高。

7.3 catalog.js —— 分类注册表

当资产数量庞大后,需要一个目录来分类管理。catalog.js导出一个分类数组,例如“家具”、“兵器”、“交通工具”等。每个分类下包含其拥有的资产列表。Vue的下拉选择框可以直接绑定到这个分类数据,自动生成分组选项,用户体验更好。

一个推荐的分类扩展体系可以是:家具(挤出+圆角盒)、兵器(基础几何体组合)、交通工具(组合图元)、日用品(旋转体)、电子设备(薄盒子)、建筑(基础几何体)、自然(球体+噪声)等。


八、从零实现一个“花瓶”完整示例

我们以创建一个“花瓶”为例,走一遍完整流程:

Step 1: 定义参数。 在对应的分类文件(如daily.js)中,使用createAsset工厂函数,定义花瓶的高度、瓶身大小、瓶颈大小、鼓起程度等参数。

Step 2: 编写build逻辑。build函数中,将参数转换为实际尺寸,然后使用LatheUVGeometry,通过定义几组关键的轮廓点(底部、瓶身最宽处、肩部、瓶口)来生成花瓶的旋转体几何体。

Step 3: 注册到目录。catalog.js的“日用品”分类中,添加一行,将刚创建的花瓶类注册进去。

Step 4: 在页面中使用。 在Vue组件中,从目录中按索引或ID获取到花瓶的类定义,用其默认参数创建实例,并调用场景的addObject方法加入渲染。

完成以上四步,一个可交互、参数可调的花瓶就诞生了。打开页面,拖动高度或鼓起程度的滑块,就能看到花瓶形状实时变化。


九、性能优化与实战踩坑指南

在开发过程中,以下几点对于保证应用性能和稳定性至关重要:

9.1 必须dispose旧几何体。 在资产的generate方法开头或dispose方法中,务必调用旧几何体的.dispose()方法。否则,每次参数变化都会在GPU中残留旧的几何数据,导致显存泄漏,页面会越来越卡。

9.2 解决Vite构建相关报错。 如果项目使用了Element Plus等UI库,且Vite配置了optimizeDeps.noDiscovery: truevite.config.jsoptimizeDeps.include中显式包含'dayjs'等依赖。

9.3 注意大物体的相机距离。 对于建筑等大型物体,其尺寸可能达到数米。需要相应调整OrbitControls.maxDistance,或者在将物体加入场景前,统一进行缩放(instance.scale.setScalar(5)),以确保其在相机视野内。

9.4 实现“可控随机”。 生成随机变体时,务必使用资产类提供的static random()方法,而不是直接用Math.random()。前者会严格遵守paramData中定义的minmaxchance等规则,确保生成的参数总是合法的,不会产生破碎的模型。


十、未来可以延伸的方向

这套程序化生成的架构具有很好的扩展性,天然适合向以下几个方向演进:

  1. 大屏3D编辑器:左侧是资产库(已实现的catalog),中间是3D画布,右侧是属性面板(已实现的参数控件),形成一个完整的编辑环境。
  2. 低代码3D场景搭建:将catalog视为3D组件库,用户通过拖拽和参数配置就能快速搭建出复杂的3D场景。
  3. AI生成参数:让大语言模型(LLM)理解物体描述,输出符合paramData Schema的JSON参数,调用generate(json)即可直接生成对应模型。
  4. 导出glTF:利用Three.js的GLTFExporter,将程序化生成的动态模型“烘焙”成标准的静态glTF文件,用于其他平台。
  5. 游戏关卡道具:利用random()方法,每次游戏关卡加载时,生成外形略有不同的道具(如不同形状的岩石、箱子),增加游戏内容的多样性。

十一、总结

回顾整个项目,其成功的关键在于清晰的层级划分和解耦:

层级 职责 关键文件
基类 参数默认值 / 随机 / Group 容器 assets-utils.js
资产 描述「怎么造」 mug.js, table.js...
几何 Lathe / RoundedBox / Extrude assets-utils.js
UV 图集打包 bin-packing.js
扩展 批量造物体 procedural-kit.js
目录 分类管理 catalog.js
视图 3D 展示 + 调参 AllInOne.vue

一句话总结: 正是这种“资产定义、几何生成、UI交互”三者解耦的设计,使得项目能够从一个简单的杯子示例,轻松扩展到管理数十上百种各不相同的3D物体,而整个架构依然保持清晰和可维护。


附录:如何本地运行与探索

如果你想亲身体验或基于此项目进行二次开发,可以按照以下步骤在本地运行:

git clone [项目仓库地址]
cd three.js-3d-assets
npm install
npm run dev

项目启动后,通常可以通过以下路由访问不同功能页面:

路由 说明
/ 首页 — 资产卡片概览
/demo Demo — 分卡片展示
/all-in-one 全场景 — 多物体同屏 + 参数面板

通过动手实践,你将更深刻地理解如何用代码“捏”出一个参数化、可交互的3D世界。

来源:https://juejin.cn/post/7643753392239116288
上一篇VuReact 1.8.4发布:Vue迁移React编译器稳定性大修与问题修复 下一篇vxe-select下拉框实现人员选择功能
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Layui弹出层监听子页面键盘快捷键实现方法
前端开发 · 2026-07-06

Layui弹出层监听子页面键盘快捷键实现方法

子页面键盘事件监听需在DOM加载完成后绑定,父页无法直接监听子页按键,必须由子页自身监听后通过parent或postMessage通知父页。典型写法为子页调用父页已定义的关闭函数。需注意焦点状态、输入法及layui版本兼容性等陷阱。

Layui表单提交时携带当前页面Meta信息的实现方法
前端开发 · 2026-07-06

Layui表单提交时携带当前页面Meta信息的实现方法

Layui表单提交不会自动携带页面Meta信息,需在form on( submit )回调中手动读取meta内容并拼接到表单数据,注意后端字段映射及特殊字符编码,多meta时按需选取。

HTML5拖拽事件流状态转移监控调试
前端开发 · 2026-07-06

HTML5拖拽事件流状态转移监控调试

HTML5拖拽事件流易因漏监听或未调用preventDefault而中断。需掌握dragstart设置数据、dragover接受放置、drop触发条件等关键点。通过统一日志捕获事件上下文、识别常见状态丢失场景并配合可视化面板,可清晰定位拖拽过程断点。

uni-app实现小红书商品详情图卡片切换
前端开发 · 2026-07-06

uni-app实现小红书商品详情图卡片切换

通过手写touch事件与transform控制五张卡片,动态计算translateX、scale、opacity及z-index模拟层叠滑动效果。滑动距离超过80rpx触发切换,否则复位。图片仅渲染当前及前后两张,有效优化加载性能与渲染效率。

图像旋转倾斜与扭曲的Canvas像素矩阵变换
前端开发 · 2026-07-06

图像旋转倾斜与扭曲的Canvas像素矩阵变换

Canvas图像变形本质是操作坐标系,图像被动跟随。旋转需先平移原点至目标中心再旋转后复位;倾斜通过仿射变换矩阵实现;扭曲无原生API,可用分块模拟或转用WebGL。每次变换前保存状态,完成后恢复,避免坐标系偏移。