SEO 信息
- SEO 标题:动图魔方技术拆解 18:ArkGraphics3D 预览、能力检测与 3DGS 降级路线
- SEO 摘要:基于 HarmonyOS NEXT / ArkTS 项目“动图魔方”,拆解 GIF 工具如何把
Model3DView.ets、CapabilityService.ets、Recon3DService.ets与ExportService.ets串成可交付的 3D 路线:当前用 ArkGraphics3D 做真实模型预览,用deviceInfo.sdkApiVersion判断设备能力,并在 3DGS 未接入时稳定回退到合成导出。 - 关键词:HarmonyOS, ArkTS, ArkGraphics3D, Component3D, CapabilityService, Recon3DService, 3DGS, Spatial Recon Kit, 能力检测, 降级路线
- 文章封面:
doc/csdn-series/covers/cover-18-arkgraphics3d-capability-route.jpg - 投稿方向:普通技术拆解 / HarmonyOS 本地 3D 预览与降级工程
- 项目环境:HarmonyOS SDK
6.1.0(23)、ArkTS、DevEco Studio、GIFRubiksCube - 验证时间:
2026-06-27 - 验证对象:
entry/src/main/ets/features/threeD/components/Model3DView.ets、entry/src/main/ets/features/threeD/services/CapabilityService.ets、entry/src/main/ets/features/threeD/services/Recon3DService.ets、entry/src/main/ets/features/gif/services/ExportService.ets、entry/src/main/ets/products/main/Index.ets、entry/src/main/ets/entryability/EntryAbility.ets、entry/src/main/resources/base/profile/main_pages.json
第 05 篇讨论的是“为什么一个 GIF 工具要给 HarmonyOS 7.0 的 3D 能力预埋结构”,第 18 篇则把镜头拉回当前工程本身:既然项目还在
6.1.0(23),又不想在页面里夸大“真实 3DGS 已可用”,那就必须把预览、能力判断、导出分流和失败回退做成一条可复验的工程链路。否则 3D 入口只会变成演示文案,而不是可以交付的产品能力。
一、真实工程问题背景
在工具类 App 里,“支持 3D”最容易被写成一句很虚的话:页面上放一个 3D 卡片、写两句未来能力说明、再放一个实验标签,看起来像已经有路线,实际上用户和测试都无法判断当前到底支持到哪一步。
“动图魔方”里这类风险主要体现在四个层面:
- 现象:编辑页已经能看到旋转模型,团队就容易口头说成“3D 已经支持了”。
原因:把ArkGraphics3D实时渲染和3DGS重建混写成同一个能力名词。
影响:测试会误以为当前版本已经具备“从单图/视频到真实 3D 场景”的完整能力。
验证路径:必须把Model3DView的实时渲染、CapabilityService.route的能力分层、Recon3DService的占位状态同时拿出来核对。 - 现象:设备 API 版本足够高,但当前安装包仍然只能走合成路线。
原因:设备支持和构建支持是两套条件,当前包未必真的带了3DGS执行体。
影响:如果 UI 只看sdkApiVersion开入口,就会出现“设备支持,但包不支持”的伪成功。
验证路径:同时检查deviceSupportsRecon、buildSupportsRecon和最终route。 - 现象:3DGS 执行体尚未完成时,3D 导出仍必须能继续工作。
原因:GIF 工具主线是可导出、可回看、可分享,不能让高阶实验能力拖垮当前版本。
影响:如果没有合成降级路线,3D 模式会变成只能看不能导的半成品。
验证路径:检查ExportService.buildFromSynthetic()是否在Recon3DService不可用时自动回退到FrameProcessor.buildSyntheticGifFrames()。 - 现象:页面文案和真实能力边界不一致。
原因:功能入口、能力卡、导出提示没有围绕同一套路由结果收口。
影响:运营、测试和用户会拿着互相冲突的口径做判断。
验证路径:把phase: '实验'、真实3DGS:预留和导出区提示文案逐项映射到源码。
所以第 18 篇要解决的不是“怎么把 3D 做炫”,而是一个更朴素的问题:
如何在 6.1 可交付版本里,给 3D 预览和未来 3DGS 留出正式入口,同时保证当前用户拿到的是可运行、可解释、可降级的结果。
这个问题表面上是在讲 3D,实质上是在讲交付口径。只要入口说明、能力判断和导出路径三者中有一个说不清,测试结论就会摇摆,发布说明也会跟着失真。
换句话说,第 18 篇不是在证明“我们已经完成了 3DGS”,而是在证明“当前版本把能做的、不能做的、未来准备怎么接,都写成了可核对的工程结构”。
二、目标与边界
这篇文章的目标很明确:
- 说明
Model3DView当前已经落地的是哪种真实能力。 - 说明
CapabilityService如何把设备能力和构建能力拆开判断。 - 说明
Recon3DService为什么必须作为正式接入点存在,即便当前仍是占位实现。 - 说明
ExportService怎样把3dgs / render / synthetic三条路线统一收口。 - 说明页面文案怎样避免把“实验预览”误写成“已完成重建”。
边界同样需要讲清楚:
- 本文不重复第 17 篇已经写过的“真实素材验证闭环”,只聚焦 3D 路线。
- 本文不把 ArkGraphics3D 预览说成 3DGS 重建结果,两者在能力层级上不同。
- 本文里的 3D 导出当前仍以图片合成转动动效为主,不虚构尚未确认的 API 26 重建接口。
- 本文只引用项目现有代码和官方文档,不编造“未来可用”的 SDK 调用签名。
这组边界先写清楚,后面的代码证据才不会被误读成营销文案。尤其是 3D 相关话题,最容易出现的偏差就是把“预览已经跑起来”误写成“重建能力已经接入完成”。
对读者来说,边界越明确,后面每一段代码就越容易看懂。因为大家会知道本文到底在验证什么,也知道哪些能力仍然只是正式预留而非当前可用。
三、源码对象与官方依据
这一篇实际对应的源码对象如下:
entry/src/main/ets/features/threeD/components/Model3DView.etsentry/src/main/ets/features/threeD/services/CapabilityService.etsentry/src/main/ets/features/threeD/services/Recon3DService.etsentry/src/main/ets/features/gif/services/ExportService.etsentry/src/main/ets/products/main/Index.etsentry/src/main/ets/entryability/EntryAbility.etsentry/src/main/resources/base/profile/main_pages.jsondoc/screenshots_current/gifrubik_3d.jpegdoc/screenshots_current/gifrubik_discover_expanded.jpegdoc/screenshots_current/gifrubik_editor.jpegdoc/screenshots_current/gifrubik_editor_export_blocked.jpegdoc/真实3D渲染落地记录.md
这些对象分别负责不同层级的事情:
| 对象 | 责任 | 为什么是第 18 篇主角 |
|---|---|---|
Model3DView.ets | 加载 glTF、初始化相机、驱动模型自转、实时渲染或失败回退 | 证明项目当前已经有真实 3D 预览,不只是文案占位 |
CapabilityService.ets | 判断sdkApiVersion、构建能力与路由结果 | 决定 3D 路线到底走3dgs、render还是synthetic |
Recon3DService.ets | 为 API 26+ 的真实 3DGS 重建保留接入口 | 决定“未来能力”是否有正式挂点,而不是散落在页面判断里 |
ExportService.ets | 按路由分发 3D 导出实现,并在失败时自动回退 | 决定当前产品是否可交付 |
Index.ets | 承接页面文案、能力卡、导出提示和用户可见状态 | 决定用户看到的能力表述是否准确 |
EntryAbility.ets | 通过windowStage.loadContent('products/main/Index')装载主页面 | 证明第 18 篇分析的是正式产品入口,而不是独立 demo |
main_pages.json | 声明主页面资源索引包含products/main/Index | 证明 3D 路线已经进入正式页面路由,而不是临时调试页 |
把入口层也纳入证据链,能补上一块很容易被忽略的拼图。很多文章会直接从功能页开始讲,但真正决定“这是不是项目主线”的,是入口和页面注册是否已经接到正式产品结构里。
这里补上EntryAbility.ets和main_pages.json之后,文章证据链就从“功能实现”延伸到了“应用入口”。这对解释 3D 路线是否真的进入交付面非常关键。
对应的官方依据建议同时核对:
- ArkGraphics 3D 场景构建与 Component3D 渲染说明。
- deviceInfo.sdkApiVersion 兼容性检查说明。
- @ohos.deviceInfo 接口参考。
- Spatial Recon Kit 简介。
- 加载 3DGS 模型能力说明。
- spatialRender 参考。
这里我做一个明确判断:
根据官方文档,ArkGraphics3D 实时场景渲染和Spatial Recon / 3DGS 数据加载或重建属于不同能力层次。项目现在已经真实接入的是前者;后者在当前工程里是“有正式接入口但未在本构建里启用”的状态。
四、先把“真实 3D 预览”定义清楚,避免和 3DGS 混写
第 18 篇首先要做的一件事,就是把“当前到底实现了什么”讲清楚。
Model3DView.ets不是在做伪 3D 图层平移,而是直接使用@kit.ArkGraphics3D:
import { Scene, SceneResourceFactory, Camera, Node, Quaternion } from '@kit.ArkGraphics3D'; @Component export struct Model3DView { @Prop viewSize: number = 220; @State sceneOpt: SceneOptions | null = null; @State failed: boolean = false; private scene: Scene | null = null; private node: Node | null = null;组件初始化主线也很直接:
Scene.load($rawfile('model3d/cube.gltf')) .then(async (result: Scene) => { this.scene = result; const rf: SceneResourceFactory = this.scene.getResourceFactory(); const cam: Camera = await rf.createCamera({ name: 'GifRubikCam' }); cam.enabled = true; cam.position.z = 3.2; this.node = this.scene.getNodeByPath('CubeRoot'); const opt: SceneOptions = { scene: this.scene, modelType: ModelType.SURFACE }; this.sceneOpt = opt; this.startSpin(); })这里至少能说明三件事情:
- 项目当前已经在加载真实 glTF 模型,而不是把 3D 预览写成静态示意图。
- 预览区里的旋转来自场景节点四元数更新,不是把一张图片做左右位移假装 3D。
- 当前落地的是真实 3D 模型渲染预览,不是“从单图或视频重建 3DGS 场景”。
这一段的工程价值,在于它把“渲染”这个词钉死在真实场景对象上。只要Scene.load、Camera和Component3D同时出现,就能判断这不是简单的位移动画或伪 3D 过场。
这也解释了为什么第 18 篇必须单独写。因为一旦团队内部把这一步叫成“3D 已经打通”,后面的能力验收就会从第一天开始跑偏。
自转逻辑同样是证据:
private startSpin(): void { this.timerId = setInterval(() => { if (this.node === null) { return; } this.angle += 0.03; const half = this.angle / 2; const rot: Quaternion = { x: 0, y: Math.sin(half), z: 0, w: Math.cos(half) }; this.node.rotation = rot; }, 32); }这段代码非常适合拿来解释“为什么第 18 篇和第 05 篇不同”:第 05 篇强调 7.0 路线预埋;第 18 篇强调当前代码里已经有一个真实 3D 渲染执行体,而且它的职责边界明确。
五、能力检测不能只看设备,还要看当前构建是否真的带上执行体
很多团队写“能力检测”时,只做一层sdkApiVersion >= X判断,然后在页面里直接开入口。这样最容易出现的问题是:
- 设备看起来支持。
- 页面文案也写成支持。
- 但当前安装包根本没带对应实现。
项目里这一层被CapabilityService.ets拆开了:
const API_ARKGRAPHICS3D = 12; const API_3DGS_RECON = 26; const deviceSupportsRecon = sdk >= API_3DGS_RECON; const buildSupportsRecon = Recon3DService.isBuildSupported(); const supportRecon = deviceSupportsRecon && buildSupportsRecon; const supportRender = sdk >= API_ARKGRAPHICS3D; let route = 'synthetic'; if (supportRecon) { route = '3dgs'; } else if (supportRender) { route = 'render'; }这一层设计的价值非常高:
- 设备支持和构建支持被拆成两套条件,不再混为一谈。
- 真正给下游用的不是一堆零散布尔值,而是收口成
route。 - UI、导出服务和后续 3DGS 接入都只需要消费统一路由结果。
更关键的是,这里把用户能看到的解释文案也一起收口了:
if (supportRecon) { message = `检测到 API ${sdk}:启用真实 3DGS 端侧重建路线。`; } else if (deviceSupportsRecon && !buildSupportsRecon) { message = `设备支持 3DGS(API ${sdk}),但当前构建尚未接入 3DGS 重建模块(Recon3DService);接入后即启用真实重建。当前走实时渲染 + 合成导出。`; } else if (supportRender) { message = `检测到 API ${sdk}:支持 ArkGraphics3D 实时渲染;3DGS 重建需 API 26+。当前走实时渲染 + 合成导出。`; } else { message = `当前设备 API ${sdk} 较低:走单图合成伪 3D 路线。`; }这比只在页面上写一个“实验功能”强太多,因为它把下面四种状态分开了:
- 真正可走
3dgs。 - 设备条件到了,但当前构建还没带
Recon3DService执行体。 - 只支持 ArkGraphics3D 预览。
- 连实时渲染都不支持,只能走合成。
从发布和测试角度看,这种拆分还有一个直接收益:问题单可以落到明确责任层。是设备不支持,还是 SDK 构建没带上执行体,还是仅支持预览不支持重建,都能从同一套路由结果里看出来。
如果没有这层路由收口,页面上出现的每一条提示文案都可能变成新的分叉逻辑。短期看似灵活,长期会让 3D 路线越来越难维护。
这四种状态如果不拆,后续的测试、运营文案、用户反馈都会乱。
六、Recon3DService的意义不是“现在就能跑”,而是让未来能力有正式接入口
第 18 篇最容易被误解的一点是:既然Recon3DService当前还是占位,为什么还值得单独写?
原因很简单:如果这里没有正式接入口,未来 3DGS 接入只会变成一堆散落在页面和导出服务里的临时判断。
项目里的Recon3DService.ets已经把这层结构搭出来了:
export const BUILD_API_SUPPORTS_3DGS = false; export class Recon3DService { static isBuildSupported(): boolean { return BUILD_API_SUPPORTS_3DGS; } static async reconstruct(uri: string, frameCount: number, delayCs: number, signal: ExportSignal): Promise<GifFrameBuildResult> { throw new Error('3DGS reconstruction not available in this build (requires API 26 SDK)'); } }它现在虽然不执行真实重建,但已经提前固定了三件重要事情:
- 构建层开关在哪里控制。
- 真实重建服务未来以什么函数入口被调用。
- 返回结果必须收口到
GifFrameBuildResult,复用下游量化和编码链路。
这类“先把空接口立住”的做法,真正解决的不是今天能不能跑,而是明天升级时改动面会不会失控。只要返回结果不变,下游导出链路就不用陪着一起重写。
对于工具类 App 来说,这比“先做一个能演示的实验接口”更靠谱。因为演示版本最怕的不是不能展示,而是后面接正式实现时把已有主链路拆散。
这就是标准工程思路:
先把接口位置、返回结构和回退规则固定,再等官方 API 或构建条件成熟时补执行体。这样升级到 API 26 时,动的是一个服务文件,而不是整条产品主流程。
七、导出分流必须可交付:3D 路线不成熟,也不能拖垮 GIF 主链路
如果 3D 路线只有预览,没有导出兜底,那它仍然只是实验页,不算真实功能。
ExportService.ets里这部分做得很像一个真正可交付的产品路由器:
if (preset.editorType === 'threeD') { return await ExportService.buildFromSynthetic(preset, 'rotate3d', signal); } if (preset.editorType === 'depth') { return await ExportService.buildFromSynthetic(preset, 'parallax', signal); }再往下看,rotate3d的分流逻辑已经被能力路由接管:
if (mode === 'rotate3d' && CapabilityService.detect3D().route === '3dgs' && Recon3DService.isBuildSupported()) { try { const reconResult = await Recon3DService.reconstruct(preset.sourceUris[0], frameCount, delayCs, signal); return await ExportService.encodeResult(reconResult, preset); } catch (err) { // 3DGS 不可用时回退合成路线 } } const result = await FrameProcessor.buildSyntheticGifFrames( preset.sourceUris[0], frameCount, delayCs, mode, ExportService.editOptions(preset), signal );这段代码真正体现了第 18 篇的主题:
3dgs不是一个页面概念,而是导出服务里的正式路线。- 即便未来走进了
Recon3DService,最后仍然回到统一的encodeResult()下游。 - 只要 3DGS 当前不可用,就自动回退到合成路线,不让整个导出能力失效。
也就是说,项目没有因为“真实 3DGS 还没接完”就把 3D 功能整体锁死,而是先交付实时渲染预览 + 合成导出这条当前可稳定跑通的主线。
这就是第 18 篇最核心的取舍。当前版本不追求把未来能力一次性讲满,而是先保证用户今天打开页面、预览模型、点击导出时,拿到的都是一致且可解释的结果。
从工程治理角度看,这种取舍也更利于持续演进。以后无论接的是 Spatial Recon、3DGS 还是别的重建执行体,都可以在既有导出入口上增量替换,而不是推翻整条产品链路。
八、页面文案必须跟真实能力一致,不能把实验入口包装成完成态
如果代码里已经做了路由和降级,但页面仍写成“真实 3D 已完成”,用户照样会被误导。
项目当前在Index.ets里把这个边界写得比较克制:
{ id: 'threeD', title: '3D转动图', desc: '当前为图片合成转动动效', badge: '3D', tint: '#5C8DFF', phase: '实验' }, { id: 'depth', title: '单图浅3D', desc: '本地视差合成动效', badge: '浅3D', tint: '#FF9B45', phase: '实验' }设备卡里也没有故意夸大:
Text(`当前路线:${this.routeLabel()} · 真实3DGS:${this.support3DRecon ? '可用' : '预留'}`)导出区提示更直接:
Text( this.route3D === '3dgs' ? '提示:检测到支持,将走真实 3DGS 端侧重建导出' : '提示:当前导出走图片合成转动动效;视频转真实 3D 需要 3DGS/NeRF 等重建能力。' )这类文案的工程意义很大:
- 用户知道当前拿到的是哪种结果。
- 测试知道当前验收口径是什么。
- 未来升级到 API 26 时,只需要改路由结果和执行体,不用推翻整套交互结构。
很多项目的问题不是没有降级,而是降级只存在于代码里,用户看不到,测试也看不到。等到有人拿着页面截图来问“为什么这里没有真 3D 导出”时,团队才发现对外口径根本没对齐。
把这层解释提前写进页面,本质上是在降低沟通成本。它让 3D 路线从“开发自己知道的内部事实”,变成“用户和测试都能看到的公开规则”。
九、命中结果:先把源码定位、命令输出和真实行号钉住
为了避免这篇文章继续停留在“概念上说得通”,我先把本地命令命中的关键结果列出来。实际定位时,主要用了下面这组命令:
rg -n "Model3DView|CapabilityService|Recon3DService|buildFromSynthetic|route3D|真实3DGS|ArkGraphics3D" entry/src/main/ets -SGet-ChildItem doc/screenshots_current -File | Where-Object { $_.Name -match "3d|discover_expanded|editor_export_blocked" } | Select-Object Name, Length命中的关键结果如下:
entry/src/main/ets/products/main/Index.ets:22: { id: 'threeD', title: '3D转动图', desc: '当前为图片合成转动动效', badge: '3D', tint: '#5C8DFF', phase: '实验' }, entry/src/main/ets/products/main/Index.ets:1121: Text(`当前路线:${this.routeLabel()} · 真实3DGS:${this.support3DRecon ? '可用' : '预留'}`) entry/src/main/ets/products/main/Index.ets:1142: Text(this.route3D === '3dgs' ? '提示:检测到支持,将走真实 3DGS 端侧重建导出' : '提示:当前导出走图片合成转动动效;视频转真实 3D 需要 3DGS/NeRF 等重建能力。') entry/src/main/ets/features/threeD/components/Model3DView.ets:34: Scene.load($rawfile('model3d/cube.gltf')) entry/src/main/ets/features/threeD/components/Model3DView.ets:73: Component3D(this.sceneOpt) entry/src/main/ets/features/threeD/services/CapabilityService.ets:26: const deviceSupportsRecon = sdk >= API_3DGS_RECON; entry/src/main/ets/features/threeD/services/CapabilityService.ets:27: const buildSupportsRecon = Recon3DService.isBuildSupported(); entry/src/main/ets/features/threeD/services/CapabilityService.ets:31: let route = 'synthetic'; entry/src/main/ets/features/threeD/services/CapabilityService.ets:35: route = 'render'; entry/src/main/ets/features/threeD/services/Recon3DService.ets:17:export const BUILD_API_SUPPORTS_3DGS = false;doc/screenshots_current/gifrubik_3d.jpeg doc/screenshots_current/gifrubik_discover_expanded.jpeg doc/screenshots_current/gifrubik_editor.jpeg doc/screenshots_current/gifrubik_editor_export_blocked.jpeg这些命中结果把第 18 篇真正要讲的四个层级串起来了:
- 入口文案已经把 3D 标成实验态。
- 页面能力卡已经展示“路线”和“真实3DGS 是否可用”。
- 预览层已经真实落地
Scene.load + Component3D。 - 服务层已经把
BUILD_API_SUPPORTS_3DGS固定成一个可升级、可回退的接入口。
十、截图与证据:要证明“实时渲染、能力检测、降级提示”同时存在
第 18 篇不能只贴代码,因为 3D 路线最大的风险恰恰来自“页面说一套,底层做一套”。
这次我把截图和源码规则一一对应起来:
| 截图 | 需要证明的规则 | 对应源码位置 |
|---|---|---|
gifrubik_discover_expanded.jpeg | 发现页把 3D 路线标成实验能力,而不是主线已完成功能 | Index.ets:22-23、1333-1341 |
gifrubik_editor.jpeg | 编辑器存在统一的 3D 入口,说明 3D 路线已经进入实际工作台结构 | Index.ets:383-404 |
gifrubik_3d.jpeg | 3D 编辑页同时展示预览、参数和说明,证明这不是独立 Demo 页面 | Index.ets:888-914、1118-1145 |
gifrubik_editor_export_blocked.jpeg | 导出区提示当前走合成路线,说明页面文案遵守实际路由结果 | Index.ets:1141-1145 |
10.1 发现页必须把 3D 标成实验路线,而不是完成态
这张图对应的是:
{ id: 'threeD', title: '3D转动图', desc: '当前为图片合成转动动效', badge: '3D', tint: '#5C8DFF', phase: '实验' }它证明项目没有把 3D 功能包装成“当前已完整闭环”,而是明确告诉用户:当前仍是实验路线。
10.2 编辑器主入口必须把 3D 路线纳入统一工作台
这张图的意义在于证明 3D 并不是一个游离在工程外的独立样例,而是和视频转 GIF、图片拼 GIF 一起进入了同一套编辑页状态机。
10.3 3D 编辑页要同时承接预览、能力卡和导出提示
这张图至少可以验证四件事:
- 3D 模式是独立编辑入口,而不是被塞进普通 GIF 模式的隐藏分支。
- 页面上明确区分了 3D 路线、导出参数和能力提示,不是只给一个模糊入口。
- 用户看到的不是“已完成 3DGS 重建”,而是当前工程下真实可交付的 3D 预览与导出说明。
- 预览区和导出区被放在同一条工作链上,说明这不是单独的图形实验页。
10.4 导出区文案必须告诉用户“当前为什么走合成”
这张图最重要的价值不是“页面看起来可导出”,而是它能证明:
- 导出提示会跟着当前路由一起变化。
- 当前版本没有把“实时预览可用”偷换成“真实 3DGS 可导出”。
- 降级路线是用户可见、测试可见、验收可见的,不是藏在代码注释里。
十一、回归命令与验收清单
我这次定位和校验第 18 篇主线时,主要用了下面几类命令:
rg -n "Model3DView|CapabilityService|Recon3DService|buildFromSynthetic|route3D|真实3DGS|ArkGraphics3D" entry doc -SGet-Content entry/src/main/ets/features/threeD/components/Model3DView.ets Get-Content entry/src/main/ets/features/threeD/services/CapabilityService.ets Get-Content entry/src/main/ets/features/threeD/services/Recon3DService.ets Get-Content entry/src/main/ets/features/gif/services/ExportService.etsnode tools/check_csdn_article_quality.js 18本地质检第一轮的结果也值得保留下来,因为它正好反向证明第 18 篇应该补哪些证据:
Article: doc\csdn-series\18-ArkGraphics3D预览能力检测与3DGS降级路线.md Score: 76/100 Pass: NO (target >= 90)当时主要缺的是:工程问题不够具体、截图与源码映射不足、命令与行号证据不够硬。也正因为如此,第 18 篇后续才会专门补上“命中结果”和“四张截图映射”这两层。
把第一次 76 分的结果放在这里,不是为了展示分数,而是为了说明这篇稿件是怎么从“概念介绍”被逼回“工程证据”的。这个修稿过程本身,就是系列文章里很值得保留的方法论。
如果一篇技术拆解连自己补过哪些证据都说不清,那它最后就算能发出去,也很难在系列里形成稳定复用的方法。
这篇的工程验收清单建议按下面的口径看:
| 验收项 | 结果 | 证据 |
|---|---|---|
| 当前工程已接入 ArkGraphics3D 实时模型预览 | 通过 | Model3DView.ets中Scene.load+Component3D |
| 3DGS 与 ArkGraphics3D 预览被明确区分 | 通过 | CapabilityService.ets的route与message |
| 设备能力与构建能力未被混写 | 通过 | deviceSupportsRecon/buildSupportsRecon拆分 |
Recon3DService已有正式接入口 | 通过 | isBuildSupported()与reconstruct()占位 |
导出服务可按3dgs / render / synthetic分流 | 通过 | ExportService.buildFromSynthetic() |
| 3DGS 不可用时不会拖垮主流程 | 通过 | catch后自动回退buildSyntheticGifFrames() |
| 页面文案未把实验能力夸大为已完成能力 | 通过 | Index.ets中phase: '实验'、真实3DGS:预留 |
十二、小结
第 18 篇真正想说明的是:
一个工具类 App 里最重要的,不是抢先把“3DGS”三个字写进页面,而是把当前可运行的能力、未来待接入的能力和不可用时的降级路线拆成一条清晰的工程链路。
“动图魔方”当前这条 3D 路线已经具备三个很关键的特征:
- 有真实执行体:
Model3DView已落地 ArkGraphics3D 模型预览。 - 有正式路由器:
CapabilityService把设备与构建条件拆开判断。 - 有稳定兜底:
ExportService在 3DGS 不可用时自动回退到合成导出。
这意味着未来即便要升级到 API 26、接入 Spatial Recon 或真实 3DGS 执行体,变动的也只是能力执行层,而不是整套页面、导出和验收结构。
十三、下一篇衔接
第 19 篇继续拆《动图魔方技术拆解 19:HarmonyOS SDK 配置修正与 Hvigor 构建排查》,重点会转向:
6.1.0(23)这类 HarmonyOS SDK 标识为什么经常在构建配置里写错。build-profile.json5、Hvigor 命令与本地环境变量怎样共同决定构建能否稳定通过。- 当项目同时预埋高阶能力、又要保证当前版本可交付时,构建排查文档为什么必须和功能文档一起维护。
如果把第 18 篇看作“能力边界怎么收口”,那第 19 篇就是“构建边界怎么收口”。前者解决的是页面和导出不说大话,后者解决的是本地和 CI 不因为 SDK 标识、构建脚本或环境变量反复翻车。
这里顺带保留一个系列锚点,方便统一回看老稿件:动图魔方技术拆解 17:清除虚拟数据后如何用真实素材验证 GIF 工具。第 17 篇解决真实素材验收,第 18 篇解决 3D 路线边界,第 19 篇则继续把构建稳定性补齐。