鸿蒙 3D 开发实战:从模型加载到动画管理的完整实现

前言

大家好,我是simple。我的理想是利用科技手段来解决生活中遇到的各种问题。

在鸿蒙应用开发中,三维模型的集成能极大提升用户交互体验,尤其在虚拟展示、互动教育等场景中不可或缺。本文基于一份实际的鸿蒙3D模型加载代码,详细解析从模型加载、光源配置、相机调优,到动画控制与资源管理的全流程。

一、核心框架与代码结构概览

本次示例基于鸿蒙ArkUI 3D图形框架,核心依赖@kit.ArkGraphics3D提供的3D渲染能力,主要涉及Scene(场景)、Camera(相机)、Light(光源)、Animation(动画)等核心类。

二、模型加载:从资源到场景的桥梁

3D模型的加载是整个流程的基础,鸿蒙推荐使用glTF格式(.gltf或.glb),因其轻量且适合移动端渲染。代码中通过Scene.load实现模型加载,我们来拆解这一过程。

1. 资源准备与路径处理

加载时需确保模型关联的纹理、二进制文件(.bin)与gltf文件在同一目录,避免资源丢失。

2. 场景初始化流程

模型加载的核心逻辑在init方法中,通过Scene.load异步加载模型,并在加载完成后初始化场景参数:

typescript 复制代码
init(): void {
  if (this.scene == null) {
    // 加载gltf模型(核心API)
    const scene = Scene.load(this.model);
    scene.then(async (result: Scene) => {
      if (result) {
        this.scene = result;
        // 调整模型根节点位置(模型调整)
        if (result.root) {
          result.root.position = { x: 0, y: 0, z: 0 }; // 重置模型位置到原点
        }
        // 后续初始化:相机、光源、动画...
      }
    });
  }
}
  • Scene.load:异步加载模型资源,返回Promise<Scene>,场景实例包含模型的所有节点、动画等信息。
  • 模型位置调整:通过result.root.position设置模型根节点坐标,确保模型在场景中的初始位置符合预期(此处重置到原点,避免模型偏移)。

三、光源配置:让模型"可见"的关键

3D模型默认处于"无光环境",若不添加光源,模型会因无法反射光线而完全黑屏。代码中通过创建平行光(DirectionalLight)为场景提供照明,我们来分析其实现。

1. 平行光的创建与参数

typescript 复制代码
// 在模型加载完成后创建光源
let sceneFactory: SceneResourceFactory = this.scene.getResourceFactory();
let sceneLightParameter: SceneNodeParameters = { name: "light" };
// 创建平行光(DirectionalLight)
const light: Light = await sceneFactory.createLight(sceneLightParameter, LightType.DIRECTIONAL);
light.color = this.lightColor; // 从外部接收光源颜色
  • 光源类型 :选择LightType.DIRECTIONAL(平行光),模拟太阳光效果,光线平行照射,适合大多数场景。
  • 颜色控制 :通过@Prop lightColor接收外部传入的颜色,支持动态调整光源色调(如白色{r:1, g:1, b:1, a:1}或暖黄色{r:1, g:0.8, b:0.6, a:1})。

目前只有平行光和点光源。

四、相机调整:定义"观看视角"

相机是3D场景的"眼睛",决定了用户从哪个角度观察模型。代码中创建了自定义相机,并通过参数调整优化视角,关键配置如下:

1. 相机创建与核心参数

typescript 复制代码
// 创建相机
let rf: SceneResourceFactory = this.scene.getResourceFactory();
this.cam = await rf.createCamera({ "name": "Camera" });
// 相机启用
this.cam.enabled = true;
// 位置与旋转
this.cam.position = this.cameraPosition; // {x:0, y:0, z:0}(可外部调整)
this.cam.rotation = this.cameraRotation; // 初始旋转(四元数)
// 视场角与裁剪面
this.cam.fov = 75; // 视场角75度(值越大,视野越广)
this.cam.nearPlane = 0.1; // 近平面(距离相机过近的物体不渲染)
this.cam.farPlane = 1000; // 远平面(距离相机过远的物体不渲染)
  • fov(视场角):75度是平衡视角广度与模型细节的常用值,值越大场景容纳内容越多,但模型可能显小。
  • 近/远平面nearPlane=0.1避免近距离模型被裁剪,farPlane=1000确保远距离模型可见,根据模型大小调整(如小模型可减小farPlane提升性能),在这里切记nearPlane必须比farPlane要小,不然会有不可预料的错误。

2. 视角优化建议

  • 若模型显示不全:调整相机position.z(沿Z轴后退,如z:5),拉远与模型的距离。
  • 若模型倾斜:通过cameraRotation调整旋转角度(如{x: -15, y: 30, z: 0, w:1}实现俯视角)。
  • 复杂场景可添加多个相机,通过切换enabled属性实现视角切换。

五、动画控制:让模型"动起来"

3D模型的动画(如机械臂运动、角色动作)是提升交互感的核心。代码中通过Animation类管理模型动画,并实现了完整的启停控制逻辑。

1. 动画加载与初始化

模型加载完成后,通过result.animations获取所有内置动画(gltf模型可包含多个动画轨道):

typescript 复制代码
// 获取模型中的所有动画
let animations: Animation[] = result.animations;
if (animations.length) {
  this.threeDModelAnimation = animations; // 保存动画列表
  this.startAni(); // 启动动画
}

