《HarmonyOS 7 ArkGraphics 3D 空间设计开发实战》01:从空场景到第一个可运行的3D房间【鸿蒙心迹】
ArkGraphics 3D 系列开篇:Scene、Camera、Light、Node 全部创建成功,为什么屏幕还是一片黑?

前言
做过 3D 开发的人大概都遇到过这个问题:工程跑起来了,没有报错,Scene 初始化成功了,Node 也 addChild 了,Camera 也设置了,Light 也加了——但模拟器屏幕就是一片空白。
我第一次接 ArkGraphics 3D 的时候就卡在这一步。代码里一行 try-catch 都没有,日志也没有任何异常,但运行结果就是看不到任何东西。然后开始一步步排查:Node 加进去了吗?加了。Camera 位置对吗?感觉对。有光照吗?有啊。
最后发现的问题特别简单:Camera 放在了立方体内部,而立方体又没有给背面剔除,所以从 Camera 视角看过去全是模型内部的面,什么都看不到。
这不是 API 不会用,是 3D 场景里几个基础角色之间的关系没有理清楚。ArkGraphics 3D 把 Scene、Node、Camera、Light、Geometry、Material 都做成了独立对象,但它们之间不是各自存在就能工作的。
这一篇不急着写复杂模型,就把 SpaceRoom 这个 Demo 的第一版搭起来:一个能看到地面和测试立方体的空白 3D 房间。同时把 3D 场景里最基础的几个角色之间的关系讲清楚。
一、为什么 3D 对象创建成功,不代表能被看见
先列一个表,把 3D 场景里"创建成功"和"能被看见"之间需要满足的条件拆开:
| 需要回答的问题 | 对应对象 | 如果不满足会怎样 |
|---|---|---|
| 场景根节点存在吗 | Scene | 所有 Node 没有挂载点 |
| Node 真正加入节点树了吗 | addChild() | 对象创建了但不在场景里 |
| Camera 在场景里吗 | Scene.addChild(cameraNode) | 没有观察视角,画面空白 |
| Camera 朝向物体吗 | Camera.lookAt() | 相机对着天空或地面 |
| 物体在 Camera 视野范围内吗 | Position + FOV | 物体在相机后面或太远 |
| 有光照吗 | Light Node | 模型全黑或全白 |
| Material 绑定到 Mesh 了吗 | Geometry + Material | 模型没有颜色 |
| 物体尺寸合理吗 | Scale | 太小看不见,太大出视野 |
这个表其实就是我第一次排查黑屏问题时列的。每一行对应一个检查点,一个个排除以后才能找到真正的原因。
很多 3D 教程上来就贴一段"创建 Scene + 创建 Cube + 创建 Camera"的代码,跑起来就能看到效果。但真实工程里,没有人会把所有参数都写对,尤其是当这些对象分散在不同类、不同生命周期方法里的时候。
所以 SpaceRoom 第一版的目标不是做一个好看的房间,而是把这几个角色的职责和依赖关系理清楚。
二、工程结构:先把几个管理器定下来
后面 8 篇都会在这个工程结构上继续加东西,所以第一篇先把骨架搭好:
SpaceRoom/
├── scene/
│ └── SceneManager.ets # 负责 Scene 生命周期
├── camera/
│ └── CameraController.ets # 负责 Camera 位置和朝向
├── model/
│ └── ModelManager.ets # 负责模型加载和 Node 管理
├── entity/
│ └── FurnitureEntity.ets # 业务对象,和 Scene Node 对应
└── ui/
└── SpaceRoomPage.ets # ArkUI 页面入口
这个划分不是为了显得工程规范,是因为后面会发现:3D 对象的生命周期和 ArkUI 页面的生命周期不一样,模型资源和业务对象也不能混在一起。如果一开始全部写在 Page 里,到第 03 篇做资源缓存的时候就会很难拆。
先写 SceneManager 的基础版本:
import { scene } from '@kit.ArkGraphics3DKit';
export class SceneManager {
private sceneView: scene.SceneView | null = null;
private scene: scene.Scene | null = null;
// 初始化 Scene,绑定到 ArkUI 的 SceneView 组件
init(sceneView: scene.SceneView): void {
this.sceneView = sceneView;
this.scene = sceneView.getScene();
console.info('SceneManager: scene initialized');
}
getScene(): scene.Scene | null {
return this.scene;
}
// 释放时清理
release(): void {
this.scene = null;
this.sceneView = null;
}
}
SceneManager 很薄,就做两件事:持有 Scene 引用,提供给其他模块用;页面销毁的时候把引用清掉。
接下来是 CameraController。这里要特别注意:Camera 本身也是一个 Node,必须加到 Scene 节点树里才能工作:
import { scene } from '@kit.ArkGraphics3DKit';
import { SceneManager } from './SceneManager';
export class CameraController {
private cameraNode: scene.Node | null = null;
private camera: scene.Camera | null = null;
constructor(private sceneMgr: SceneManager) {}
// 创建 Camera 并加入场景
setupCamera(): void {
const scene = this.sceneMgr.getScene();
if (!scene) {
console.error('CameraController: scene not ready');
return;
}
// 1. 创建 Camera 节点
this.cameraNode = scene.createNode('MainCamera');
this.camera = this.cameraNode.createComponent(scene.NodeComponentType.CAMERA) as scene.Camera;
// 2. 设置位置:站在 (0, 1.6, 5) 的高度看房间
this.cameraNode.position = { x: 0, y: 1.6, z: 5 };
// 3. 朝向原点
this.camera.lookAt = { x: 0, y: 0, z: 0 };
// 4. 关键:把 Camera 节点加到 Scene 根节点
scene.getRoot().addChild(this.cameraNode);
console.info('CameraController: camera ready at (0, 1.6, 5)');
}
getCameraNode(): scene.Node | null {
return this.cameraNode;
}
}
这里最容易漏掉的一行就是 scene.getRoot().addChild(this.cameraNode)。Camera 对象创建出来以后,如果不挂到节点树上,Scene 根本不知道用哪个 Camera 来渲染画面。

