
Grok Build 将 2D 户型图转为 3D 漫游听起来像是把一张户型图丢进工具就能得到一套可以走进走出的三维空间。实际处理过这类需求的人都知道真正的工作量往往不在模型生成而在图纸解析、几何校正、漫游体验和工程化接入。这里以 Grok Build 为例拆解一条从 2D 户型图到可交互 3D 漫游的完整链路并提供可复用的示例代码和排查路径。适合的读者包括想在页面里接入户型 3D 展示的 Web 前端工程师正在做室内设计工具的产品和研发需要做 BIM 可视化转型的技术团队以及想借助 AI 工具减少重复建模工作的独立开发者。阅读前不需要掌握复杂的图形学知识但需要了解 HTML、JavaScript 和基本的命令行操作。下文会先从原理上说明 2D 户型图转 3D 为什么不是“一键生成”这么简单再进入环境准备、转换调用、Web 漫游实现、验证排错最后给出一份适合生产环境落地的检查清单。1. 先理解 2D 户型图转 3D 漫游的技术链路1.1 2D 转 3D 的难点不在“拉伸”而在语义理解很多第一次接触这个需求的人会以为把户型图中的墙线识别出来再设置一个层高向上“拉”出墙体就完成了。算法层面确实存在这一步但真正的难点是算法如何知道一根线段是墙体、一条弧线是窗、一个矩形是门洞哪些区域属于客厅哪些属于卫生间。一张常见的 PNG 户型图本质上只是像素点阵没有墙、门、窗、房间这些语义信息。即使是一份 DXF 或 PDF 矢量图里面也可能只有线段、弧线、填充色块没有明确标注“这是卫生间门”或“这是承重墙”。Grok Build 这类工具要做的就是先对输入图纸做语义识别再生成可编辑、可渲染的几何数据。如果输入图纸不够清晰或墙体被家具遮挡识别结果就可能出现断墙、漏门、房间合并等错误。所以工程上更合适的理解是2D 户型图转 3D 漫游是一条由“图纸解析、语义识别、几何生成、场景编排、前端漫游”组成的技术链路而不是单个模型转换命令。1.2 一条完整链路包含哪些环节把整条链路拆开看大致包含下面几个阶段输入处理读取 PNG、JPG、DXF、PDF 等格式的户型图统一坐标系调整图纸方向。墙体识别通过图像分割或矢量分析提取墙线、柱体、承重墙轮廓。门窗识别区分门洞、落地窗、普通窗、推拉门并记录位置和朝向。空间划分根据墙体和门洞闭合关系生成房间区域标记客厅、卧室、厨房、卫生间等语义。几何生成设置层高、墙厚将二维轮廓拉伸为三维墙体再补充门窗洞口。材质与光照为地板、墙面、玻璃、门板分配材质生成基础贴图和光照信息。烘焙与导出输出 GLB、glTF、3D Tiles 等格式必要时附带 JSON 语义数据。客户端漫游在 Web 中加载模型接入第一人称相机控制、碰撞检测和交互逻辑。实际项目中阶段 1 到阶段 6 通常由 Grok Build 这类 AI 建模工具完成阶段 7 需要根据落地场景选择格式阶段 8 则是前端工程师的主要工作。这样分工的好处是不把前端页面和算法引擎强耦合模型生成一次可以在 Web、小程序、移动端多处复用。1.3 为什么要单独设计“漫游”环节生成三维模型只是第一步。用户进入户型后需要像在真实房间中行走一样移动这时就出现两个新问题相机不能随意穿过墙体观察视角必须符合人的身高和视野同时模型加载速度不能太慢否则首屏体验会很差。漫游体验涉及相机控制、碰撞检测、视线高度、移动速度、门洞通过条件等多个参数。这些参数并不来自 Grok Build 的输出而是由前端运行时根据业务场景决定。正因为如此不能把 Grok Build 的输出直接当成最终页面。模型是“静态资产”漫游是“动态交互”两者必须配合。2. 环境准备与输入数据整理2.1 依赖和版本建议在开始转换之前先确认使用环境。由于 Grok Build 的版本更新较快下面的安装命令和参数以“常见 CLI 形态”展示落地前先通过--help或官方文档确认当前版本。用途推荐工具版本建议说明运行示例脚本Node.js18 及以上原生支持 fetch、FormData脚本更简洁包管理npm 或 pnpm最新稳定版前端工程需要3D 渲染Three.jsr150 及以上示例代码基于 Three.js 新 API模型检查gltf-transform最新版用于查看 GLB 面数、贴图、尺寸可选编辑器Blender3.6 及以上需要二次调整材质时使用需要注意这里的前端渲染方案选用 Three.js是因为它对 GLB/glTF 格式支持成熟社区资料丰富。如果团队已经有 Unity 或 Unreal 管线也可以复用 Grok Build 导出的模型但漫游控制需要换成对应引擎的原生方案。2.2 输入户型图的质量要求输入图片的质量直接影响识别结果。按经验看至少需要满足以下条件图纸内容完整墙体线没有大面积断线。图片分辨率建议不低于 1024×768扫描件尽量做去噪处理。如果有很多彩色家具和装饰素材先确认工具是否支持自动忽略否则会干扰墙体识别。矢量 DXF 文件需要提前清理多余图层减少文字标注和外部参照的干扰。图纸方向尽量统一入口朝下或朝左方便后续坐标对齐。如果原始材料没有明确给出“Grok Build 支持哪些输入格式”最稳妥的做法是先用 PNG 或 JPG 跑通流程再验证 DXF 等矢量格式。栅格图对普通用户最友好矢量图虽然精度高但图层整理成本也更高。2.3 最小工程结构先建立一个干净的目录用它同时存放输入文件、转换脚本和前端页面。floor-plan-to-3d/ ├── input/ │ └── apartment.png ├── output/ │ └── apartment.glb ├── scripts/ │ └── convert.mjs └── web/ ├── index.html ├── package.json └── src/ └── main.jsinput目录放原始户型图output目录放转换后的模型和语义数据scripts目录放调用 Grok Build 的脚本web目录是前端漫游工程。这样划分的好处是转换过程、模型资产、前端代码互不干扰后续 CI/CD 发布时可以把output目录作为静态资源直接分发。3. 使用 Grok Build 完成 2D 到 3D 转换3.1 调用前先确认服务地址和凭证Grok Build 可能有 CLI 和 HTTP API 两种接入方式。学习环境建议先用 CLI因为参数少容易看到失败原因。生产环境则更适合通过 API 集成到后端服务由后端统一管理凭证、任务队列和回调。先检查 CLI 是否可用grok-build --version如果输出版本号说明命令已经安装。如果没有说明可执行文件不在 PATH 中需要先完成安装或在命令中使用完整路径。不要在这里跳过很多后续报错都是因为命令找不到。API 方式需要准备三个信息接口地址、API Token、输入文件路径。常见做法是把它们写入环境变量避免把密钥提交到代码仓库。export GROK_BUILD_APIhttps://your-grok-build-endpoint.example/v1/convert export GROK_BUILD_TOKENyour-token这里使用.example域名作为占位符实际操作时替换成平台提供的真实地址。3.2 用 CLI 完成一次最小转换当 CLI 可用时最小命令类似这样grok-build convert \ --input input/apartment.png \ --output output/apartment.glb \ --unit mm \ --height 2800 \ --format glb参数含义如下--input输入户型图路径。--output输出模型路径。--unit源图/输出模型使用的单位常见为 mm、cm、m。--height层高单位跟随--unit2800 表示 2.8 米层高。--format导出格式Web 场景推荐 glb。命令执行后先看output目录是否生成文件。如果只生成了空目录说明转换失败需要把标准错误输出截图或复制到文本中继续排查。3.3 通过 HTTP API 接入业务系统团队如果打算把转换能力做成一个内部服务建议用 API 而不是 CLI。下面是一个最小 Node.js 示例使用 fetch 上传文件并接收返回的模型地址。// scripts/convert.mjs import fs from node:fs; import path from node:path; import { fileURLToPath } from node:url; const __dirname path.dirname(fileURLToPath(import.meta.url)); const inputPath path.resolve(__dirname, ../input/apartment.png); const apiUrl process.env.GROK_BUILD_API; const token process.env.GROK_BUILD_TOKEN; if (!apiUrl || !token) { console.error(缺少 GROK_BUILD_API 或 GROK_BUILD_TOKEN 环境变量); process.exit(1); } const fileBuffer fs.readFileSync(inputPath); const formData new FormData(); formData.append( file, new Blob([fileBuffer], { type: image/png }), path.basename(inputPath) ); formData.append(unit, mm); formData.append(height, 2800); formData.append(format, glb); const response await fetch(apiUrl, { method: POST, headers: { Authorization: Bearer ${token}, }, body: formData, }); if (!response.ok) { console.error(请求失败, response.status, await response.text()); process.exit(1); } const result await response.json(); console.log(转换完成产物地址, result.model_url);这段脚本用到了 Node 18 内置的fetch、FormData、Blob不需要额外安装请求库。关键点在于文件以 FormData 方式上传API Token 放在 Authorization 请求头中响应体里读取模型下载地址。异步转换的接口往往不会立刻返回模型而是返回一个task_id。此时脚本要做的是轮询任务状态或者让后端在转换完成后回调通知。轮询逻辑可以写成async function waitForTask(taskId, intervalMs 2000, timeoutMs 120000) { const start Date.now(); while (Date.now() - start timeoutMs) { const res await fetch(${apiUrl}/tasks/${taskId}, { headers: { Authorization: Bearer ${token} }, }); const data await res.json(); if (data.status succeeded) return data; if (data.status failed) throw new Error(data.error || 转换失败); await new Promise((resolve) setTimeout(resolve, intervalMs)); } throw new Error(任务超时); }生产环境不建议前端直接调用转换 API因为密钥容易泄露而且文件上传和大模型转换耗时较长浏览器连接很容易中断。正确做法是后端请求转换前端只接收任务 ID 并展示进度。3.4 关键参数速查表下面表格汇总了 2D 转 3D 常见参数具体名称和默认值以实际平台为准。参数含义常见值调大影响调小影响推荐场景resolution识别分辨率1024、2048细节更丰富耗时增加速度快但门窗容易漏检图纸复杂时用 2048confidence语义识别置信度0.5 - 0.8漏检少但可能生成多余墙误检少但门洞可能丢失看图纸质量调整height层高2800 mm空间更空旷空间压抑住宅按建筑图wall_thickness墙厚200 mm结构更粗视觉更轻按项目规范unit单位mm、cm、m影响尺寸比例影响尺寸比例和图纸原单位保持一致format导出格式glb、gltf、3dtilesglb 体积小gltf 便于调参Web 用 glb容易踩坑的是unit。如果原图标注单位是毫米但转换时设置成了米模型在国内尺寸上会差 1000 倍进入前端后要么模型巨大要么小到看不见。所以转换前先确认原图单位转换后立刻检查模型包围盒尺寸。3.5 转换输出产物不只有模型Grok Build 的产物里通常不只有 GLB 文件还可能包含语义 JSON记录每个房间名称、中心点、面积、门宽、窗高。材质纹理地板、墙面、玻璃等贴图。碰撞体数据用于前端判断是否可以通行。缩略图方便列表页展示。拿到产物后先检查是不是存在apartment.semantic.json或类似的辅助文件。这些数据会让漫游功能好做很多例如自动标注房间名称、从门洞中心生成路径点、计算房间面积等。4. 在 Web 场景中实现 3D 漫游4.1 创建一个 Vite Three.js 工程模型转换完成后进入前端环节。先创建一个 Web 工程并安装 Three.js。npm create vitelatest web -- --template vanilla cd web npm install three然后把转换生成的apartment.glb复制到web/public/models/目录。Vite 会把public目录下的文件直接作为静态资源输出浏览器加载时路径就是/models/apartment.glb。4.2 加载 GLB 模型使用 Three.js 的GLTFLoader加载模型并加入合适的光照。户型模型通常有室内光照需求至少需要一盏环境光和方向光。// web/src/main.js import * as THREE from three; import { GLTFLoader } from three/addons/loaders/GLTFLoader.js; import { PointerLockControls } from three/addons/controls/PointerLockControls.js; const scene new THREE.Scene(); scene.background new THREE.Color(0x111122); const camera new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); camera.position.set(0, 1.6, 5); const renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled true; document.body.appendChild(renderer.domElement); const ambientLight new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const dirLight new THREE.DirectionalLight(0xffffff, 1.0); dirLight.position.set(5, 10, 5); scene.add(dirLight); const controls new PointerLockControls(camera, renderer.domElement); const loader new GLTFLoader(); loader.load(/models/apartment.glb, (gltf) { const model gltf.scene; scene.add(model); console.log(模型加载完成); });这段代码完成了三件事创建场景和相机加入基础光照加载 GLB 模型。此时页面只是把模型显示出来还不能漫游。下一步需要处理鼠标锁定和键盘移动。4.3 加入第一人称漫游与碰撞检测漫游最直接的方案是使用PointerLockControls。点击画布进入鼠标锁定状态鼠标控制视角WASD 控制移动。这里最关键的是碰撞检测。简单做法是在尝试移动前从相机位置向目标移动方向发射一条射线如果射线提前撞到墙体或其他碰撞体就取消本次移动。const raycaster new THREE.Raycaster(); const cameraObject controls.getObject(); function canMove(direction) { raycaster.set(cameraObject.position, direction.clone().normalize()); const intersects raycaster.intersectObjects(colliders, true); if (intersects.length 0) return true; return intersects[0].distance 0.5; }先收集可碰撞对象。理想情况下Grok Build 会单独导出一组碰撞体或者把墙体网格放在一个分组中。如果没有就手动收集所有墙体const colliders []; let modelRoot null; loader.load(/models/apartment.glb, (gltf) { modelRoot gltf.scene; scene.add(modelRoot); modelRoot.traverse((child) { if (child.isMesh child.name.toLowerCase().includes(wall)) { colliders.push(child); } }); });键盘移动逻辑可以这样写const keyState {}; document.addEventListener(keydown, (e) { keyState[e.code] true; }); document.addEventListener(keyup, (e) { keyState[e.code] false; }); function getMoveDirection() { const forward new THREE.Vector3(); const right new THREE.Vector3(); cameraObject.getWorldDirection(forward); right.crossVectors(forward, cameraObject.up).normalize(); const move new THREE.Vector3(); if (keyState[KeyW]) move.add(forward); if (keyState[KeyS]) move.sub(forward); if (keyState[KeyD]) move.add(right); if (keyState[KeyA]) move.sub(right); return move; } function update() { requestAnimationFrame(update); const move getMoveDirection(); if (move.lengthSq() 0) { move.y 0; move.normalize(); const speed 2.2; move.multiplyScalar(speed); if (canMove(move)) { cameraObject.position.add(move); } } renderer.render(scene, camera); } controls.lock(); update();这个实现的关键点是移动前先检测方向碰撞距离阈值设为 0.5 米左右。阈值太大会在门前被挡住太小会穿模或贴墙。0.5 到 0.8 之间通常比较合适实际项目需要根据模型单位调整。4.4 材质、光照和渲染性能优化户型推荐开启阴影但对移动端要谨慎。阴影贴图非常消耗显存在手机端会造成明显掉帧。可以这样处理const dirLight new THREE.DirectionalLight(0xffffff, 1.0); dirLight.castShadow true; dirLight.shadow.mapSize.set(1024, 1024);如果墙面白模太过单调可以在 Three.js 中重新给地板和墙体赋材质或直接使用 Grok Build 导出的 PBR 材质。注意 GLB 中的贴图尺寸过大时建议先用gltf-transform压缩npx gltf-transform optimize output/apartment.glb output/apartment-optimized.glb这条命令会压缩几何、纹理和元数据显著减小模型体积。生产环境建议把优化后的模型作为线上发布资产。5. 运行验证与常见问题排查5.1 确认转换结果是否正确模型能加载出来不意味着结果正确。建议按下面的顺序验证尺寸校验在 Three.js 中打印模型包围盒确认长宽高接近真实户型。门窗校验把相机移动到门洞前确认高度可以通行。墙体校验观察外墙、内墙、阳台栏板是否完整。材质校验检查地板、玻璃、门板贴图是否对应。性能校验打开浏览器开发者工具记录加载时间、帧率和显存占用。可以用一段代码打印包围盒const box new THREE.Box3().setFromObject(modelRoot); const size box.getSize(new THREE.Vector3()); console.log(模型尺寸${size.x} x ${size.y} x ${size.z});如果单位设置正确一个两居室的户型长宽大约在 10 到 15 米之间层高约为 2.8 米。如果打印结果是 10000 或者 0.01多半是单位配置错误。5.2 常见问题排查表问题现象常见原因检查方式处理建议模型加载失败路径错误或跨域限制打开 Network 面板看请求状态确认模型文件在 public 下资源域名开启 CORS模型尺寸偏大或偏小单位配置错误打印包围盒尺寸重新设置 unit 并转换墙体重叠或出现多余墙体原始图纸有重复线放大原始图检查线宽和图层清理图纸后再次转换门洞识别为墙体confidence 过高降低识别置信度阈值调整参数并重新转换漫游时穿墙未加入碰撞检测检查 colliders 数组是否为空加入碰撞检测并确认墙体网格被收集浏览器掉帧模型面数过高、贴图过大查看 draw calls 和显存占用使用 gltf-transform 压缩模型减少阴影玻璃材质过亮或过暗光照参数不合适调整方向光和环境光强度引入环境贴图或调低金属度5.3 典型错误日志分析常见的第一类错误是模型请求直接失败控制台显示类似Failed to fetch GET http://localhost:5173/models/apartment.glb 404 (Not Found)原因通常是文件没有复制到web/public/models/或文件名不一致。确认路径后刷新即可。第二类错误出现在加载阶段THREE.GLTFLoader: Error loading Error: Cannot read properties of undefined (reading extensionsUsed)这通常说明目标文件不是标准 GLB或者文件损坏。可以把文件拖入 Blender 或gltf-validator做校验。第三类错误是转换 API 返回认证失败401 Unauthorized检查 API Token 是否过期是否写到了正确的位置。生产环境建议使用专门的读写权限 Token并设置过期周期不要使用所有人共享的超级凭证。5.4 要注意的三个高频坑第一个坑是直接把带有多余装饰的彩色户型图交给转换工具。沙发、植物、文字标注会被识别成墙体或障碍物导致最终户型被割裂。推荐先提供“结构平面图”而不是“精装彩平图”。第二个坑是前端拿到 GLB 后直接使用原始贴图。工程中很容易出现一张地板纹理是 4096×4096而模型只有几十个网格最终体积却高达几十 MB。处理方式是先压缩再按平台要求做多级清晰度列表页用低模详情页用高模。第三个坑是漫游时忽略门洞高度。有的识别结果会把门洞生成得很矮相机高度 1.6 米能通过但 1.8 米会卡住。可以在碰撞检测时把相机碰撞半径设小一点或单独对门洞网格做一次几何检查。6. 从 Demo 到生产最佳实践与扩展方向6.1 学习环境与生产环境的差异学习环境里用人手执行命令、手工复制文件、直接在浏览器访问本地页面都是可接受的。生产环境需要更多考虑关注点学习环境生产环境凭证管理写在脚本里放在密钥管理服务中按环境隔离调用方式页面内直接调用 API后端服务统一调用前端走业务接口模型存储本地文件对象存储开启 CDN 分发性能只看能否加载设置模型体积上限、加载进度、降级策略可观测性控制台日志接入日志、告警和任务链路追踪安全性不关注校验上传文件类型、大小限制并发6.2 上线前检查清单上线前可以对照这份清单快速检查[ ] 输入图纸是否经过脱敏处理避免暴露隐私和设计方版权信息。[ ] 输出 GLB 是否经过压缩体积是否在目标范围内。[ ] 单位、层高、墙厚是否符合项目规范。[ ] 碰撞体是否完整能否从入口走到每个房间。[ ] 移动端和低端设备下的帧率是否可接受。[ ] 模型加载是否有进度提示和失败重试。[ ] API Token 是否仅存储在服务端。[ ] 是否预留了语义 JSON方便后续做房间标签和面积展示。[ ] 发布流程中是否包含自动校验尺寸和模型完整性的脚本。6.3 扩展方向当基础漫游跑通后可以继续扩展三个方向。第一个方向是数据后台化。把 Grok Build 转换任务做成异步服务用户上传户型图后自动排队转换完成后通过 WebSocket 或轮询通知前端。这样能支持批量处理和失败重试。第二个方向是漫游增强。加入自动寻路演示根据语义 JSON 中的门洞位置生成路径点用户点击某个房间后相机沿路径自动移动到目标点。再进一步可以加入 VR 模式或视角切换。第三个方向是交互编辑。用户进入页面后不仅能漫游还能点击墙面修改颜色、替换地板材质、拖拽摆放家具。这些编辑数据可以持久化到后端形成“在线户型改造”产品。从 2D 户型图到 3D 漫游真正的技术价值并不只在“生成模型”那一瞬间。模型生成工具会越来越强但如何设计输入规范、如何管理产物、如何让用户在三维空间中获得稳定流畅的体验仍然是每个开发团队需要自己花时间解决的问题。建议先按最小链路跑通一次再逐步补齐工程化能力。