玻璃杯、塑料瓶、水晶摆件这类透明物体,3DGS 重建基本废掉。我们团队做商品展示项目时,把一只高脚杯拍进去重建,出来的东西像冰柱——实心的,没有折射,没有透光。折腾一周后我们承认:3DGS 在透明物体这件事上,目前有明确的失败边界。这篇文章记录我们测过的边界在哪,以及哪些情况下可以放弃挣扎直接换方案。
能力面:高斯点 alpha 混合表达不了折射
3DGS 的渲染原理是每个高斯点带一个 alpha 值,投影到屏幕后按深度做 alpha 混合叠加。这个模型能表达"半透明"——比如烟雾、毛玻璃那种把背景糊掉的效果。但它表达不了"折射"——光线穿过玻璃改变方向、把背后的景物扭曲重组成像的过程。
折射需要知道入射角、折射率、介质厚度,然后做光线追踪或至少是屏幕空间的折射近似。3DGS 没有这一层。每个高斯点只管"我在这个位置、这个颜色、这个透明度",不管光线怎么走。所以透明物体在 3DGS 里只能被表达成"一团有颜色的半透明体积",而不是"一个会折射光线的实心介质"。
我一开始以为加大采集密度、多拍几圈能救回来。后来发现不行——重建算法本身就不建模折射,给再多数据也拟合不出折射路径,只会把那块区域填成更密的半透明点云,看起来像脏玻璃。团队里有人提议换一组重建参数试试,调了几版发现 SSIM 变化在 0.02 以内,属于噪声范围,放弃。
Spatial Recon Kit 在 API 26 暴露的重建接口里,没有折射相关的参数或开关。我们翻过文档和头文件,确认目前版本不提供任何折射建模能力。这意味着透明物体的失败不是配置问题,是能力缺失。
// ArkTS:当前重建 API 没有任何折射/透明度相关的参数
import { spatialRecon } from '@kit.SpatialReconKit';
const session = await spatialRecon.createReconSession({
inputUri: 'file:///data/capture/glass.mp4',
outputDir: 'file:///data/output/glass/',
// 没有 refractionEnabled、transparencyMode 之类的参数
// 重建算法对透明物体的处理是固定的
});
约束面:透明度阈值与失败临界
我们把透明度分成三档测:高透(玻璃杯,可见光透射率约 92%)、半透(塑料瓶,约 70%)、低透(磨砂玻璃,约 40%)。重建质量用 SSIM 跟原始采集帧对比,主观评分由 3 个人独立打分取平均。拍摄条件统一:环绕 360°,每 3° 一帧,共 120 帧,室内均匀光照。
| 玻璃杯 | 92% | 18.7 万 | 11.2s | 0.41 | 2.3 | 68% |
| 塑料瓶(透明款) | 70% | 24.3 万 | 13.8s | 0.58 | 4.1 | 45% |
| 磨砂玻璃杯 | 40% | 31.2 万 | 15.6s | 0.72 | 6.4 | 22% |
| 实心陶瓷杯(对照) | 0% | 27.8 万 | 14.1s | 0.89 | 8.7 | 5% |
数据是我们 3 台麒麟 9030 设备跑出来的,每台跑 3 次取中位数。SSIM 波动大概 ±0.03,主观评分人与人之间差 1 分以内。重建耗时波动更大一些,±1.5s,跟当时后台负载有关。
透射率高于 70% 的物体,重建质量基本不可用。玻璃杯那行 SSIM 0.41 是什么概念——比直接拿一张采集图模糊处理后比较还低,说明重建结果跟原始物体已经不像了。失败区域占比 68% 意味着杯身大部分区域是错的。
磨砂玻璃反而是三者里最救得回来的一种。原因也好理解:磨砂表面把光线散射掉,折射信息本来就被破坏了,3DGS 用半透明体积去近似,反而歪打正着。但边缘——杯口、杯底这些曲率变化大的地方——依然会糊掉,因为没有折射信息来约束边缘的几何。
我们还测了一个中间态:半磨砂玻璃杯(一面磨砂一面透明)。结果很有意思——磨砂那面 SSIM 0.69,透明那面 SSIM 0.43,跟单独测的两个案例接近。这说明透明度的影响是局部的,不是整体重建质量一刀切。这个发现后来影响了我们的产品策略:如果物体局部透明,可以接受重建,把透明区域做特殊处理。
场景落地:三种透明度物体的重建对比
我们拍了三组对比素材,同一光照、同一背景、同一拍摄路径,分别重建后加载到端侧渲染。
玻璃杯:重建成实心
玻璃杯重建出来,杯身变成一坨带轻微透明度的实心体积。从侧面看,背后物体的折射完全丢失——杯子里插的花看不到,本来应该透过玻璃看到花茎被折射拉弯,现在花茎直接消失,因为重建算法把那块区域当成"杯壁本身的颜色"。
更糟的是杯口边缘。薄壁在高斯点里被表达成一条半透明的"带",没有厚度信息。渲染时从某些角度看杯口像被削平了一块。我们截图给非技术的产品同学看,他以为是杯子被压扁了。
失败区域主要集中在两类地方:薄壁区域(杯壁、杯口)和折射强烈的区域(杯身中部,因为这里光线路径最复杂)。这两类区域加起来占杯身投影面积的 68%,跟表格里的失败区域占比对得上。
塑料瓶:瓶身可用,瓶口翻车
透明塑料瓶(矿泉水瓶那种)情况好一些。瓶身曲面重建基本正确,因为塑料的折射率比玻璃低(PET 约 1.57,玻璃约 1.52,但塑料壁更薄),折射扭曲没那么剧烈。但瓶口螺纹处翻车——那是薄壁加高曲率,重建出来的螺纹糊成一团,看不清结构。
塑料瓶还有一个问题是反光。塑料表面有高光,3DGS 会把高光"烧"进点云里,导致从其他角度看时那个高光还在原位,像贴了一块白斑。这不是透明物体特有的问题,但在透明物体上特别明显,因为背景信息本来就少,高光一占就更没有可用信息了。
我们试过在采集时加偏振片消反光,效果有改善但没根治。偏振片只能消除某个角度的反光,环绕拍摄时总有角度消不掉。而且加偏振片后进光量下降,弱光噪声上来,重建质量又掉。两头不讨好,最后放弃。
磨砂玻璃:勉强可用,边缘糊掉
磨砂玻璃杯是三者里效果最好的。杯身半透明效果接近真实,因为磨砂的本质就是打散折射,3DGS 的半透明体积模型正好对路。
但杯口边缘依然糊。磨砂玻璃杯口通常有一圈抛光带(不磨砂的透明边缘),这圈抛光带的折射信息丢失,重建后变成一段模糊的过渡,没有清晰的边界。
我们试过一个取巧办法:把磨砂玻璃杯口用不透明胶带贴住再重建,重建后把胶带区域的高斯点删掉,用周围点外推补齐。效果比直接重建好一点,但补齐的区域颜色偏暗,因为外推用的是磨砂区域的颜色,没有透明边缘的亮度。这个方案最后放弃了,工程上不划算——用户不会为了重建一个杯子先贴胶带。
踩坑与取舍
坑 1:玻璃杯重建成实心,用户以为模型错了
产品同学第一次看到玻璃杯重建结果,以为是重建 bug,让我们"修一下"。解释了半天这是 3DGS 的原理局限,不是 bug。后来我们达成共识:透明物体在产品层面要么不重建,要么重建后明确标注"非真实折射"。
这个沟通成本不小。建议在做产品方案阶段就把透明物体的能力边界跟产品对齐,别等到验收阶段才解释为什么玻璃杯不像玻璃杯。
坑 2:增加采集帧数没用
我们试过把玻璃杯的采集帧数从 120 帧加到 360 帧,重建耗时从 11 秒涨到 28 秒,SSIM 只从 0.41 涨到 0.43。投入产出比极差。原因前面说过,算法本身不建模折射,给再多数据也拟合不出来。
这个坑我们踩了两天才想明白。一开始以为是采集路径不够覆盖,加了顶部和底部的帧,又加了近距离特写帧,全没用。后来去翻 3DGS 原始论文才确认,算法的渲染模型里就没有折射项,这不是数据量能解决的问题。
坑 3:背景复杂度影响重建
同一个玻璃杯,放在纯白背景前重建 SSIM 0.41,放在杂乱桌面前重建 SSIM 0.38。背景越复杂,杯身区域要表达的折射信息越多,重建越崩。这个发现让我们调整了采集规范:透明物体如果实在要重建,背景尽量干净。
但干净背景又带来另一个问题——特征点稀疏,重建算法在背景区域找不到匹配点,相机位姿估计精度下降。我们的折中方案是用带纹理但低对比度的背景(比如浅灰色带细网格的布),既有特征点又不会让折射信息太复杂。
坑 4:透明液体让事情更糟
装了水的玻璃杯是最让我们头疼的 case。本来以为水会降低整体透明度(水有颜色),能救回来一点。结果水面本身是高反射加高折射的界面,重建直接把水面丢了,杯子上方出现一个空洞。
这个 case 后来成了我们内部的"3DGS 透明物体能力测试标杆",每次参数调整都拿它跑一遍。目前没有好的解决方案,含液面的透明容器在我们的产品里直接走传统建模。
取舍:什么时候放弃 3DGS 改用传统建模
我们的判断是:透射率大于 60% 的物体,别用 3DGS,直接上传统建模或者干脆用采集图做展示。透射率 40% 到 60% 可以试,但要接受边缘糊。透射率小于 40% 基本可用,按普通物体处理。
这个阈值是我们测出来的,不是算法理论给的,换一套重建参数可能变。但在我们当前项目参数下,这个阈值稳定复现。如果后续 Spatial Recon Kit 更新了折射建模能力,这个阈值要重新测。
整个判断其实不是单一阈值,而是一条先看材质、再看透射率的链路,画出来更清楚:
#mermaid-svg-P5WD5njVKuYeYwfO{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-P5WD5njVKuYeYwfO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-P5WD5njVKuYeYwfO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-P5WD5njVKuYeYwfO .error-icon{fill:#552222;}#mermaid-svg-P5WD5njVKuYeYwfO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-P5WD5njVKuYeYwfO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-P5WD5njVKuYeYwfO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-P5WD5njVKuYeYwfO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-P5WD5njVKuYeYwfO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-P5WD5njVKuYeYwfO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-P5WD5njVKuYeYwfO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-P5WD5njVKuYeYwfO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-P5WD5njVKuYeYwfO .marker.cross{stroke:#333333;}#mermaid-svg-P5WD5njVKuYeYwfO svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-P5WD5njVKuYeYwfO p{margin:0;}#mermaid-svg-P5WD5njVKuYeYwfO .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-P5WD5njVKuYeYwfO .cluster-label text{fill:#333;}#mermaid-svg-P5WD5njVKuYeYwfO .cluster-label span{color:#333;}#mermaid-svg-P5WD5njVKuYeYwfO .cluster-label span p{background-color:transparent;}#mermaid-svg-P5WD5njVKuYeYwfO .label text,#mermaid-svg-P5WD5njVKuYeYwfO span{fill:#333;color:#333;}#mermaid-svg-P5WD5njVKuYeYwfO .node rect,#mermaid-svg-P5WD5njVKuYeYwfO .node circle,#mermaid-svg-P5WD5njVKuYeYwfO .node ellipse,#mermaid-svg-P5WD5njVKuYeYwfO .node polygon,#mermaid-svg-P5WD5njVKuYeYwfO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-P5WD5njVKuYeYwfO .rough-node .label text,#mermaid-svg-P5WD5njVKuYeYwfO .node .label text,#mermaid-svg-P5WD5njVKuYeYwfO .image-shape .label,#mermaid-svg-P5WD5njVKuYeYwfO .icon-shape .label{text-anchor:middle;}#mermaid-svg-P5WD5njVKuYeYwfO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-P5WD5njVKuYeYwfO .rough-node .label,#mermaid-svg-P5WD5njVKuYeYwfO .node .label,#mermaid-svg-P5WD5njVKuYeYwfO .image-shape .label,#mermaid-svg-P5WD5njVKuYeYwfO .icon-shape .label{text-align:center;}#mermaid-svg-P5WD5njVKuYeYwfO .node.clickable{cursor:pointer;}#mermaid-svg-P5WD5njVKuYeYwfO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-P5WD5njVKuYeYwfO .arrowheadPath{fill:#333333;}#mermaid-svg-P5WD5njVKuYeYwfO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-P5WD5njVKuYeYwfO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-P5WD5njVKuYeYwfO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P5WD5njVKuYeYwfO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-P5WD5njVKuYeYwfO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P5WD5njVKuYeYwfO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-P5WD5njVKuYeYwfO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-P5WD5njVKuYeYwfO .cluster text{fill:#333;}#mermaid-svg-P5WD5njVKuYeYwfO .cluster span{color:#333;}#mermaid-svg-P5WD5njVKuYeYwfO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-P5WD5njVKuYeYwfO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-P5WD5njVKuYeYwfO rect.text{fill:none;stroke-width:0;}#mermaid-svg-P5WD5njVKuYeYwfO .icon-shape,#mermaid-svg-P5WD5njVKuYeYwfO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P5WD5njVKuYeYwfO .icon-shape p,#mermaid-svg-P5WD5njVKuYeYwfO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-P5WD5njVKuYeYwfO .icon-shape .label rect,#mermaid-svg-P5WD5njVKuYeYwfO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P5WD5njVKuYeYwfO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-P5WD5njVKuYeYwfO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-P5WD5njVKuYeYwfO :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
不透明
透明
高于 70%
40% 到 60%
低于 40%
采集帧输入
目标区域是否透明
走正常重建流程
透射率对比门限
直接拒绝重建
可尝试, 接受边缘糊
磨砂类半透明
换传统建模或照片展示
重建后标注非真实折射
先判材质决定要不要往下走,透明物体再按透射率分流,这样就不用把算力花在注定失败的 case 上。
失败案例对比表
| 高脚杯(薄壁高透) | 92% | 实心冰柱状,无折射 | 折射未建模 | 否 | 改用传统建模 |
| 水晶摆件(多面体高透) | 95% | 完全失败,重建成一团 | 折射加多面反射 | 否 | 改用传统建模 |
| 矿泉水瓶(薄壁半透) | 70% | 瓶身可用,瓶口螺纹糊 | 薄壁高曲率 | 部分 | 瓶口单独建模替换 |
| 半透明亚克力板(平面半透) | 55% | 勉强可用,颜色偏移 | 折射近似误差 | 部分 | 后期调色 |
| 磨砂玻璃杯(半透散射) | 40% | 杯身可用,杯口边缘糊 | 抛光带折射丢失 | 部分 | 接受或建模补边缘 |
| 装满水的玻璃杯 | 混合 | 杯身可用,水面缺失 | 液面反射加折射 | 否 | 改用传统建模 |
| 实心陶瓷杯(对照) | 0% | 正常 | 无 | — | — |
水晶摆件那行值得单独说一句。多面体水晶每个面都是高透加高折射,还会互相反射,重建结果比玻璃杯还崩——直接缩成一团没有形状的点云。这个 case 让我们确认了一件事:3DGS 透明物体的失败程度跟面体复杂度正相关,不是简单的透射率单变量问题。
透明度检测:重建前的预判
既然透明物体重建大概率失败,最好在重建前就预判一下,避免浪费 10 秒重建时间。我们用了一个简单的方法:分析采集帧中目标区域的亮度方差。
透明区域的特点是背景透过来,亮度方差跟背景一致;不透明区域反映物体表面,亮度方差不同。通过比较目标区域和背景区域的亮度方差比,可以粗略判断透明度。
// ArkTS:基于亮度方差的透明度粗判
function estimateTransparency(
frames: image.PixelMap[],
targetRegion: Rect,
backgroundRegion: Rect
): number {
let targetVariances: number[] = [];
let backgroundVariances: number[] = [];
for (const frame of frames) {
const targetVar = computeLuminanceVariance(frame, targetRegion);
const bgVar = computeLuminanceVariance(frame, backgroundRegion);
targetVariances.push(targetVar);
backgroundVariances.push(bgVar);
}
// 透明区域方差比接近 1,不透明区域方差比偏离 1
const avgRatio = targetVariances.reduce((sum, v, i) =>
sum + v / Math.max(backgroundVariances[i], 1), 0
) / frames.length;
// 方差比越接近 1,透明度越高(粗估)
const estimatedTransparency = Math.min(1, Math.max(0, avgRatio));
return estimatedTransparency;
}
// 用法:透射率 > 0.6 直接跳过重建,提示用户换方案
const transparency = estimateTransparency(captureFrames, targetRect, bgRect);
if (transparency > 0.6) {
showHint('该物体透明度过高,3DGS 重建效果不佳,建议使用照片展示');
return;
}
这个方法不精确,误差大概 ±15%。但对"要不要重建"这个决策来说够了——我们只需要区分"明显透明"和"基本不透明",不需要精确透射率。误判的情况主要发生在低对比度物体上,比如浅色磨砂玻璃,方差比跟背景接近但实际透明度不高。这种 case 误判成透明跳过重建,损失不大,因为浅色磨砂玻璃重建效果也一般。
总结一下下
我们正在试一个混合方案:透明物体的几何用传统建模(一个简单的玻璃杯 mesh 很便宜),表面纹理和光影用 3DGS 采集。mesh 提供折射所需的几何,3DGS 提供表面外观。这个方案还没跑通,主要卡在 mesh 和 3DGS 点云怎么对齐——mesh 的顶点和 3DGS 的高斯点没有天然对应关系,手动对齐成本太高。
如果有读者在做类似的事,欢迎交流。另一个方向是等 Spatial Recon Kit 后续版本,看会不会加折射建模能力——目前没有公开路线图,但这是 3DGS 研究的活跃方向,端侧跟上只是时间问题。在那之前,透明物体在我们项目里就是 3DGS 的禁区。
网硕互联帮助中心







评论前必须登录!
注册