第2.7篇:3D 模型渲染入门——@kit.ArkGraphics3D 初探

难度:⭐⭐⭐ 高级
前置知识:2.5 视频播放器集成
涉及源文件products/default/src/main/ets/pages/ModelViewerPage.ets


在这里插入图片描述

前言

在"画伴梦工厂"的绘画转动画流程中,3D 模型展示是一个重要的视觉环节。HarmonyOS 提供了 @kit.ArkGraphics3D 图形能力集,让开发者可以轻松加载 glTF/GLB 格式的 3D 模型,并在应用中渲染出精美的 3D 场景。

本篇将首次探索 ArkGraphics3D 的核心概念——Scene(场景)、Camera(相机)、Light(灯光)、Node(节点),并一步步实现从 glTF 文件加载到 3D 场景渲染的完整流程。


1. ArkGraphics3D 核心概念

在动手编码之前,先理清四个核心概念:

概念 说明 类比
Scene(场景) 3D 世界的容器,管理所有节点、相机、灯光 一个舞台
Camera(相机) 定义观察者的位置、视角范围、远近裁剪面 摄影师的眼睛
Light(灯光) 为场景提供光照,决定了物体的明暗和立体感 舞台灯光
Node(节点) 场景中的基本元素,可以是模型、空节点等 舞台上的演员

它们的关系是:Scene 包含 Camera、Light、Node,Camera 决定用户看到什么,Light 决定物体看起来什么样,Node 承载具体的模型数据。


2. 导入 ArkGraphics3D 模块

首先从 @kit.ArkGraphics3D 中导入需要的类型:

import {
  Camera, Color, Light, LightType, Node,
  Quaternion, RenderContext, RenderParameters,
  Scene, SceneNodeParameters, SceneResourceFactory
} from '@kit.ArkGraphics3D';

这些类型涵盖了 3D 场景渲染的核心要素。注意 SceneResourceFactory 是用于创建相机和灯光的工厂类,Quaternion 用于处理旋转(下篇文章会详细展开)。


3. 加载 GLB 模型:Scene.load + importScene

加载一个标准 glTF/GLB 模型需要两步:

第一步:从 rawfile 资源加载源场景(sourceScene),此时模型数据位于 sourceScene 中。

第二步:创建一个新的空场景(scene),然后通过 importScene 将 sourceScene 中的模型节点导入到目标场景中。

private async loadGlbModel(): Promise<void> {
  try {
    this.status = '正在加载普通 GLB 模型';

    // 从 rawfile 加载源场景
    const sourceScene: Scene = await Scene.load($rawfile('assets/gltf/car.glb'));

    // 创建空场景
    const scene: Scene = await Scene.load();

    // 将源场景中的模型导入到新场景的根节点下
    const previewRoot: Node = scene.importScene('PreviewModel', sourceScene, scene.root);
    previewRoot.visible = true;

    // 设置模型位置和缩放
    previewRoot.position.x = 0;
    previewRoot.position.y = 0;
    previewRoot.position.z = 0;
    previewRoot.scale.x = 1;
    previewRoot.scale.y = 1;
    previewRoot.scale.z = 1;

    // 配置相机和灯光
    await this.setupDebugView(scene);

    // 保存引用
    this.activeScene = scene;
    this.modelRoot = previewRoot;

    // 应用初始旋转
    this.applyModelRotation();

    // 配置 SceneOptions 供 Component3D 使用
    const options: SceneOptions = {
      scene: scene,
      modelType: ModelType.SURFACE
    };
    this.sceneOptions = options;

    this.status = '普通 GLB 模型加载成功';
  } catch (error) {
    this.status = '普通 GLB 加载失败: ' + this.getErrorMessage(error as Error);
  }
}

关键细节:

  • Scene.load() 无参数调用创建一个空场景;Scene.load($rawfile(...)) 从 rawfile 加载包含模型的场景。
  • scene.importScene(name, sourceScene, parentNode) 将 sourceScene 中的完整节点树导入到当前场景,挂载到指定父节点下。
  • ModelType.SURFACE 表示使用表面渲染模式(相对于点云、线框等)。

4. 设置相机和灯光:SceneResourceFactory

通过 scene.getResourceFactory() 获取工厂实例,然后创建 Camera 和 Light。

4.1 创建 Camera

private async setupDebugView(scene: Scene): Promise<void> {
  const factory: SceneResourceFactory = scene.getResourceFactory();

  // 创建相机
  const cameraParams: SceneNodeParameters = { name: 'DebugCamera' };
  const camera: Camera = await factory.createCamera(cameraParams);

  const clearColor: Color = {
    r: 0.94,
    g: 0.96,
    b: 1,
    a: 1       // 浅蓝灰色背景
  };

  camera.enabled = true;
  camera.fov = Math.PI / 4;      // 45° 视场角
  camera.nearPlane = 0.1;        // 近裁剪面 0.1
  camera.farPlane = 1000;        // 远裁剪面 1000
  camera.clearColor = clearColor; // 背景色
  camera.position.x = 0;
  camera.position.y = 1.2;       // 略微仰视
  camera.position.z = 5;         // 相机在 Z 轴正方向

  this.debugCamera = camera;

  // ... 创建灯光
}

