【共创稿事节】HarmonyOS ArkGraphics 3D 实操:用 GLB 与 PBR 完成智能音箱外观预览器

如果一款智能音箱出现在商城页面,最直观的体验不是先读一串参数,而是换个颜色、转一圈,看清机身、网罩和金属底座的细节。OrbitDesk 把这个过程搬到了 HarmonyOS:本地 GLB 负责造型,PBR 材质负责质感,滑杆和手势负责观察角度。

接下来从资源加载、灯光、换色、旋转到退出释放,完整做出这个可以在手机上直接操作的 3D 外观预览器。项目资源全部打包在应用内,运行时无需联网。

先看真机效果

页面保留产品展示所需的最少控件:左上角返回按钮、底部三枚材质按钮和旋转滑杆。模型包含圆角机身、七排网罩孔、顶部青色发光环、琥珀色状态灯、双层金属底座和闭合光轨。

本次在 HUAWEI Mate 60 Pro、HarmonyOS 7.0.0.105 上运行。页面启动后直接加载本地 GLB,三种机身配色、滑杆旋转和模型拖动均可操作。

动态效果展示

先看三段最直观的操作:换色、拖动滑杆、直接拖动音箱。每张 GIF 都来自真机连续画面。

材质切换

依次点击极夜蓝、雾银灰和珊瑚橙按钮,选中环与机身 PBR 材质同步变化。

滑杆拖动旋转

拖动底部滑杆时,滑块位置和产品根节点的 Y 轴角度连续变化。

直接拖动模型

在模型区域横向拖动,不触碰滑杆;音箱旋转后,底部滑块同步到当前角度。

工程文件

  • 页面:entry/src/main/ets/features/graphics3d/OrbitDeskPage.ets
  • 3D 主页:entry/src/main/ets/features/graphics3d/Graphics3DHomePage.ets
  • GLB:entry/src/main/resources/rawfile/orbitdesk_speaker.glb
  • 材质说明:entry/src/main/resources/rawfile/orbitdesk_speaker_materials.md
  • GLB 生成脚本:tools/generate-orbitdesk-glb.js

生成后的 orbitdesk_speaker.glb 为自包含 glTF 2.0 二进制文件,大小 175416 字节,包含 10 个节点、9 个网格和对应 PBR 材质。Khronos glTF Validator 检查结果为 0 error、0 warning、0 info、0 hint。

1. 用 $rawfile 加载 GLB

页面先尝试加载真实 GLB;只有捕获到加载异常时,才进入程序化几何回退,并记录 GLB_LOAD_FAILED。这样既保证实验页可诊断,也不会把回退画面写成资源加载成功。

复制代码
try {
  this.scene = await Scene.load($rawfile('orbitdesk_speaker.glb'));
  this.renderMode = 'glb';
  hilog.info(DOMAIN, TAG, 'ORBIT_INIT step=scene_load_done mode=glb');
} catch (glbError) {
  const loadError = glbError as BusinessError;
  hilog.warn(DOMAIN, TAG,
    'GLB_LOAD_FAILED code=%{public}d message=%{public}s fallback=procedural',
    loadError.code ?? -1, loadError.message ?? `${glbError}`);
  this.scene = await Scene.load();
  this.renderMode = 'procedural-fallback';
}

真机调试时遇到一个关键差异:最初传入 resource://rawfile/orbitdesk_speaker.glb 字符串,设备返回 Scene creation failed;改成 $rawfile('orbitdesk_speaker.glb') 后,同一台设备记录 scene_load_done mode=glb,模型随即正常显示。因此,工程最终使用 $rawfile 加载应用内资源。

2. 配置相机与双色方向光

相机位于 Z 轴正方向,朝向模型原点。暖色主光负责右侧金属高光,青色轮廓光补出左侧边缘,与顶部和底部发光环形成统一色彩关系。

