云计算百科
云计算领域专业知识百科平台

【共创稿事节】HarmonyOS 7透明/半透明物体重建的失败边界

玻璃杯、塑料瓶、水晶摆件这类透明物体,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 帧,室内均匀光照。

物体透射率重建高斯点数重建耗时SSIM主观评分(1-10)失败区域占比
玻璃杯 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 误判成透明跳过重建,损失不大,因为浅色磨砂玻璃重建效果也一般。

总结一下下

  • 透射率大于 60% 的物体不走 3DGS 重建,直接切传统建模或图像展示
  • 透明物体重建时背景尽量干净,但要有低对比度纹理保证特征点
  • 增加采集帧数对透明物体收益极低,不要在这上面花时间
  • 磨砂类半透明物体可用,但要在产品层面接受边缘模糊
  • 含液面的透明容器(杯子装水、花瓶装水)目前不可重建
  • 重建结果若用于商品展示,透明物体要标注"非真实折射"避免用户误解
  • 重建前做透明度预判,避免浪费重建算力
  • Spatial Recon Kit 当前版本无折射建模参数,不要期待调参能救回来
  • 我们正在试一个混合方案:透明物体的几何用传统建模(一个简单的玻璃杯 mesh 很便宜),表面纹理和光影用 3DGS 采集。mesh 提供折射所需的几何,3DGS 提供表面外观。这个方案还没跑通,主要卡在 mesh 和 3DGS 点云怎么对齐——mesh 的顶点和 3DGS 的高斯点没有天然对应关系,手动对齐成本太高。

    如果有读者在做类似的事,欢迎交流。另一个方向是等 Spatial Recon Kit 后续版本,看会不会加折射建模能力——目前没有公开路线图,但这是 3DGS 研究的活跃方向,端侧跟上只是时间问题。在那之前,透明物体在我们项目里就是 3DGS 的禁区。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【共创稿事节】HarmonyOS 7透明/半透明物体重建的失败边界
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!