跳到主要内容

修改 GLB 模型

模型通常作为独立资源提供,看起来像黑盒,即使小改动也可能需要回三维工具修改、导出,并在每次更新后重复。

实际中常需代码检查或调整,例如移除自带灯光、用 React 状态控制车灯自发光,或给全部网格添加物理。

Gltf 组件​

Gltf 同时负责显示和定制,类似 Render,但暴露内部层级。

import { useModel } from '@playcanvas/react/hooks';
import { Gltf } from '@playcanvas/react';

const GltfModel = () => {
const { asset } = useModel('model.glb');
return <Gltf asset={asset} key={asset.id} />;
};

修改模型​

Modify 选择内部节点,对匹配项添加、移除或调整组件,也可插入实体。

例如为模型手部添加灯光:

<Gltf asset={asset} key={asset.id}>
{/* Select the Hand child under Arm under Body */}
<Modify.Node path="Body.Arm.Hand">
<Light color="red" intensity={2} />
</Modify.Node>
</Gltf>

Modify.Node 的 path 指定目标,此处选 Body 下 Arm 下的 Hand。

查找正确节点​

path 支持类似 glob 的模式,可一次选择多个。例如 Body 下全部 Hand:

{/* Load the model */}
<Gltf asset={asset} key={asset.id}>
{/* Select all hand nodes under the Body node */}
<Modify.Node path="Body.**.Hand">
{/* Add a light to each hand */}
<Light color="red" intensity={2} />
</Modify.Node>
</Gltf>

也支持按组件类型,例如 [light]:

{/* Load the model */}
<Gltf asset={asset} key={asset.id}>
{/* Select all nodes with a Light component under the Body node */}
<Modify.Node path="Body.**[light]">
{/* Adjust each matching light */}
<Light color="red" intensity={2} />
</Modify.Node>
</Gltf>

常用模式如下:

  • 精确路径:"Body.Arm.Hand",指定节点。
  • 单级通配:"Body.*",Body 直接子项。
  • 多级通配:"Body.**",Body 全部后代。
  • 全部节点:"**"。
  • 组件筛选:"**[light]",全部带 Light 的节点。
  • 组合:"Head.*[light]",Head 下带 Light 的直接子项。

也可使用谓词函数:

<Modify.Node path={(entity) => entity.name.includes('Weapon')}>
<Modify.Render castShadows />
</Modify.Node>

添加组件与实体​

可为目标添加动画、物理或新的层级内容。

添加物理​

为指定网格添加碰撞和刚体:

import { Gltf, Modify } from '@playcanvas/react';
import { Collision, Rigidbody } from '@playcanvas/react/components';

<Gltf asset={asset} key={asset.id}>
<Modify.Node path="Body.Mesh">
<Collision type="box" />
<Rigidbody type="dynamic" mass={10} />
</Modify.Node>
</Gltf>

此例为 Body.Mesh 添加物理,使其参与模拟。

添加动画​

找到骨架根,添加 Anim:

import { Gltf, Modify } from '@playcanvas/react';
import { Anim } from '@playcanvas/react/components';

<Gltf asset={asset} key={asset.id}>
<Modify.Node path="Root">
<Anim asset={asset} clip="Walk" loop />
</Modify.Node>
</Gltf>

Root 选择模型根,但应根据实际结构调整,也可能叫 Scene 或其他骨架根名。

添加实体​

也可在模型节点下创建全新实体:

import { Gltf, Modify } from '@playcanvas/react';
import { Entity, Light } from '@playcanvas/react/components';

<Gltf asset={asset} key={asset.id}>
<Modify.Node path="Head">
<Entity name="HelmetLight" position={[0, 0.5, 0]}>
<Light type="omni" color="yellow" intensity={3} />
</Entity>
</Modify.Node>
</Gltf>

修改已有组件​

直接修改属性会与已有配置合并,无需移除组件。

移除组件​

