文章目录
-
- 前言
- [一、环境准备与 API 导入](#一、环境准备与 API 导入)
-
- [1.1 Kit 导入说明](#1.1 Kit 导入说明)
- [1.2 组件状态设计](#1.2 组件状态设计)
- [二、Scene 异步加载流程](#二、Scene 异步加载流程)
-
- [2.1 使用 Scene.load 加载 GLB 文件](#2.1 使用 Scene.load 加载 GLB 文件)
- [2.2 GLB 文件放置位置](#2.2 GLB 文件放置位置)
- 三、相机配置详解
-
- [3.1 相机参数说明](#3.1 相机参数说明)
- [3.2 背景类型对比](#3.2 背景类型对比)
- 四、生命周期管理
-
- [4.1 资源释放](#4.1 资源释放)
- 总结
前言
随着 HarmonyOS 6 的正式推出,@kit.ArkGraphics3D 为开发者带来了原生 3D 渲染能力。无论是工业产品展示、虚拟试穿还是 AI 生成模型的预览,3D 内容已经成为移动端应用的重要组成部分。本文将以一个完整的 GLB 模型加载项目为例,深入讲解 Scene 的异步加载流程 、相机创建与配置 、节点树遍历 以及生命周期管理,帮助你在 HarmonyOS 应用中快速接入 3D 渲染。
本文代码来源于一个真实项目------在 HarmonyOS 设备上实时预览 Hunyuan 3D 生成的 GLB 模型,所有代码片段均可直接运行。
运行效果如下:

一、环境准备与 API 导入
1.1 Kit 导入说明
HarmonyOS 6 将 3D 图形相关 API 整合进 @kit.ArkGraphics3D,使用前只需一行导入:
typescript
import { Animation, Scene, Camera, Node, EnvironmentBackgroundType, SceneResourceFactory } from '@kit.ArkGraphics3D';
import { AnimatorResult } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
| 导入符号 | 作用 |
|---|---|
| Scene | 3D 场景容器,承载所有节点、相机、动画 |
| Camera | 相机对象,控制视角和渲染参数 |
| Node | 场景节点,承载变换(位置/旋转/缩放)信息 |
| Animation | 场景内置动画轨道 |
| SceneResourceFactory | 资源工厂,用于创建相机、灯光等运行时资源 |
| EnvironmentBackgroundType | 背景类型枚举 |
| AnimatorResult | ArkUI 帧驱动动画控制器 |
提示:
@kit.ArkGraphics3D从 HarmonyOS 12(API Level 12)起支持,确保build-profile.json5中compileSdkVersion不低于 12。
1.2 组件状态设计
场景加载是异步操作,UI 需要根据加载状态动态切换,因此将 sceneOpt 设计为可为 null 的响应式变量:
typescript
@Entry
@Component
struct Index {
@State sceneOpt: SceneOptions | null = null;
@State isAnimPlaying: boolean = false;
scene: Scene | null = null;
cam: Camera | null = null;
anim: Animation | null = null;
modelNode: Node | null = null;
@State sceneOpt 初始为 null,加载完成后赋值,UI 层通过 if (this.sceneOpt) 条件渲染,实现加载态与渲染态的平滑切换。scene、cam、anim、modelNode 不需要触发 UI 更新,故使用普通成员变量。
二、Scene 异步加载流程
2.1 使用 Scene.load 加载 GLB 文件
Scene.load 是加载 3D 模型的核心 API,支持 GLB/GLTF 格式,接受 $rawfile() 资源引用:
typescript
initScene(): void {
if (this.scene !== null) {
return;
}
Scene.load($rawfile('82.glb'))
.then(async (result: Scene) => {
if (!result) {
hilog.error(0x0000, TAG, 'Scene.load returned null');
return;
}
this.scene = result;
this.scene.environment.backgroundType = EnvironmentBackgroundType.BACKGROUND_NONE;
let rf: SceneResourceFactory = this.scene.getResourceFactory();
this.cam = await rf.createCamera({ name: 'Camera1' });
this.cam.enabled = true;
this.cam.position.z = 5;
this.cam.clearColor = { r: 0.05, g: 0.05, b: 0.05, a: 1.0 };
// 获取根节点下第一个子节点作为旋转目标
if (this.scene.root && this.scene.root.children.count() > 0) {
this.modelNode = this.scene.root.children.get(0);
hilog.info(0x0000, TAG, 'modelNode: %{public}s', this.modelNode?.name ?? 'null');
}
if (this.scene.animations && this.scene.animations.length > 0) {
this.anim = this.scene.animations[0];
this.anim.enabled = true;
}
this.sceneOpt = { scene: this.scene, modelType: ModelType.SURFACE } as SceneOptions;
// 启动帧驱动
this.frameAnimator?.play();
hilog.info(0x0000, TAG, 'Scene init done');
})
.catch((reason: string) => {
hilog.error(0x0000, TAG, 'Scene.load error: %{public}s', reason);
});
}
加载流程分为以下几个关键步骤:
- 幂等保护 :
if (this.scene !== null) return;防止重复加载(页面刷新或aboutToAppear多次触发时) - 背景设置 :
BACKGROUND_NONE关闭环境贴图背景,由相机的clearColor填充纯色背景 - 相机创建 :通过
SceneResourceFactory.createCamera异步创建,设置position.z = 5将相机置于模型前方 - 节点获取 :通过
scene.root.children.get(0)获取模型的根节点,作为后续旋转的操作目标 - 最后赋值 :
sceneOpt必须在所有初始化完成后才赋值,确保Component3D创建时场景已就绪
提示:
sceneOpt赋值顺序非常关键。若在相机创建前就赋值,Component3D会以无相机状态开始渲染,导致黑屏。始终将this.sceneOpt = ...放在.then()回调的最后一行。
2.2 GLB 文件放置位置
GLB 文件需放在 entry/src/main/resources/rawfile/ 目录下,通过 $rawfile('文件名.glb') 引用:
entry/
src/
main/
resources/
rawfile/
82.glb <- 模型文件放这里
rawfile 目录的文件不经过资源编译,以原始二进制形式打包进 HAP,适合存放体积较大的二进制资源。
三、相机配置详解
3.1 相机参数说明
typescript
this.cam = await rf.createCamera({ name: 'Camera1' });
this.cam.enabled = true;
this.cam.position.z = 5;
this.cam.clearColor = { r: 0.05, g: 0.05, b: 0.05, a: 1.0 };
| 属性 | 值 | 说明 |
|---|---|---|
| enabled | true | 激活相机,场景必须有至少一个 enabled 的相机才能渲染 |
| position.z | 5 | 相机在 Z 轴正方向距离原点 5 个单位,从正面看向模型 |
| clearColor | {r:0.05, g:0.05, b:0.05, a:1.0} | 深灰色背景,避免纯黑背景与模型边缘混淆 |
提示:
position.z的合适值取决于模型的实际尺寸。GLB 模型单位通常为米,若模型过小或过大,可通过调整position.z或模型节点的scale属性修正视野。
3.2 背景类型对比
| 背景类型 | 效果 | 适用场景 |
|---|---|---|
| BACKGROUND_NONE | 使用 clearColor 纯色填充 | 产品展示、AR 合成底层 |
| BACKGROUND_IMAGE | 使用指定图片作为背景 | 场景渲染、全景展示 |
| BACKGROUND_CUBEMAP | 使用立方体贴图 | 写实渲染、IBL 照明 |
| BACKGROUND_EQUIRECTANGULAR | 全景 equirectangular 图 | 360 度场景 |
四、生命周期管理
4.1 资源释放
3D 场景占用 GPU 显存,必须在页面销毁时主动释放:
typescript
aboutToAppear(): void {
this.initAnimator();
this.initScene();
}
aboutToDisappear(): void {
this.frameAnimator?.cancel();
if (this.scene) {
this.scene.destroy();
this.scene = null;
}
this.cam = null;
this.anim = null;
this.modelNode = null;
}
aboutToDisappear 中的释放顺序很重要:
- 先停止
frameAnimator(避免帧回调继续访问已销毁的场景) - 再调用
scene.destroy()释放 GPU 资源 - 将所有引用置 null,帮助 GC 回收 JS 层对象
提示:若不调用
scene.destroy(),切换页面后 GPU 显存不会释放,多次进出页面将导致内存持续增长,最终触发系统 OOM。
总结
本文详细讲解了在 HarmonyOS 6 中使用 @kit.ArkGraphics3D 加载 GLB 模型的完整流程:通过 Scene.load 异步加载模型文件,使用 SceneResourceFactory 创建相机,通过节点树 API 获取模型节点,以及在生命周期钩子中正确管理资源。核心要点是初始化顺序 (sceneOpt 最后赋值)和资源释放 (aboutToDisappear 中调用 scene.destroy()),掌握这两点可以避免绝大多数黑屏和内存泄漏问题。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
需要源码的记得私聊哦