最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
用代码构建3D世界:Vue3联合Three.js实现程序化资产生成实战
时间:2026-05-29 08:10:01 编辑:袖梨 来源:一聚教程网
程序化生成3D资产正在改变传统建模流程,本文将详解如何通过Vue3+Three.js实现参数化建模,从架构设计到具体实现完整解析。
不写模型文件,用代码「捏」出 3D 世界:Vue3 + Three.js 程序化资产生成实战
本文对应开源仓库:qdcxj/three.js-3d-assets
基于原项目:boytchev/3d-assets 的 Vue 3 二次开发版本
效果预览
项目Demo展示页包含16种程序化3D资产,每个模型都支持通过重新生成功能创建随机变体。

前言:为什么不用 .glb?
传统3D页面开发流程存在四个典型痛点,程序化生成提供了更优解决方案:
| 问题 | 程序化生成的解法 |
|---|---|
| 改尺寸要重新建模 | 改一个数字,几何体实时重建 |
| 100 个变体 = 100 个文件 | 一套 paramData + generate() 覆盖无限变体 |
| 下载体积大 | 纯 JS 计算,KB 级代码 |
| UI 滑块无法联动 | 参数即 API,天然对接表单 |
程序化生成的核心优势在于参数化建模,本项目通过Vue3改造实现了16种物体的实时调参和场景组合展示功能。
一、整体架构
┌─────────────────────────────────────────────────────────┐
│ Vue 视图层 │
│ AllInOne.vue / Demo.vue │
│ · 下拉选择资产 · 参数面板 · 滑块实时刷新 │
└───────────────────────┬─────────────────────────────────┘
│
┌───────────────────────▼─────────────────────────────────┐
│ ThreeScene.vue + useThreeScene.js │
│ · Scene / Camera / Renderer / OrbitControls │
│ · addObject / removeObject / clearScene │
└───────────────────────┬─────────────────────────────────┘
│
┌───────────────────────▼─────────────────────────────────┐
│ src/assets/ 资产层 │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ mug.js │ │ table.js │ │ catalog.js │ │
│ │ plate.js │ │ wardrobe.js │ │ (分类注册表) │ │
│ └──────┬──────┘ └──────┬───────┘ └───────────────┘ │
│ └────────────────┴──────────────────────────────│
│ │ │
│ assets-utils.js (基类 + 自定义几何体) │
│ bin-packing.js (UV 图集打包) │
└─────────────────────────────────────────────────────────┘
二、项目目录说明
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
所有资产继承同一个基类 Asset(本质是 THREE.Group):
// assets-utils.js
class Asset extends Group { // 从 paramData 自动提取默认值
static get defaults() {
let result = {}
for (const [key, param] of Object.entries(this.paramData)) {
result[key] = param.default
}
return result
} // 按 min/max/chance 随机生成一套参数
static random() {
let result = {}
for (const [key, param] of Object.entries(this.paramData)) {
if (param.type != Boolean) {
result[key] = random(param.min, param.max, param.prec)
}
if (param.type == Boolean) {
result[key] = Math.random() < param.chance
}
}
return result
}
}
3.1 标准资产模板
每个物体文件都遵循 四件套:
class Xxx extends ASSETS.Asset {
static name = 'Xxx' // ① 显示名
static paramData = { ... } // ② 参数 Schema
constructor(params) { // ③ 构造时 generate
super()
this.generate(params)
}
generate(params) { ... } // ④ 核心:程序化构建
dispose() { ... } // ⑤ 释放 GPU 几何体内存
}
3.2 paramData:参数就是 UI 的「契约」
paramData 不仅描述几何计算,还直接驱动前端参数面板:
static paramData = {
plateHeight: {
default: 1.6,
type: 'cm', // 单位:厘米(内部用 cm() 转 Three.js 米制)
min: 0.5,
max: 5,
folder: 'Plate', // UI 分组
name: 'Height' // UI 显示名
},
plateComplexity: {
default: 50,
type: 'n', // 整数(细分段数)
min: 4,
max: 120,
exp: true // 随机生成时用指数分布
},
flat: {
default: false,
type: Boolean,
chance: 0.3 // random() 时 30% 概率为 true
}
}
设计要点:
- 用
cm/deg/%等业务单位,别直接暴露 Three.js 坐标 folder+name让属性面板自动分组,无需手写表单min/max保证滑块不会生成非法几何
四、三种几何构建方式(由简到难)
4.1 旋转体 Lathe —— 适合杯、盘、瓶
原理: 在 XY 平面画轮廓点,绕 Y 轴旋转一圈。
以 Plate(盘子)为例:
generate(params) {
this.dispose() const pH = ASSETS.cm(params.plateHeight)
const pS = ASSETS.cm(params.plateSize)
const pC = Math.floor(params.plateComplexity) // 轮廓点:[x, y, 圆角半径, uv坐标]
const points = [
[0, 0],
[pS / 2, 0],
[pS / 2, pH],
[pS / 2 - pW, pH],
[0, pW]
] const geometry = new ASSETS.LatheUVGeometry(points, pC)
const material = ASSETS.defaultMaterial.clone() this.body = new THREE.Mesh(geometry, material)
this.add(this.body)
}
LatheUVGeometry 在 Three.js 原生 LatheGeometry 基础上扩展了 UV 映射,后续贴图不会乱。
适用: 杯子、盘子、酒瓶、花瓶、盾牌的弧度轮廓……
4.2 圆角盒子 RoundedBox —— 适合箱子、设备外壳
RoundBox 用 RoundedBoxGeometry 一次生成带倒角的 Box:
this.box = new ASSETS.RoundedBoxGeometry({
x: params.x, y: params.y, z: params.z,
roundness: params.roundness,
segments: params.roundDetail,
faces: [params.f0, params.f1, ...], // 哪些面要渲染
roundFaces: [params.r0, ...], // 哪些边要倒角
})this.add(new THREE.Mesh(this.box, material))
适用: 音箱、显示器外壳、收纳盒、任何「方方正正但有圆角」的东西。
4.3 挤出 + UV 图集 —— 适合复杂家具
Table、Chair、Wardrobe 这类家具有 异形截面 + 曲线路径挤出:
// 1. 用 RoundedShape 定义截面(桌腿轮廓)
const legProfile = new ASSETS.RoundedShape([
[0, legThickness / 2],
[-legThickness / 2, legThickness / 2, legRoundness, 0.2, , roundDetail],
// ...
])// 2. 用贝塞尔曲线定义桌腿路径
const curve = new THREE.CubicBezierCurve3(
new THREE.Vector3(-legOffset, top, 0),
new THREE.Vector3(-(legOffset + b), top * (1 - a), 0),
new THREE.Vector3(-legSpread, top * legShape, 0),
new THREE.Vector3(-legSpread, 0, 0)
)// 3. 沿路径挤出截面
const geom = new ASSETS.SmoothExtrudeGeometry(legProfile, {
extrudePath: curve,
steps: params.legDetail,
caps: [1, 1]
})
为什么要 bin-packing?
复杂家具有多个部件,每个部件 UV 矩形大小不一。bin-packing.js 用 MAXRECTS-BSSF-BNF 算法把所有 UV 矩形打进同一张贴图 atlas,避免纹理浪费:
import * as BP from './bin-packing.js'const rects = ASSETS.SmoothExtrudeGeometry.getRectangles(legProfile, legData)
const binPacker = BP.minimalPacking(rects, 1.0)
binPacker.generateUV() // 写回每个几何体的 uvMatrix
这是本项目最「硬核」的部分,也是和普通 Three.js Demo 拉开差距的地方。
五、Three.js 场景封装
5.1 useThreeScene —— Composable 管理生命周期
// composables/useThreeScene.js
export function useThreeScene(containerRef) {
function init() {
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera(35, aspect, 0.1, 100)
renderer = new THREE.WebGLRenderer({ antialias: true })
controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
animate()
} function addObject(object) {
scene.add(object)
objects.push(object)
} function clearScene() {
objects.forEach(obj => scene.remove(obj))
objects = []
} return { init, dispose, addObject, removeObject, clearScene,
getScene, getCamera, getRenderer }
}
5.2 ThreeScene.vue —— 对外暴露 API
父组件通过 ref 调用:
const sceneRef = ref(null)// 创建物体
const mug = new Assets.Mug(Assets.Mug.defaults)
mug.scale.setScalar(5)
sceneRef.value.addObject(mug)// 实时重建
mug.generate({ ...newParams })
六、Vue 参数面板:滑块拖动实时变模型
关键思路:不要重建整个 Scene,只调用 generate() 原地换几何体。
// AllInOne.vue 核心逻辑
const currentParams = reactive({})
let currentObject = null
let refreshFrameId = nullfunction onParamInput(key, value) {
currentParams[key] = typeof value === 'boolean' ? value : parseFloat(value)
scheduleRefresh()
}function scheduleRefresh() {
if (refreshFrameId) return
refreshFrameId = requestAnimationFrame(() => {
refreshFrameId = null
currentObject?.generate({ ...currentParams })
})
}
为什么用 requestAnimationFrame 节流?
拖动滑块时 input 事件每秒触发几十次,每次都重建几何体会卡。节流到「每帧最多重建一次」,体验丝滑,CPU/GPU 压力也可控。
完整交互链路:
- 下拉框选择资产 →
new AssetClass(defaults)→ 加入场景 - 读取
AssetClass.paramData→ 渲染滑块 / 开关 - 滑块拖动 → 更新
currentParams→generate()→ 模型实时变化 - 点击「随机生成」→
AssetClass.random()→ 一键换造型
七、模块化扩展:从 16 个到 70+ 个物体
当物体数量上来后,建议引入 工厂 + 目录 两层抽象。
7.1 procedural-kit.js —— 资产工厂
import * as ASSETS from './assets-utils.js'export const COMMON_COMPLEXITY = {
segments: { default: 24, type: 'n', min: 8, max: 64, folder: 'Complexity', name: 'Segments' },
flat: { default: false, type: Boolean, chance: 0.3, folder: 'Complexity', name: 'Flat' }
}export function createAsset({ name, paramData, build }) {
class GeneratedAsset extends ASSETS.Asset {
static name = name
相关文章
-
中通快递单号怎么查询
06-04
-
小红书笔记图片加载失败怎么办
06-04
-
哔哩哔哩怎么取消关注自动回复
06-04
-
如何进入Bilibili网站首页
06-04
-
高校超星平台登录入口在哪
06-04
-
乐读小说app如何清理缓存
06-04