模型自带不需要的灯光等,可选择节点并使用 remove。

import { Gltf, Modify } from '@playcanvas/react';

<Gltf asset={asset} key={asset.id}>
<Modify.Node path="**[light]">
<Modify.Light remove />
</Modify.Node>
</Gltf>

path="**[light]" 找到所有灯光节点,remove 移除对应组件。

修改灯光​

<Gltf asset={asset} key={asset.id}>
<Modify.Node path="Headlight">
<Modify.Light color="red" intensity={2} />
</Modify.Node>
</Gltf>

此例选择 Headlight,修改颜色和强度,其他属性保留。

修改渲染属性​

<Gltf asset={asset} key={asset.id}>
<Modify.Node path="Body[render]">
<Modify.Render castShadows receiveShadows />
</Modify.Node>
</Gltf>

[render] 选择带 Render 的节点,此例为车身开启投射和接收阴影。

函数更新​

可根据当前值计算新值:

<Gltf asset={asset} key={asset.id}>
<Modify.Node path="**[light]">
<Modify.Light intensity={(val) => (val || 1) * 2} />
</Modify.Node>
</Gltf>

此例将全部灯光强度翻倍。

通过 useEntity 查找实体​

useEntity 可在当前父上下文中查找模型实体,适合在 Modify.Node 子组件内访问同一子树其他节点。

import { Gltf, Modify, useEntity } from '@playcanvas/react';
import { Entity, Light } from '@playcanvas/react/components';

function HandGlow() {
// Find the 'Hand' entity relative to the current parent
const handEntity = useEntity('Hand');

if (!handEntity) return null;

// Add a glow effect to the hand
return (
<Entity name="HandGlow" position={[0, 0.1, 0]}>
<Light type="omni" color="cyan" intensity={2} />
</Entity>
);
}

<Gltf asset={asset} key={asset.id}>
<Modify.Node path="Arm">
<HandGlow />
</Modify.Node>
</Gltf>

此例 useEntity(Hand) 相对 Arm 查找,找到返回实体,否则 null。

路径模式​

useEntity 支持与 Modify.Node 相同的模式:

function FindMultipleLights() {
// Find all direct children with lights
const lights = useEntity('*[light]');

if (!lights || !Array.isArray(lights)) return null;

return (
<>
{lights.map((light, index) => (
<Entity key={index} name={`LightMarker_${index}`}>
{/* Add markers to each light */}
</Entity>
))}
</>
);
}

通配模式返回匹配数组,精确路径返回单实体。

谓词函数​

也可按自定义逻辑查找:

function FindWeapons() {
// Find all entities whose name includes "Weapon"
const weapons = useEntity((entity, metadata) =>
entity.name.includes('Weapon')
);

if (!weapons) return null;

const weaponArray = Array.isArray(weapons) ? weapons : [weapons];

return (
<>
{weaponArray.map((weapon, index) => (
<Entity key={index} name={`WeaponEffect_${index}`}>
<Light type="omni" color="red" intensity={1} />
</Entity>
))}
</>
);
}

函数接收实体与元数据,可按组件、名称或自定义属性筛选。

注意,仅可在 Gltf 或 Modify.Node 的子组件中使用,相对当前父上下文搜索。

多条修改规则​

同一 GLB 可应用多个独立规则:

<Gltf asset={asset} key={asset.id}>
{/* Remove all original lights */}
<Modify.Node path="**[light]">
<Modify.Light remove />
</Modify.Node>

{/* Add shadow casting to all meshes */}
<Modify.Node path="**[render]">
<Modify.Render castShadows receiveShadows />
</Modify.Node>

{/* Add a new light to the head */}
<Modify.Node path="Head">
<Entity name="HeadLight">
<Light type="omni" color="cyan" intensity={2} />
</Entity>
</Modify.Node>
</Gltf>

多规则修改同节点同组件时,更具体的路径优先,可先定义通用规则,再指定例外。