复制代码
this.camera = await this.factory.createCamera({ name: 'OrbitCamera' });
this.camera.enabled = true;
this.camera.position = { x: 0, y: 0.12, z: 7.8 };
this.camera.fov = 0.72;

this.keyLight = await this.factory.createLight(
  { name: 'WarmKey' }, LightType.DIRECTIONAL) as DirectionalLight;
this.keyLight.intensity = 4;
this.keyLight.color = { r: 1, g: 0.66, b: 0.38, a: 1 };

this.rimLight = await this.factory.createLight(
  { name: 'CyanRim' }, LightType.DIRECTIONAL) as DirectionalLight;
this.rimLight.intensity = 9;
this.rimLight.color = { r: 0.16, g: 0.74, b: 1, a: 1 };

3. 找到机身节点并切换 PBR 材质

GLB 中的产品根节点名为 OrbitDeskProduct,可换色机身节点名为 SpeakerBody。场景加载后递归查找节点;任一关键节点缺失都抛出真实错误,不继续显示"已就绪"。

换色时创建新的 METALLIC_ROUGHNESS 材质,设置基础色、粗糙度和金属度,再覆盖到机身网格。替换完成后销毁旧动态材质,避免用户连续点按时累积资源。

复制代码
this.bodyMaterial = await this.factory.createMaterial(
  { name: `BodyMaterial_${index}` },
  MaterialType.METALLIC_ROUGHNESS) as MetallicRoughnessMaterial;

this.bodyMaterial.baseColor.factor = palette.body;
this.bodyMaterial.material.factor = {
  x: 1,
  y: palette.roughness,
  z: palette.metallic,
  w: 0.5
};
this.bodyParts.forEach((part: Geometry) => {
  part.mesh.materialOverride = this.bodyMaterial;
});

点击第三枚按钮后,机身变为珊瑚橙,选中环同步移动到第三枚按钮。真机日志记录 COLOR_SWITCH palette=珊瑚橙

4. 让滑杆和模型拖动共用旋转状态

滑杆范围为 -135°135°。角度转为弧度后,写入产品根节点的 Y 轴四元数;底座、光轨和音箱作为同一产品节点一起旋转。

复制代码
private applyRotation(degrees: number): void {
  if (!this.productRoot) {
    return;
  }
  const radians = degrees * Math.PI / 180;
  this.productRoot.rotation = {
    x: 0,
    y: Math.sin(radians / 2),
    z: 0,
    w: Math.cos(radians / 2)
  };
}

真机拖动滑杆后,角度连续变化并停在约 115°,网罩孔和状态灯移动到侧面,说明旋转来自 3D 节点变换,不是替换静态图片。

为了让用户也能直接拖音箱,模型区增加横向 PanGesture。手势开始时保存当前角度,更新时把横向位移映射成角度,并写回同一个 rotation 状态,因此底部滑杆会同步移动。

复制代码
PanGesture({ fingers: 1, direction: PanDirection.Horizontal, distance: 2 })
  .onActionStart(() => {
    this.dragStartRotation = this.rotation;
  })
  .onActionUpdate((event: GestureEvent) => {
    const requested = this.dragStartRotation + event.offsetX * 0.55;
    const clamped = Math.max(-135, Math.min(135, requested));
    this.rotation = clamped;
    this.applyRotation(clamped);
  })

5. 解决 Component3D 吞掉触摸的问题

首轮真机上模型能显示,但材质按钮、滑杆和返回按钮都无法稳定响应。原因是全屏 Component3D 参与了命中测试。最终把 3D 组件设为只渲染,并在其上放置独立手势层和控制层:

复制代码
Component3D(this.sceneOpt)
  .width('100%')
  .height('100%')
  .hitTestBehavior(HitTestMode.None)
  .zIndex(0)

// 模型手势层位于控制区上方,避免抢占按钮和滑杆事件。
Row()
  .width('100%')
  .height('67%')
  .position({ x: 0, y: 64 })
  .hitTestBehavior(HitTestMode.Block)
  .zIndex(1)

