跳到主要内容

不使用实体进行渲染

无需为每个对象创建 Entity 或渲染组件,也能渲染几何体。使用网格、材质和 GraphNode 创建 MeshInstance,再将其加入图层。它会参与图层的常规渲染,包括光照、阴影、排序和剔除。

这种方式适用于程序化环境、自定义场景管理或大量重复对象。应用自行控制通常由组件管理的变换、图层归属和资源生命周期。网格实例设置参见网格实例,代码构建网格参见创建网格。

实时示例​

示例用手动创建的 StandardMaterial 渲染地面,并用从 GLB 加载、经硬件实例化的树木渲染森林。只有相机和灯光使用实体。通过 Show Trees(显示树木)移除和重新添加同一网格实例,或通过 Tree Count(树木数量)改变使用现有实例缓冲区的树木数量。

不使用实体进行渲染

创建网格实例​

以下代码假定应用已初始化并具有相机和光照。World 是应用默认图层之一,相机和灯光的 layers 列表默认包含它。

import {
Color,
GraphNode,
LAYERID_WORLD,
Mesh,
MeshInstance,
PlaneGeometry,
StandardMaterial,
Vec2,
VertexBuffer,
VertexFormat
} from 'playcanvas';

const layer = app.scene.layers.getLayerById(LAYERID_WORLD);

const material = new StandardMaterial();
material.diffuse = new Color(0.53, 0.55, 0.43);
material.gloss = 0.15;
material.update();

const mesh = Mesh.fromGeometry(app.graphicsDevice, new PlaneGeometry({
halfExtents: new Vec2(20, 20)
}));

const node = new GraphNode('Ground');
const ground = new MeshInstance(mesh, material, node);
const meshInstances = [ground];
layer.addMeshInstances(meshInstances);

每个网格实例都需要 GraphNode。渲染组件将所在实体作为节点;没有组件时,需要自行创建。节点提供世界变换,也提供便于调试的名称。独立 GraphNode 无需挂到 app.root,可通过常规 GraphNode 变换方法移动、旋转或缩放。

保持持久的图层归属​

Layer.addMeshInstances() 将实例持续注册,直到显式移除。对象加入场景时调用一次,随后按需更新节点或材质,无需每帧重新提交。

// Hide the ground while preserving its resources for reuse.
layer.removeMeshInstances(meshInstances);

// Show it again later, using the same mesh instance.
layer.addMeshInstances(meshInstances);

图层还控制这些对象相对于场景其他内容的渲染时机。场景按称为图层组合的顺序保存图层,可通过 app.scene.layers 访问。若使用自定义图层替代 World,应将其添加到 app.scene.layers 和相机的 layers 列表,灯光的 layers 也必须包含它。排序和相机配置参见图层。

要投射阴影,应在添加到图层之前设置 meshInstance.castShadows = true;注册时也会将符合条件的实例加入图层阴影投射者列表。移除实例时会同时从两个列表移除。StandardMaterial 通过常规渲染器接收光照和阴影,与渲染组件上的行为一致。

实例化加载的网格​

加载的容器资源直接暴露渲染资源和材质,无需调用 instantiateRenderEntity()。示例的树木 GLB 包含一个静态网格和一个材质:

// treeAsset is an already loaded container asset.
const treeMesh = treeAsset.resource.renders[0].resource.meshes[0];
const treeMaterial = treeAsset.resource.materials[0].resource;
const forest = new MeshInstance(treeMesh, treeMaterial, new GraphNode('Forest'));
forest.castShadows = true;

这种直接查找仅适用于示例资源。一般 GLB 可能包含多个图元和材质、节点变换、蒙皮和变形目标。绕过实体实例化时,需要自行配置对应内容。示例先将树木原始网格居中并贴地,再放置副本。

对于硬件实例化,创建变换缓冲区并附加到网格实例。这里 matrices 是包含 treeCount 个世界空间 Mat4 变换的 Float32Array,forestBounds 包围所有变换后的树木:

const instanceBuffer = new VertexBuffer(
app.graphicsDevice,
VertexFormat.getDefaultInstancingFormat(app.graphicsDevice),
treeCount,
{ data: matrices }
);

forest.setInstancing(instanceBuffer, true);
forest.setCustomAabb(forestBounds);
const forestInstances = [forest];
layer.addMeshInstances(forestInstances);

// Draw only the first half of the buffer, without reallocating it.
forest.instancingCount = Math.floor(treeCount / 2);

默认实例化格式使用世界空间矩阵,因此森林的 GraphNode 保持单位变换。向 setInstancing() 传入 true,启用整个群组的视锥体剔除。应提供足够保守、覆盖每个副本的包围盒;不会逐棵剔除树木。变换变化时需更新边界。减少数量后保留完整森林边界是安全的,但精度较低。

实例化让全部树木复用同一网格和材质。图层移除和重新添加也保留网格实例及其缓冲区,因此两种操作都无需重建森林。

释放自有资源​

从图层移除实例不会销毁它。不再需要时,应从其所属的所有图层移除,再销毁:

layer.removeMeshInstances(meshInstances);
ground.destroy();
material.destroy();

layer.removeMeshInstances(forestInstances);
forest.destroy();
instanceBuffer.destroy();

MeshInstance.destroy() 释放网格引用,无剩余引用时销毁网格。它不销毁材质或你提供的实例缓冲区。自行管理的材质和缓冲区应在无其他对象使用时销毁。GLB 资源交给资源注册表管理,仅在所有使用者都结束后卸载资源。完整示例在应用销毁时执行清理。