《HarmonyOS 7 ArkGraphics 3D 空间设计开发实战》07:复杂3D场景的帧率、内存与资源性能优化【鸿蒙心迹】

家具加到50个以后,为什么转视角开始卡了?

前言

前面六篇,SpaceRoom 从空房间做到了能选家具、能转视角。房间里放个十来件家具,跑起来很流畅。

然后我放了 50 件家具进去,问题来了:转视角的时候开始掉帧,拖一下卡一下,内存也涨了不少。

第一反应是:手机性能不够吧?然后我打印了一下帧率,发现还真不是 GPU 跑不动,而是很多地方做了不必要的工作。

这一篇就讲:3D 场景卡顿,不能只盯着 GPU。节点数量、纹理大小、重复材质、不可见对象、每帧的逻辑更新,这些都会影响性能。先把性能数据测出来,才知道该优化哪里。


一、先测数据:卡顿到底卡在哪

很多人做性能优化,上来就"模型减面、纹理压缩",结果做完发现帧率还是上不去。因为你都不知道瓶颈在哪,优化就是瞎猜。

先建一个性能监控,把关键指标打出来:

typescript 复制代码
export class ScenePerformanceMonitor {
  private frameCount: number = 0;
  private lastTime: number = 0;
  private fps: number = 0;

  // 统计数据
  private stats = {
    nodeCount: 0,        // 节点总数
    meshCount: 0,        // Mesh数量
    materialCount: 0,    // 材质数量
    textureCount: 0,     // 纹理数量
    memoryMB: 0,         // 内存占用
    initTimeMs: 0,       // 场景初始化耗时
    frameTimeMs: 0       // 每帧耗时
  };

  /**
   * 每帧调用,统计FPS
   */
  onFrame(): void {
    this.frameCount++;
    const now = Date.now();

    if (now - this.lastTime >= 1000) {
      this.fps = this.frameCount;
      this.frameCount = 0;
      this.lastTime = now;

      console.info(`PerfMonitor: FPS=${this.fps}, nodes=${this.stats.nodeCount}, meshes=${this.stats.meshCount}`);
    }
  }

  /**
   * 收集场景统计
   */
  collectStats(scene: scene.Scene): void {
    // 递归统计节点数
    this.stats.nodeCount = this.countNodes(scene.getRoot());
    console.info('PerfMonitor: stats collected', this.stats);
  }

  private countNodes(node: scene.Node): number {
    let count = 1;
    const children = node.getChildren();
    for (const child of children) {
      count += this.countNodes(child);
    }
    return count;
  }
}

先把数据打出来,才能知道问题在哪。常见的问题有:

指标 正常范围 异常表现
FPS 50~60 低于30就明显卡
节点数 几百个以内 上千个就要注意
纹理内存 几十MB 超过100MB就吃紧
每帧耗时 16ms以内 超过33ms就是30帧

二、最常见的优化:不可见对象不渲染

第一个最容易做的优化:相机看不到的物体,就不要渲染了。

比如房间里有个柜子,柜子门是关着的,柜子里面的东西相机根本看不到。但如果柜子里面的模型也在渲染,就是浪费。

更常见的情况:用户视角转到另一边,左边的家具完全在视野外面,但还是每帧都在算。

这个叫视锥体裁剪(Frustum Culling):相机视野是个锥形,锥形外面的物体直接跳过渲染。

ArkGraphics 3D 应该会自动做这个,但我们自己也可以加一层:离相机太远的小物体,直接不渲染。

typescript 复制代码
export class VisibilityManager {
  private cameraPos: {x: number, y: number, z: number} = {x: 0, y: 1.6, z: 5};

  /**
   * 每帧更新可见性
   */
  updateVisibility(nodes: scene.Node[]): void {
    for (const node of nodes) {
      const pos = node.position;
      // 算一下离相机多远
      const dx = pos.x - this.cameraPos.x;
      const dy = pos.y - this.cameraPos.y;
      const dz = pos.z - this.cameraPos.z;
      const dist = Math.sqrt(dx*dx + dy*dy + dz*dz);

      // 超过30米的小物体,直接隐藏
      if (dist > 30) {
        if (node.visible) {
          node.visible = false;
        }
      } else {
        if (!node.visible) {
          node.visible = true;
        }
      }
    }
  }
}

这个优化很简单,但效果明显。尤其是大场景里,远处的小物体全部跳过渲染,帧率马上就上来了。