2. 动画启停与循环控制

代码封装了startAnistopAnipauseAni三个方法,分别控制动画的启动、停止与暂停:

typescript 复制代码
// 启动动画
startAni: () => void = () => {
  if (this.scene && this.visible) {
    this.threeDModelAnimation.forEach((ani) => {
      ani.onStarted(() => {
        // 绑定帧率同步,每帧渲染场景
        this.backDisplaySyncRate?.on("frame", () => {
          if (this.scene && this.cam) {
            this.scene.renderFrame(); // 渲染一帧
          }
        });
        this.backDisplaySyncRate?.start(); // 启动帧率同步
        // 动画完成后循环播放
        ani.onFinished(() => {
          if (this.visible) {
            ani.seek(0); // 回到起点
            ani.stop();
            ani.start(); // 重新启动
          }
        });
      });
      ani.start(); // 启动当前动画
    });
  }
};

// 停止动画
stopAni: () => void = () => {
  this.threeDModelAnimation.forEach((ani) => {
    ani.stop();
    ani.seek(0); // 重置到初始状态
    this.backDisplaySyncRate?.stop(); // 停止帧率同步,节省资源
  });
};
  • 帧率同步 :通过displaySync.create()创建60帧同步器(sync60),确保动画流畅且不占用额外性能。
  • 循环播放onFinished回调中重新启动动画,实现无缝循环。
  • 资源释放 :停止动画时调用backDisplaySyncRate.stop(),避免后台无效渲染。

六、可见性管理:动态资源优化

当模型不可见时(如页面切换、组件隐藏),继续运行动画会浪费系统资源。代码通过visible状态结合onVisibleAreaChange实现动态管理:

1. 可见性监测与状态联动

typescript 复制代码
build() {
  Stack() {
    // 监测可见区域变化(触发visible状态更新)
    Row()
      .width(5).height(5)
      .onVisibleAreaChange([0.0, 1.0], (visible) => {
        this.visible = visible; // 当可见比例在0~1之间时更新状态
      });
    // 3D场景渲染(仅当sceneOpt存在时显示)
    if (this.sceneOpt) {
      Component3D(this.sceneOpt);
    }
  }
}
  • onVisibleAreaChange:监听组件可见比例,当完全不可见时visible设为false,触发动画停止。

2. 可见性变化时的动画控制

通过@Watch('changeVisible')监听visible状态变化,自动启停动画:

typescript 复制代码
@Watch('changeVisible')
@State visible: boolean = false;

changeVisible() {
  if (this.visible) {
    // 可见时:若未初始化则初始化,否则启动动画
    if (!this.scene) {
      this.init();
    } else {
      this.startAni();
    }
  } else {
    // 不可见时:停止动画
    this.stopAni();
  }
}

这一机制确保模型在隐藏时完全释放动画资源,显著提升应用后台性能。

七、代码封装与扩展:ThreeDController的作用

为了让动画控制逻辑更清晰,代码通过ThreeDController封装了启停方法,方便外部调用:

typescript 复制代码
// 控制器定义(简化)
export class ThreeDController {
  stop?: () => void;
  start?: () => void;
  pause?: () => void;
}

// 组件中关联控制器
controller: ThreeDController = new ThreeDController();

aboutToAppear(): void {
  this.controller.stop = this.stopAni;
  this.controller.start = this.startAni;
  this.controller.pause = this.pauseAni;
}

外部可通过获取controller实例,灵活控制动画(如按钮点击暂停/继续):

typescript 复制代码
// 外部调用示例
ThreeDModel({ model: $rawfile('model.gltf') })
  .onReady((controller) => {
    // 点击按钮暂停动画
    Button('暂停').onClick(() => controller.pause());
  });

八、常见问题

  1. 模型格式选择 :优先使用glb格式(二进制glTF),相比gltf减少文件数量,加载更快。
  2. 常见问题排查
    • 模型不显示:检查光源是否添加、相机位置是否正确(避免模型在相机后方);
    • 动画不播放:确认gltf模型包含动画轨道(可通过Blender导出时勾选"动画"选项);
  3. 三维模型审核不通过
    • 在不可见时,模型未被销毁但仍然在运行动画等会造成资源浪费,从而导致上架审核不通过,可参考第六点。
相关推荐
nanchen22511 小时前
从混沌到有序:揭秘团购鸿蒙高内聚、可扩展的现代化前端架构
harmonyos
长弓三石1 小时前
鸿蒙网络编程系列59-仓颉版TLS回声服务器示例
harmonyos·鸿蒙·tls·仓颉
yrjw4 小时前
一款基于 ReactNative 最新发布的`Android/iOS` 新架构文档预览开源库
harmonyos
ajassi20004 小时前
开源 Arkts 鸿蒙应用 开发(十三)音频--MP3播放
linux·华为·开源·harmonyos
zhanshuo16 小时前
让鸿蒙应用丝滑如飞:绘图性能优化全攻略(附代码实战)
harmonyos
zhanshuo16 小时前
适配鸿蒙低性能设备的终极优化方案:从启动到渲染全链路实战
harmonyos
Georgewu16 小时前
【HarmonyOS】鸿蒙ArkWeb加载优化方案详解
harmonyos
Georgewu17 小时前
【HarmonyOS】鸿蒙应用HTTPDNS 服务集成详解
harmonyos
litongde20 小时前
React Native 编程
react native·harmonyos