按钮和滑杆所在控制层使用更高 zIndex。修正后,真机固定坐标操作依次触发换色、滑杆旋转、模型拖动和返回,四条链路都有截图与 Hilog。

6. 退出时释放场景

返回按钮先调用 releaseScene(),再切换页面。释放逻辑销毁程序化回退可能创建的几何体、动态材质和 Scene,并清空页面持有的节点引用。aboutToDisappear() 也调用同一方法,因此日志里会出现两次释放记录:第一次 scene=1 表示真实 Scene 被销毁,第二次 scene=0 表示引用已为空,只做幂等收口。

复制代码
private releaseScene(): void {
  const hadScene: number = this.scene ? 1 : 0;
  this.geometries.forEach((geometry: Geometry) => geometry.destroy());
  this.materials.forEach((material: Material) => material.destroy());
  this.scene?.destroy();
  this.geometries = [];
  this.materials = [];
  this.productRoot = undefined;
  this.scene = undefined;
  this.sceneOpt = undefined;
  hilog.info(DOMAIN, TAG, 'ORBIT_RELEASE scene=%{public}d', hadScene);
}

设计稿对照

真机截图去掉系统状态栏和导航栏后,按等比方式放入 942 × 1672 的设计稿画布,并生成并排图和 50% 透明叠加图。对照确认页面结构已经一致:深夜蓝画布、圆角音箱、网罩孔、上下发光环、双层底座、三枚圆形材质按钮和细滑杆均已落地。

现在这个版本已经串起 GLB 加载、PBR 换色、相机灯光、双入口旋转和资源释放。继续打磨时,可以把网罩纹理、金属拉丝和机身比例交给更精细的美术资产,而页面交互代码无需推倒重来。

真机日志

复制代码
ORBIT_INIT step=scene_load_start
ORBIT_INIT step=scene_load_done mode=glb
ORBIT_READY mode=glb camera=1 lights=2 resource=orbitdesk_speaker.glb
COLOR_SWITCH palette=珊瑚橙
SLIDER_ROTATE degrees=115
ORBIT_DRAG_START degrees=115
ORBIT_DRAG_END degrees=26
ORBIT_RELEASE scene=1

到这里,OrbitDesk 已经形成从资源加载到退出释放的完整闭环。上面这组真机日志可以在复现时快速核对关键步骤是否执行到位。

相关推荐
Latte Moments开发1 小时前
Harmony鸿蒙实战开发-记账app「可对时间进行分类管理,支出分析和收入分析-升级版」【源码在文末】
华为·harmonyos
●VON1 小时前
Flutter 鸿蒙插件适配实战:用 flutter_native_timezone_2025 1.0.1 读取当前时区与系统目录
flutter·华为·harmonyos·鸿蒙
熊猫钓鱼>_>2 小时前
ArkTS 性能优化实战:从冷启动到长列表,一套可复现的实测方法论
app·harmonyos·arkts·鸿蒙·组件·性能·arkui
熊猫钓鱼>_>2 小时前
从拍照到建模:HarmonyOS 7 3DGS端侧重建完整实战指南
人工智能·3d·ai·harmonyos·arkts·鸿蒙·3dgs
李游Leo2 小时前
HarmonyOS 7 实战开发 04:适配手机、折叠屏与大屏布局
ios·harmonyos
衝鋒壹号3 小时前
鸿蒙 PC 能跑 Docker 吗?一次从安装失败到成功运行的实测记录
后端·harmonyos
Latte Moments开发3 小时前
Harmony鸿蒙实战开发-浏览器app【源码在文末】
华为·harmonyos
柒儿吖3 小时前
SSCom 重构全记录:用 Rust 把串口调试助手送上鸿蒙 PC、macOS、Windows和Linux
测试工具·rust·harmonyos
李游Leo3 小时前
HarmonyOS 7 端侧 AI 视觉能力实战 06:把识别、增强与搜索串成完整处理链
harmonyos