举着 Quest,对着桌上的杯子按下扳机。
几十秒后,一个可以拿在手里转的杯子出现在你面前。
这就是这个项目要做的事:把生成式 3D 接进 XR 运行时。
也正因为"接进"两个字,它比听起来难得多。
为什么难
把 2D 生成接进网页,你只要拿回一张图,塞进一个 image 标签就行。
但把 3D 生成接进 XR,你要面对三个运行时约束:
① 材质不能在运行时可靠重建
生成式管线经常把材质烘进 shader graph。而在 XR 渲染管线里,动态加载的 shader graph 无法可靠还原——它依赖编辑器期的编译结果。
② 生成模型的坐标是不可信的
自动生成的模型,pivot 位置随机、坐标原点不规则、内部结构千奇百怪。直接拿它的 pivot 去转,你会看到模型一边转一边漂。
③ 尺度完全不可控
有的模型生成出来像指甲盖,有的大得像房子。
这三个问题都不是"模型生成得不好"——是生成结果和 XR 运行时之间的接口没有对齐。
系统的三个部分

整个系统分三块:
| 组件 | 跑在哪 | 职责 |
|---|---|---|
| XR 运行时 | Meta Quest 一体机(Unity) | 透视取景、交互控制、运行时装配与渲染 |
| 服务端编排 | 轻量控制器 | 工作流协调、状态管理、资产路由 |
| 生成管线 | ComfyUI + Hunyuan3D | 所有计算密集的生成任务 |
通信走 REST:异步上传、状态轮询、资产拉取。
整个系统完全在运行时工作——不需要任何离线预处理或编辑器期资产准备。这是刻意的:真实部署的 XR 应用就是这么跑的。
决策一:在生成端就把几何和纹理拆开
这是整个系统最核心的一条。
常规做法是生成一个带材质的整体 GLB,丢进引擎直接用。
我们没这么做。
ComfyUI 工作流最后输出两个独立的东西:
- 一个只含几何的 GLB 网格
- 一张纹理图片
然后在 XR 运行时里,显式创建一个原生渲染材质,把纹理绑到对应的输入通道上。
为什么
因为这样绕过了隐式的 shader 编译机制。
一旦材质是烘在资产里的,你就把控制权交给了资产生成方。而生成方给的材质,在 XR 运行时里未必能加载、未必能编译、也未必要得。
拆开之后,材质绑定变成一个确定性的、你可以完全控制的步骤。
一句话:牺牲了"资产自包含"的便利,换来了运行时行为的确定性。
决策二:运行时显式装配
生成结果不会直接扔进场景。
系统走一条显式装配流程:
- 收到两个独立资产(GLB + 纹理)
- 实例化原生材质、绑定纹理
- 挂到转盘锚上(下一节)
- 自动归一化:用包围盒估算空间尺寸,算出一个统一缩放系数,让模型落进一个预设的"感知尺寸区间"
- 挂到更高层的空间根节点,成为可交互的 XR 对象
第 4 步解决的就是"有的像指甲盖有的像房子"。
而且这个归一化是在运行时算的——不需要人工调、不需要离线预处理。
决策三:转盘锚(Turntable Anchor)
这是解决"pivot 不可信"的答案。
思路很干脆:不去修正模型的 pivot,而是把它挂在一个锚下面。
- 模型自身的局部变换完全不动
- 所有关于"呈现"的变换——旋转、漂浮、缩放——全部作用在锚上
数学上就是:
| |
其中 T_m 是模型的局部变换(不动),T_a 是锚的变换(动态更新)。
为什么这比"修 pivot"好
因为旋转轴只在世界空间里定义,与模型内部几何无关。
于是:
- 模型只会原地震转,不会一边转一边漂
- 不需要依赖任何几何启发式(比如"用包围盒中心当 pivot"——对不对称模型经常算错)
- 缩放、漂浮、透视/虚拟场景切换,全部在锚这一层统一处理
与其去猜一个结构不可信的对象"中心在哪",不如给它一个外部坐标系。
一个很具体的设计问题:什么时候该阻塞用户输入
这个问题看着小,但处理不好会直接打崩服务端。
场景:用户按下扳机触发取景。
问题:什么时候禁止再次触发?
如果等到服务端响应才阻塞——那么在这段时间窗口里,用户可以连续触发多次取景。每一次都会向 ComfyUI 发一个新请求,任务队列直接被压垮。
更麻烦的是输入抖动:一次物理按键,可能产生多个输入事件。这会让系统平白提交好几个重复的生成任务,烧掉大量算力。
解法
在第一个取景命令触发的瞬间就阻塞输入。
然后在上传完成并拿到服务端响应时释放——包括失败和超时的情况。
正常流程下这个阻塞窗口很短,用户基本感知不到。而如果生成成功,阻塞会一直保持到整个工作流结束。
另外还加了帧缓冲与丢弃机制,避免网络延迟导致的错误决策。
这个例子的价值在于:它不是一个算法问题,是一个时序问题。 而时序问题只能靠想清楚状态机来解决。
生成管线

工作流搭在 ComfyUI 里,核心生成模型是 Hunyuan3D-dit-v2.0。
几个关键阶段:
| 阶段 | 做什么 |
|---|---|
| 输入预处理 | 把透视 ROI 图缩放到固定方形分辨率 |
| 去背景 | 剥离环境,得到干净的目标 |
| 几何生成(GenerateMesh) | 图像 → 中间隐表示 → 解码出三维表面 |
| 网格后处理 | 修整产物 |
| 去光照(Delighting) | 把原图的光照信息剥掉,避免"烤"进纹理 |
| 多视角渲染 | 用于纹理采样 |
| 多视角纹理采样 + UV 烘焙 | 把各视角的颜色烘到统一 UV 上 |
去光照这一步很容易被忽略,但很关键。 如果不去,原图里的阴影和高光会被当成材质的一部分烘进去,模型换个光照就露馅。
实测结果

系统在 Meta Quest 一体机上完整跑通。
| 指标 | 实测 |
|---|---|
| 端到端延迟(取景 → 可视化) | 数十秒,取决于管线复杂度和服务端负载 |
| 连续多次生成 | 稳定,无持久性失败或不可恢复状态 |
| 失败/超时 | 正确释放输入阻塞、回到 idle,无需重启应用 |
| 旋转与漂浮动画 | 在不同模型间保持稳定,无轨道运动或漂移 |
| XR 交互 | 生成期间取景/上传被门控,但视角操作仍然响应 |
一个诚实的说明
这个延迟不满足严格的实时约束。
“近实时"在这里指的是:通过异步执行和视觉反馈,让用户在等待期间保持参与感,而不是"几十秒内出结果”。
这个边界我写进了论文,也写在这里。
这个项目真正学到的东西
回头看,最值钱的不是"跑通了 Hunyuan3D"。
而是这三个判断:
- 接口比模型重要。 生成质量再好,如果和运行时的接口对不齐,也用不了。
- 不可信的东西,给它一个外部参照。 pivot 不可信就给锚,尺度不可控就归一化。
- 先想时序,再写代码。 输入阻塞那个问题,想清楚了只有十行代码;没想清楚会让服务端崩溃。
技术栈
Unity · Meta Quest · OpenXR · C# · ComfyUI · Hunyuan3D-dit-v2.0 · Python / Flask · REST 异步编排
论文:A System for Near Real-Time Generation and Runtime Visualization of Generative 3D Models in XR Environments(IEEE 会议格式)
最后一句是我写在论文里的判断:
这套系统的价值不在于生成得多好,而在于它把一条不可控的生成管线,接上了一个必须可控的运行时。