ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

AI写代码生成3D模型:Three.js与Blender参数化建模

AI写代码生成3D模型:Three.js与Blender参数化建模 上周把一个 AI 直接生成 3D 模型的方案按了暂停键。原因不复杂演示视频里确实惊艳一进真实项目就露馅。拓扑像被猫抓过的毛线尺寸没法批量改换个比例整条流程重跑导出 GLTF 后材质和命名还乱成一锅粥。后来我把方向改成“别让 AI 直接吐 3D 模型让它写代码”让 AI 生成参数化脚本再由 Three.js、Blender Python 或 OpenSCAD 去执行。模型就从“一次性结果”变成了可读、可调、可回滚的工程资产。这个思路适合做 Web 3D、产品原型、参数化家具、简单机械件、教学演示的人。上篇我先讲清楚为什么这么选、管线怎么搭、工具怎么选再用两个能跑起来的实操把核心环节串一遍。1. 先想清楚为什么“AI 直接吐 3D 模型”这条路这么容易翻车1.1 直接生成网格的三个硬伤拓扑、可控、迭代让 AI 直接输出 GLB、OBJ 或 STL本质上是让它预测顶点、面片、法线和 UV。大模型对空间结构的理解远没有对文字和代码那么稳。你让它“做一个带圆角的杯子”它可能给你一个看起来像杯子的三角面集合但面与面之间可能有重叠、非流形边、法线反向杯口厚度也未必均匀。演示时转一圈能看进入生产环境就麻烦了切片软件报错游戏引擎导入后光照奇怪渲染器一开细分就炸。更隐蔽的问题是可控性。直接生成的网格没有参数表你想把杯高从 120 毫米改成 150 毫米只能重新生成一版而新一版可能连杯把位置都变了。你想加一个孔位、改一个倒角半径、把底部加厚 2 毫米这些在参数化建模里是改一个数字的事在直接生成的网格里却像拆盲盒。迭代成本高到一定程度AI 带来的效率就被吃回去了。最后是复现性。同一个提示词今天和明天生成的结果可能完全不同。项目里如果有人问“上一版模型为什么这里多了一块”你很难解释。代码化建模把“结果”变成了“规则”规则可以读、可以 diff、可以回滚。这个差别在个人玩具项目里不明显在多人协作或需要持续交付的场景里几乎是生死线。1.2 把 AI 当代码生成器从“给结果”改成“给规则”换个思路让 AI 写代码而不是让 AI 直接给模型。代码里包含尺寸参数、几何原语、布尔运算、阵列、挤出、倒角、导出配置。AI 在代码生成上的能力已经被大量工具验证过它更擅长写结构清晰的函数和配置而不是凭空保证三维网格的拓扑正确。你让它写一个createLamp(baseRadius, poleHeight, shadeRadius)它通常能给出可读的 Python 或 JavaScript因为这类代码有大量公开示例模式稳定。代码化之后模型的可控性完全不一样。底座半径、灯杆高度、灯罩开口角度、分段数、导出格式全都可以放在一个参数对象里。你甚至可以让 AI 根据一张参数表批量生成同系列模型比如 0.9 米、1.1 米、1.3 米三种高度的落地灯。每次生成都走同一套函数只有参数不同结果自然一致。对做电商展示、产品配置器、建筑构件库的人来说这种一致性比单次惊艳重要得多。还有一点容易被忽略代码可以被审查。AI 写出来的脚本里如果单位搞错了、把毫米当米、把 Z 轴当 Y 轴你在代码层面就能看出来。直接生成的网格里单位错误往往要等导入引擎、放到场景里和参照物对比才发现。审查代码比审查网格容易太多尤其是团队里有人熟悉 Three.js 或 Blender API 的时候。1.3 代码化建模的适用边界与不适用场景代码化建模不是万能药。它适合规则几何、参数化结构、批量变体、Web 实时展示、简单机械件、家具构件、包装展示、教学演示。比如台灯、椅子腿、齿轮、法兰盘、置物架、建筑幕墙分格、产品外壳的近似体这些都能用原语和布尔运算拼出来。用 AI 写代码生成这些内容效率高改起来也快。但它不适合高精度有机角色、雕刻类模型、扫描重建、复杂曲面生物、毛发和布料模拟。你让 AI 写代码生成一个写实人脸代码会非常长效果也未必比直接生成网格好。代码化建模的优势在“规则明确”和“可参数化”如果需求本身就是高度艺术化、非规则、依赖手绘雕刻的硬套代码只会增加负担。我的判断标准很简单如果这个模型未来需要改尺寸、换材质、批量复制、导出多种格式就值得代码化如果它只是一张宣传图里的静态摆件直接生成或手工建模可能更省事。2. 整体方案设计让 AI 写代码生成 3D 内容的管线怎么搭2.1 输入层提示词不是许愿而是需求规格很多人给 AI 的提示词像许愿“做一个好看的现代台灯3D 模型谢谢。”这种输入得到的结果随机性极大。让 AI 写代码时提示词要像给外包写需求规格。至少要包含单位、坐标系、整体尺寸范围、必须包含的部件、材质倾向、导出格式、面数预算、命名规则、是否需要参数化。比如这样写“用 Three.js 写一段 JavaScript生成一个参数化台灯模型。单位米Y 轴向上底座半径 0.6底座高 0.12灯杆半径 0.035灯杆高 1.2灯罩上半径 0.32下半径 0.5高 0.35。底座用深灰金属灯杆用银色金属灯罩用米白半透材质。所有尺寸放在params对象里。最后用 GLTFExporter 导出 glTF。面数控制在 2 万三角面以内。部件命名为 Base、Pole、Shade。”这种提示词给 AI 的约束足够多它写出来的代码就更容易一次跑通。需求规格越像工程任务书AI 的代码越稳定。你还可以把参数表单独做成 JSON让 AI 写代码读取 JSON这样后面改参数不用重新问 AI。2.2 生成层AI 写哪几类代码最稳目前我实测下来三类代码最适合让 AI 生成。第一类是 Three.js 的几何构建代码适合 Web 预览和 GLTF 导出。第二类是 Blender Python 脚本适合批量生成、渲染、导出 STL 或 GLTF也能做更复杂的修改器操作。第三类是 OpenSCAD适合纯参数化、适合 3D 打印的硬表面零件。三类代码的共同点是结构清晰、API 稳定、示例丰富AI 容易写对。不要一上来就让 AI 写一个几百行的完整项目。先让它写一个最小可运行脚本比如只生成一个立方体并导出确认环境没问题。然后再让它加第二个部件、加材质、加参数、加导出。每次只增加一个变量跑通再继续。这个习惯能省掉大量排查时间因为一旦报错你知道问题大概率出在刚加的那一小段。2.3 验证层渲染、导出、检查三件套生成代码跑通不代表模型能用。我习惯做三层验证。第一层是渲染预览在浏览器或 Blender 里看一眼比例、位置、材质是否合理。第二层是导出验证把 GLTF 或 STL 导出来用另一个工具重新导入看有没有丢失材质、法线错误、尺寸变化。第三层是数据检查看三角面数量、包围盒尺寸、部件命名、原点位置是否符合预期。这三层听起来麻烦但每层只需要几十秒。很多问题在预览阶段就能发现比如灯罩悬空、底座穿模、坐标原点不在底部。如果等模型进了引擎再发现排查成本会高很多。我的经验是宁可在小脚本阶段多导出五次也不要等到大场景里再修。2.4 版本层让每次生成都可回滚代码化建模最大的好处之一是可版本控制。把 AI 生成的脚本、参数 JSON、导出配置都放进 Git。每次改参数、改材质、改导出格式都提交一次。导出的 GLB、STL 不要直接进主仓库可以放 Git LFS 或单独的产物目录避免仓库膨胀。脚本里写清楚版本号、生成日期、参数摘要后面回溯会轻松很多。如果团队里有人不写代码也可以让他们只改参数文件。比如lamp-config.json里写poleHeight: 1.2脚本读取后生成模型。这样非技术同事也能调尺寸不用碰 JavaScript 或 Python。这个分层很重要AI 写代码人调参数工具做导出。各司其职流程就稳。3. 工具选型Three.js、Blender Python、OpenSCAD 怎么选3.1 Web 方向选 Three.js实时预览和 GLTF 导出方便如果你的目标是网页展示、产品配置器、在线预览Three.js 是首选。它直接在浏览器里跑改完代码刷新就能看导出 GLTF 也方便。AI 对 Three.js 的 API 很熟CylinderGeometry、BoxGeometry、SphereGeometry、ExtrudeGeometry、Group、MeshStandardMaterial这些模式它基本不会写错。你还能用OrbitControls做旋转查看用GLTFExporter导出模型。Three.js 的短板是复杂建模能力有限布尔运算需要额外库修改器生态不如 Blender。它更适合“由基本几何拼装”的模型比如台灯、桌子、展示架、包装盒、简单机械结构。如果你需要倒角、阵列、曲面细分、布尔并集Three.js 也能做但代码会更长。上篇的实操一就用它因为环境轻浏览器直接跑适合快速验证思路。3.2 工业、打印方向选 OpenSCAD 或 Blender Python如果模型最终要 3D 打印OpenSCAD 很香。它的代码几乎就是参数化建模的语言cube、cylinder、difference、union、hull这些操作直观AI 写起来也稳。缺点是渲染和材质预览弱不适合做漂亮展示。你要是做机械零件、外壳、支架、夹具OpenSCAD 加 AI 写代码是很顺的组合。Blender Python 则更全能。它能调用 Blender 的建模、修改器、材质、渲染、导出全流程。你可以让 AI 写脚本用bpy.ops.mesh.primitive_cylinder_add创建原语用修改器加倒角、阵列、布尔最后导出 GLTF 或 STL。Blender 还能无头运行适合批量生成。缺点是 API 细节多AI 偶尔会写出过时参数需要你对着版本改。上篇实操二用它演示批量导出同系列模型。3.3 环境准备清单与避坑建议工具用途安装方式避坑点Node.js跑 Three.js 构建工具官网 LTS 版版本不要太老Vite 对 Node 有要求Vite前端开发服务器npm create vite模板选 vanilla 就够别上复杂框架Three.jsWeb 3D 几何与导出npm install three导出器路径在 examples/jsm 下BlenderPython 建模与批量导出官网下载 LTS脚本 API 随版本变先查版本Python运行 Blender 脚本随 Blender 内置不要混用系统 Python 和 Blender PythonGit版本管理系统安装大文件用 LFS别把 GLB 直接提交VS Code写代码和调试官网下载装 Blender 插件可补全 bpy环境准备最容易踩的坑是版本错配。Three.js 的导出器路径、Blender 的导出参数、Node 的版本要求都会随版本变化。我的建议是先锁定一个稳定版本项目里写清楚版本号AI 生成的代码如果报错先把错误信息和版本号一起发给它让它按你的版本改。不要让它自由发挥最新 API否则你会在兼容性上耗很久。3.4 为什么上篇先用 Three.js 和 Blender Python 两条线上篇选择这两条线是因为它们覆盖了大多数人的需求。Three.js 负责浏览器里的实时预览和 GLTF 导出适合前端、产品展示、在线配置。Blender Python 负责批量生成和更专业的导出适合设计、工程、3D 打印。两条线共用同一个思路AI 写参数化代码人管参数和验收。你学会其中一条另一条迁移很快。OpenSCAD 我会在下篇补上因为它和 3D 打印结合太紧值得单独讲。上篇先把“为什么不要让 AI 直接吐模型”和“怎么让 AI 写代码”这两件事讲透。只要这个思维转过来后面换什么工具都是同样的套路。4. 实操一用 Three.js 让 AI 写出参数化台灯底座并导出 GLTF4.1 项目初始化与依赖安装先建一个最小前端项目。用 Vite 的 vanilla 模板就够不需要 React 或 Vue避免额外复杂度。打开终端执行npm create vitelatest ai-code-3d -- --template vanilla cd ai-code-3d npm install three npm run dev跑起来后浏览器会打开一个默认页面。把main.js里的内容清掉换成 Three.js 场景。这里不要急着让 AI 写完整台灯先让它写一个立方体加相机和灯光确认渲染正常。这个最小验证能排除环境问题后面台灯代码报错时你就知道不是环境问题。4.2 参数表设计先定尺寸再写几何参数表是代码化建模的核心。不要一边写几何一边拍脑袋定尺寸先把参数列出来。下面是我常用的台灯参数表参数含义示例值单位baseRadius底座半径0.6米baseHeight底座高度0.12米poleRadius灯杆半径0.035米poleHeight灯杆高度1.2米shadeBottomRadius灯罩下半径0.5米shadeTopRadius灯罩上半径0.32米shadeHeight灯罩高度0.35米segments圆周分段64个这些参数定了代码里的几何位置就能算出来。底座中心在baseHeight / 2灯杆中心在baseHeight poleHeight / 2灯罩中心在baseHeight poleHeight shadeHeight / 2。先把这些计算写进注释再让 AI 写代码它就不容易把部件叠错位置。很多人让 AI 直接生成结果灯罩悬空或灯杆穿过底座就是因为没有先把坐标关系定清楚。4.3 核心代码生成底座、灯杆、灯罩下面这段 JavaScript 可以作为起点。它创建一个Group把底座、灯杆、灯罩放进去。材质分开定义方便后面单独改。所有尺寸来自params改数字就能改模型。import * as THREE from three; import { GLTFExporter } from three/examples/jsm/exporters/GLTFExporter.js; const params { baseRadius: 0.6, baseHeight: 0.12, poleRadius: 0.035, poleHeight: 1.2, shadeBottomRadius: 0.5, shadeTopRadius: 0.32, shadeHeight: 0.35, segments: 64 }; const group new THREE.Group(); group.name ParametricLamp; const baseMat new THREE.MeshStandardMaterial({ color: 0x2f3640, roughness: 0.6, metalness: 0.2 }); const poleMat new THREE.MeshStandardMaterial({ color: 0xbdc3c7, roughness: 0.25, metalness: 0.85 }); const shadeMat new THREE.MeshStandardMaterial({ color: 0xf5f6fa, roughness: 0.9, metalness: 0.0, side: THREE.DoubleSide, transparent: true, opacity: 0.82 }); const base new THREE.Mesh( new THREE.CylinderGeometry( params.baseRadius, params.baseRadius * 1.08, params.baseHeight, params.segments ), baseMat ); base.name Base; base.position.y params.baseHeight / 2; group.add(base); const pole new THREE.Mesh( new THREE.CylinderGeometry( params.poleRadius, params.poleRadius, params.poleHeight, params.segments ), poleMat ); pole.name Pole; pole.position.y params.baseHeight params.poleHeight / 2; group.add(pole); const shade new THREE.Mesh( new THREE.CylinderGeometry( params.shadeTopRadius, params.shadeBottomRadius, params.shadeHeight, params.segments, 1, true ), shadeMat ); shade.name Shade; shade.position.y params.baseHeight params.poleHeight params.shadeHeight / 2; group.add(shade);这段代码没有用复杂几何但已经能满足“参数化台灯”的基本要求。注意灯罩用了openEnded true这样上下通透看起来更像灯罩。材质用了DoubleSide避免从内部看时消失。底座下半径略大于上半径形成一点收分视觉上更稳。这些都是小细节但 AI 如果不知道默认参数会显得很呆。4.4 导出 GLTF 与本地预览模型建好后加相机、灯光和渲染循环你就能在浏览器里旋转查看。导出 GLTF 用GLTFExporter。下面这段代码把group导出为 glTF 文件点击按钮即可下载function exportGLTF(input, filename lamp.gltf) { const exporter new GLTFExporter(); exporter.parse( input, (result) { const output JSON.stringify(result, null, 2); const blob new Blob([output], { type: model/gltfjson }); const url URL.createObjectURL(blob); const link document.createElement(a); link.href url; link.download filename; link.click(); URL.revokeObjectURL(url); }, (error) { console.error(GLTF export error:, error); }, { binary: false } ); } exportGLTF(group, parametric-lamp.gltf);如果你想要 GLB把binary改成true导出结果就是二进制 GLB文件更小贴图和材质打包更方便。导出后把文件拖进 Blender 或在线 glTF 查看器检查尺寸、材质和命名。重点看三件事原点是否在底座中心、灯杆是否垂直、灯罩是否和灯杆对齐。如果原点不在底部后面放进场景会很难摆。4.5 实操心得先锁坐标再谈造型这个实操最大的价值不是台灯本身而是让你体会“先锁坐标再谈造型”的流程。很多 AI 生成 3D 模型翻车是因为它把造型和坐标混在一起猜。代码化建模把坐标关系显式写出来底座在什么高度、灯杆从哪开始、灯罩中心在哪全都可以计算。造型可以后面慢慢调坐标错了整个模型就废了。另外segments不要一上来给太高。64 段圆周已经足够平滑给到 256 段会让面数暴涨导出文件变大浏览器预览变卡。面数预算要一开始就定比如 2 万三角面以内。如果超了优先降分段、减少透明材质、合并重复几何。这些在代码里都是改一个数字的事比在网格里手工减面轻松太多。5. 实操二用 Blender Python 脚本批量生成同系列模型5.1 Blender 无头模式运行脚本Blender 可以不打开界面直接在命令行跑 Python 脚本。这个模式特别适合批量生成。你先写一个make_lamp.py然后在终端执行blender --background --python make_lamp.py如果你的 Blender 没加入系统路径就用完整路径。Windows 大概是C:\Program Files\Blender Foundation\Blender 4.x\blender.exe --background --python make_lamp.py。macOS 和 Linux 类似。无头模式跑通后你就能用循环批量生成几十个变体不用手动点界面。对做产品系列、不同尺寸版本、多材质版本的人来说这一步能省大量重复劳动。5.2 参数化函数拆解下面是一个简化的 Blender Python 脚本。它先清空场景然后根据参数创建底座、灯杆和灯罩。注意 Blender 的默认单位是米primitive_cylinder_add的depth是高度location是中心点。代码里的坐标计算和 Three.js 那套一致。import bpy def clear_scene(): bpy.ops.object.select_all(actionSELECT) bpy.ops.object.delete(use_globalFalse) def make_lamp(base_radius0.6, base_height0.12, pole_radius0.035, pole_height1.2, shade_bottom_radius0.5, shade_top_radius0.32, shade_height0.35): clear_scene() bpy.ops.mesh.primitive_cylinder_add( radiusbase_radius, depthbase_height, location(0, 0, base_height / 2) ) base bpy.context.object base.name Base bpy.ops.mesh.primitive_cylinder_add( radiuspole_radius, depthpole_height, location(0, 0, base_height pole_height / 2) ) pole bpy.context.object pole.name Pole bpy.ops.mesh.primitive_cone_add( radius1shade_bottom_radius, radius2shade_top_radius, depthshade_height, location(0, 0, base_height pole_height shade_height / 2) ) shade bpy.context.object shade.name Shade return base, pole, shade make_lamp()这段脚本跑完场景里会出现三个部件。你可以在 Blender 界面里检查也可以继续用脚本导出。注意primitive_cone_add的radius1是底部半径radius2是顶部半径。灯罩上下开口的话还要加end_fill_typeNOTHING但不同版本参数略有差异建议先跑默认再按需改。5.3 批量导出 GLTF/STL批量生成的核心是把参数放在列表里循环调用make_lamp然后导出。下面这段代码生成三种高度的灯分别导出 GLTFimport os import bpy from make_lamp import make_lamp output_dir os.path.abspath(./out) os.makedirs(output_dir, exist_okTrue) variants [ {pole_height: 0.9, name: lamp_short}, {pole_height: 1.2, name: lamp_standard}, {pole_height: 1.5, name: lamp_tall} ] for variant in variants: make_lamp(pole_heightvariant[pole_height]) filepath os.path.join(output_dir, f{variant[name]}.gltf) bpy.ops.export_scene.gltf( filepathfilepath, export_formatGLTF_SEPARATE ) print(exported:, filepath)export_format可以选GLB、GLTF_SEPARATE或GLTF_EMBEDDED。做 Web 展示我更推荐 GLB单文件、体积小、材质打包完整。做需要单独编辑贴图的流程用GLTF_SEPARATE。导出 STL 也类似把export_scene.gltf换成export_mesh.stl注意 STL 不带材质和层级只保留网格。3D 打印前要用切片软件检查壁厚和法线。5.4 踩坑坐标系、材质、单位Blender 的 Z 轴向上Three.js 默认 Y 轴向上。导出 GLTF 时Blender 会自动转换坐标系但如果你在代码里手工算了位置导入 Three.js 后可能发现模型躺倒了。解决办法是统一在 Blender 里按 Z 轴向上建模导出 GLTF 后由查看器处理或者导出前加一个旋转。不要两边各算一套坐标很容易乱。材质也是个坑。Blender 的 Principled BSDF 导出 GLTF 时大部分基础参数能保留但复杂节点、程序纹理、修改器生成的材质可能丢失。简单项目里材质只用基础色、粗糙度、金属度、透明度导出最稳。单位方面Blender 默认 1 单位等于 1 米如果你按毫米建模导出后模型会巨大或极小。建议一开始就确定单位并在脚本注释里写清楚。6. 常见问题与排查技巧实录6.1 模型看起来对但一导出就烂最常见的情况是预览正常导出 GLTF 后材质发黑、部件丢失、尺寸不对。原因通常有三个材质用了导出器不支持的节点模型层级里有空物体或相机单位比例不一致。排查顺序是先导出最小立方体测试确认导出器正常再逐个隐藏部件看是哪个部件导致问题最后检查材质节点只保留基础色、金属度、粗糙度、透明度。大多数导出问题都能这样定位。还有一个隐藏问题是法线。Three.js 和 Blender 里预览时双面材质会掩盖法线反向导出到某些引擎后单面渲染就会看到模型缺面。解决办法是在 Blender 里开启面朝向检查或在 Three.js 里用side: THREE.FrontSide查看。如果发现黑面可以在 Blender 里重算法线或者在代码里用geometry.computeVertexNormals()。别等到引擎里才发现排查成本会翻倍。6.2 AI 代码跑不通时怎么问AI 写代码跑不通很正常尤其是 Blender API 和 Three.js 导出器这种版本敏感的部分。不要只发一句“报错了”。把完整错误信息、你的工具版本、你期望的行为、实际结果一起发给它。比如“Blender 4.2运行bpy.ops.export_scene.gltf报错TypeError: Converting py args to operator properties: enum “GLTF_SEPARATE” not found我想导出分离式 glTF应该怎么改”这种问法AI 更容易给出准确修改。如果 AI 连续两次改不对不要继续在同一个提示词里打转。把代码缩小到最小可复现版本只保留出错的那几行再问。很多时候问题不在 AI而在环境版本或上下文太长。你也可以让 AI 先解释它为什么这么写再让它给替代方案。不同 API 版本有不同写法多要一个备选方案往往能直接跑通。6.3 常见问题速查表现象可能原因排查动作解决方向导出后材质发黑材质节点导出器不支持换成基础材质再导出只用 Principled BSDF 基础参数模型导入后躺倒坐标系不一致检查 Z 轴和 Y 轴统一建模坐标导出时转换模型尺寸巨大单位按毫米当米看包围盒尺寸统一用米或导出时缩放部件丢失层级中有空物体或未选中检查导出选项只导出目标集合合并层级面数过高分段数太大查看三角面数量降 segments减少透明材质灯罩悬空坐标计算错检查各部件中心 Y 值按底座、灯杆、灯罩顺序累加高度导出文件打不开格式或扩展名错换查看器测试GLB 用二进制glTF 用 JSONAI 代码报错API 版本不匹配提供版本和完整错误让 AI 按指定版本改写6.4 几个让我少加班的小习惯第一个习惯是每次只让 AI 改一个变量。比如先跑通底座再加灯杆再加灯罩再加材质再加导出。这样出问题时你知道是刚加的那一步。第二个习惯是参数和代码分文件参数用 JSON代码只读参数。改尺寸不用碰代码也不用重新问 AI。第三个习惯是导出后一定用第二个工具再导入一次Three.js 导出的 GLTF 丢进 Blender 看Blender 导出的 GLB 丢进浏览器看。交叉验证能发现很多单工具里看不到的问题。第四个习惯是给模型加命名和原点检查。部件命名要统一底座叫 Base灯杆叫 Pole灯罩叫 Shade不要出现Cylinder.001这种名字。原点尽量放在模型底部中心这样放进场景、做动画、做碰撞体都方便。代码化建模的好处就在这里命名和原点可以在脚本里强制写死而不是靠手工调整。你把这些习惯坚持下来AI 写代码生成 3D 内容的流程会越来越顺。上篇先聊到这下篇我会把 OpenSCAD 的参数化零件、批量导出 STL、以及怎么把 AI 写的代码接进现有项目管线继续拆开讲。
返回列表