三、加入第一个可见物体:地面和测试立方体
Camera 设置好了,接下来需要在场景里放点东西。先放一个地面平面,再放一个立方体当测试模型。
这里要区分三个概念:
- Node:节点树里的一个对象,有 Position、Rotation、Scale
- Geometry:几何体,定义这个 Node 长什么样(立方体、平面、球体)
- Material:材质,定义这个物体表面是什么颜色、什么质感
import { scene } from '@kit.ArkGraphics3DKit';
import { SceneManager } from './SceneManager';
export class ModelManager {
constructor(private sceneMgr: SceneManager) {}
// 创建地面
createFloor(): void {
const scene = this.sceneMgr.getScene();
if (!scene) return;
// 1. 创建 Node
const floorNode = scene.createNode('Floor');
// 2. 创建平面几何体
const geo = floorNode.createComponent(scene.NodeComponentType.MATERIAL) as scene.Material;
// 地面放在 y=0 位置,尺寸放大
floorNode.position = { x: 0, y: 0, z: 0 };
floorNode.scale = { x: 10, y: 1, z: 10 };
// 3. 加到节点树
scene.getRoot().addChild(floorNode);
console.info('ModelManager: floor added');
}
// 创建测试立方体
createTestCube(): void {
const scene = this.sceneMgr.getScene();
if (!scene) return;
const cubeNode = scene.createNode('TestCube');
// 立方体放在地面上方
cubeNode.position = { x: 0, y: 0.5, z: 0 };
cubeNode.scale = { x: 1, y: 1, z: 1 };
scene.getRoot().addChild(cubeNode);
console.info('ModelManager: test cube added at (0, 0.5, 0)');
}
}
现在场景里有:Camera、地面、立方体。但跑起来可能还是不对——因为还没有光。
四、光源:为什么加了 Light 模型才看得见
3D 渲染不是简单地把模型颜色画出来,而是要模拟光照在物体表面的反射。没有 Light Node 的时候,场景要么全黑,要么只显示材质本身的自发光颜色。
加一个方向光当主光源:
import { scene } from '@kit.ArkGraphics3DKit';
import { SceneManager } from './SceneManager';
export class LightManager {
constructor(private sceneMgr: SceneManager) {}
setupMainLight(): void {
const scene = this.sceneMgr.getScene();
if (!scene) return;
// 创建 Light 节点
const lightNode = scene.createNode('MainLight');
const light = lightNode.createComponent(scene.NodeComponentType.LIGHT) as scene.Light;
// 设置灯光类型和强度
light.lightType = scene.LightType.DIRECTIONAL;
light.intensity = 1.0;
// 灯光位置和朝向
lightNode.position = { x: 3, y: 5, z: 3 };
lightNode.rotation = { x: -45, y: 0, z: 0 };
// 关键:Light 也要加到节点树
scene.getRoot().addChild(lightNode);
console.info('LightManager: main directional light added');
}
}
到这里,第一版 SpaceRoom 的所有基础组件就齐了:
- Scene 创建并绑定到 SceneView
- Camera 创建、定位、朝向原点,加入节点树
- 地面和测试立方体加入节点树
- 主光源加入节点树
把这些串到 ArkUI 页面里:
import { scene } from '@kit.ArkGraphics3DKit';
import { SceneManager } from '../scene/SceneManager';
import { CameraController } from '../camera/CameraController';
import { ModelManager } from '../model/ModelManager';
import { LightManager } from '../light/LightManager';
@Entry
@Component
struct SpaceRoomPage {
private sceneMgr: SceneManager = new SceneManager();
private cameraCtrl: CameraController = new CameraController(this.sceneMgr);
private modelMgr: ModelManager = new ModelManager(this.sceneMgr);
private lightMgr: LightManager = new LightManager(this.sceneMgr);
build() {
Stack() {
// ArkUI 提供的 3D 渲染组件
scene.SceneView({ scene: undefined })
.width('100%')
.height('100%')
.onAreaChange((_, area) => {
// SceneView 尺寸变化时通知场景
})
.onAppear(() => {
// 页面出现时初始化 3D 场景
this.initScene();
})
}
.width('100%')
.height('100%')
}
private initScene(): void {
// 1. 先创建 Camera
this.cameraCtrl.setupCamera();
// 2. 加光源
this.lightMgr.setupMainLight();
// 3. 加地面和测试模型
this.modelMgr.createFloor();
this.modelMgr.createTestCube();
}
}
五、排查黑屏:几个最常见的原因
第一版跑起来以后,如果还是看不到东西,按这个顺序排查:
| 检查项 | 怎么验证 | 常见错误 |
|---|---|---|
| Camera 是否在节点树 | 打印 scene.getRoot().getChildren() | 只创建 Camera 对象,没 addChild |
| Camera 朝向是否正确 | 修改 lookAt 坐标试一下 | 相机对着 z 轴正方向,模型在负方向 |
| 模型是否在视野内 | 把模型移到 (0, 0, 0) 试一下 | 模型在相机后面 |
| 模型尺寸是否合理 | Scale 调到 2 试试 | 太小看不见 |
| 有没有光照 | Light intensity 调到 2.0 | 全黑因为没有光源 |
| 地面有没有反转 | 把地面旋转 180 度 | 平面法线朝下,背面剔除看不到 |
我第一次踩的坑是最后一个:地面平面默认法线朝上没问题,但我把立方体放在 y=0.5,Camera 在 y=1.6,结果看到的是立方体的底部和地面的背面,一片黑。后来把 Camera 的 y 坐标调高、同时确认 Light 方向正确才看到。
这些问题都不是 API 用错了,而是 3D 空间里几个对象的相对位置关系没有理清楚。这也是为什么我在文章开头列了那个表——把每个角色的职责和依赖拆开,比直接看教程代码更容易定位问题。


总结
第一篇不追求效果好看,核心是把 ArkGraphics 3D 的基础角色关系理清楚:
- Scene 是根容器,所有 Node 都要挂到节点树上
- Camera 也是一个 Node,必须加进 Scene 才能渲染
- Light 同样是 Node,决定场景里的物体能不能被照亮
- Geometry + Material 定义物体长什么样、什么颜色
- 所有对象创建成功 ≠ 能被看见,还要看相对位置和朝向
SpaceRoom 的工程骨架已经搭好,后面几篇会在这个基础上加真正的家具模型、坐标变换、材质灯光、相机交互和性能优化。下一篇专门解决 3D 开发最容易写乱的问题:世界坐标和局部坐标为什么总是对不上。
更多推荐




所有评论(0)