Camera 关键属性解析:

属性 说明
fov Math.PI / 4 垂直视场角 45°,数值越大视野越宽
nearPlane 0.1 近裁剪面,小于此距离的物体不可见
farPlane 1000 远裁剪面,超出此距离的物体不可见
clearColor Color 渲染前的背景清除颜色
position (0, 1.2, 5) 相机在世界空间中的位置

clearColor 在这里设为浅蓝灰色 (0.94, 0.96, 1.0),给场景一个柔和的背景色。

4.2 创建 DIRECTIONAL 光源

  // 创建平行光
  const lightParams: SceneNodeParameters = { name: 'DebugLight' };
  const light: Light = await factory.createLight(lightParams, LightType.DIRECTIONAL);

  const lightColor: Color = {
    r: 1,
    g: 1,
    b: 1,
    a: 1        // 纯白光
  };

  light.enabled = true;
  light.intensity = 8;          // 光照强度
  light.color = lightColor;
  light.position.x = 0;
  light.position.y = 3;         // 从上方照射
  light.position.z = 4;

  this.debugLight = light;
}

Light 关键属性解析:

属性 说明
LightType.DIRECTIONAL 平行光 模拟太阳光,所有光线平行
intensity 8 光照强度,值越大越亮
color (1,1,1,1) 纯白色
position (0, 3, 4) 光源位置(对平行光而言是方向参考)

平行光(DIRECTIONAL)的特点是光线方向一致、没有衰减,适合作为主光源照亮整个场景。


5. Component3D 渲染 3D 场景

模型加载完成后,通过 SceneOptions 配置场景,然后使用 Component3D 组件进行渲染:

// 构建 SceneOptions
const options: SceneOptions = {
  scene: scene,
  modelType: ModelType.SURFACE
};
this.sceneOptions = options;

在 UI 中使用 Component3D:

// @Builder
// private RenderPanel() {
//   Stack() {
//     if (this.sceneOptions !== null) {
//       Component3D(this.sceneOptions)
//         .width('100%')
//         .height('100%')
//     } else {
//       Column() {
//         LoadingProgress().width(36).height(36)
//         Text(this.status).fontSize(14)
//       }
//     }
//   }
//   .width('90%').height(360).backgroundColor('#10131F')
//   .borderRadius(18).clip(true)
// }

Component3D 是 ArkGraphics3D 提供的 ArkUI 组件,传入 SceneOptions 即可渲染 3D 场景。加载过程中可以用 LoadingProgress 显示等待状态。


6. requestRenderFrame 主动触发渲染

当模型旋转或场景发生变化时,需要主动请求渲染帧更新:

private requestRenderFrame(): void {
  if (this.activeScene === null) {
    return;
  }
  const params: RenderParameters = {
    alwaysRender: true
  };
  this.activeScene.renderFrame(params);
}

RenderParameters 中的 alwaysRender: true 表示即使场景没有变化也强制渲染,确保每次更新都能立即呈现。


7. 模型文件与路径

private readonly modelPath: string = 'assets/gltf/car.glb';
private readonly gsModelUri: string = 'OhosRawFile://assets/gltf/car.glb';
  • modelPath:rawfile 中的相对路径。
  • gsModelUriOhosRawFile:// 协议 URI,专用于访问 rawfile 资源。

模型文件 car.glb 位于 entry/src/main/resources/rawfile/assets/gltf/ 目录下。


8. 完整流程总结

┌─────────────────────────────────────────────────┐
│                加载流程总览                        │
├─────────────────────────────────────────────────┤
│                                                   │
│  Scene.load($rawfile)                             │
│       │                                           │
│       ▼                                           │
│  sourceScene(含模型数据)                          │
│       │                                           │
│  Scene.load() → 空 scene                          │
│       │                                           │
│  scene.importScene('Preview', sourceScene, root)   │
│       │                                           │
│       ▼                                           │
│  previewRoot(模型节点)                             │
│       │                                           │
│  setupDebugView(scene)                             │
│       │                                           │
│       ├── factory.createCamera()                  │
│       └── factory.createLight(DIRECTIONAL)        │
│       │                                           │
│       ▼                                           │
│  SceneOptions { scene, ModelType.SURFACE }         │
│       │                                           │
│       ▼                                           │
│  Component3D(options) → 渲染 3D 场景              │
│                                                   │
└─────────────────────────────────────────────────┘

小结

本篇我们完成了以下工作:

  1. 理解了 Scene / Camera / Light / Node 四者的角色与关系。
  2. 使用 Scene.load() 加载 glTF 模型,并通过 importScene 导入场景。
  3. 使用 SceneResourceFactory 创建 Camera 并配置 fovnearPlanefarPlaneclearColorposition
  4. 使用 SceneResourceFactory 创建 DIRECTIONAL 平行光 并配置 intensitycolorposition
  5. 通过 SceneOptions + Component3D 在 ArkUI 中渲染 3D 场景。
  6. 使用 renderFrame 主动触发渲染更新。

这是 ArkGraphics3D 的入门基础,下篇将继续在本篇基础上实现手势拖拽旋转模型,让 3D 模型"动"起来。


下一篇预告:第2.8篇:手势交互——拖拽旋转 3D 模型

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