三、重复材质和纹理:别每个家具都来一份

第二个常见问题:10把椅子,每把椅子都加载了一份纹理。其实椅子都是一样的,纹理只需要一份,所有椅子共享。

这个和之前说的 Resource 缓存是一个道理,但材质和纹理也要做缓存:

typescript 复制代码
export class MaterialCache {
  private materialMap: Map<string, scene.Material> = new Map();

  /**
   * 获取共享材质
   */
  getMaterial(texturePath: string): scene.Material | null {
    // 已经有了直接返回
    if (this.materialMap.has(texturePath)) {
      return this.materialMap.get(texturePath)!;
    }

    // 没有就创建
    const material = ... // 创建材质,加载纹理
    this.materialMap.set(texturePath, material);
    return material;
  }
}

10把椅子共享一份布料纹理,纹理内存直接省了 90%。这个优化在家具多的时候效果特别明显。


四、别每帧都更新不需要变的东西

第三个问题:每帧都在做不必要的逻辑更新。

比如:家具的位置从来没变过,但每帧都在重新算它的世界坐标;UI 状态从来没变过,但每帧都在刷新;动画已经停了,但每帧还在检查。

这些看起来都是小事,但每帧省一点,加起来就是帧率提升。

一个原则:只有真的会变的东西,才每帧更新。

东西 什么时候更新
相机位置 手势操作的时候更新,不是每帧
家具位置 用户拖动的时候更新
选中高亮 选中状态变化的时候更新
可见性 相机移动的时候更新,不是每帧

很多人写代码习惯了 onFrame() 里什么都做,结果就是每帧跑一大堆没用的逻辑。


五、几个最容易踩的性能坑

把这一篇遇到的坑总结一下:

坑 现象 解决办法
上来就减面压缩纹理 做了半天帧率没提升 先测数据,找到瓶颈再优化
所有物体都渲染 视野外的物体也在算 视锥体裁剪,远处的隐藏
每个家具一份纹理 纹理内存爆了 相同材质纹理共享缓存
每帧都跑所有逻辑 CPU占用高,帧率上不去 只在状态变化的时候更新
退出页面不释放资源 退出再进,内存越来越大 页面销毁时释放所有资源
只看FPS不看内存 帧率还行,但App被系统杀了 内存和帧率都要监控

3D 性能优化不是玄学,是先测数据、再找瓶颈、然后针对性优化。不要上来就瞎优化。


总结

第七篇的核心就一句话:卡顿不能只怪 GPU,先测数据再优化。

  1. 先建性能监控,把 FPS、节点数、内存都打出来
  2. 相机看不到的物体,直接隐藏不渲染
  3. 相同的材质和纹理,所有家具共享,不要每个都来一份
  4. 只有真的会变的东西,才每帧更新
  5. 退出页面记得释放资源,不然内存越积越多

SpaceRoom 现在家具多了也能流畅跑了。最后一篇做工程收尾:加动画、管生命周期,把整个项目整理成一个可维护的工程。

相关推荐
天神哥哥啊3 小时前
unity联调注意事项-安卓
android·unity·游戏引擎
机核研创社3 小时前
polo 长袖短裤套装自动化产线:八台机器排队接力
android·java·自动化
JosieBook3 小时前
【数据库】MySQL 实战精通系列 · 第2篇:数据建模与建表实战
android·数据库·mysql
Martin -Tang3 小时前
uniapp app嵌套webview 弹窗方式
android·ios·uni-app
行者-全栈开发3 小时前
华为云码道 CodeArts 实测:让 AI 独立开发一个鸿蒙原生专注计时应用「刻循」
harmonyos·arkts·鸿蒙·ai 编程·华为云码道·codearts 代码智能体·专注计时
resh_people3 小时前
开源鸿蒙平台 KMP/CMP 三方库「kotlinx-datetime」适配全流程
华为·开源·harmonyos
福兮说11 小时前
前端下载大文件,点了按钮半天没反应:fetch + blob 的三个问题和三种替代写法
前端·javascript·性能优化·文件下载
蒸鱼Yuzheng12 小时前
设备端性能工件可靠导出:断点续传、哈希、manifest 与失败恢复
android·自动化测试·python·adb·数据完整性
美狐美颜sdk13 小时前
直播APP接入美颜SDK后出现卡顿怎么办?从性能瓶颈寻找解决方案
android·人工智能·音视频·美颜sdk·直播美颜sdk