修改 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} />;
};
- 演示
- 代码
import { useModel } from '@playcanvas/react/hooks';
import { Gltf, Entity } from '@playcanvas/react';
export const GltfBasic = () => {
const { asset, error } = useModel('/assets/statue.glb');
if (error) {
console.error('Error loading model:', error);
return null;
}
if (!asset) return null;
return <Entity name='asset'>
<Gltf asset={asset} key={asset.id} />
</Entity>;
};
修改模型
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 移除对应组件。
- 演示
- 代码
import { useModel } from '@playcanvas/react/hooks';
import { Gltf, Modify } from '@playcanvas/react';
export const GltfRemoveLights = () => {
const { asset, error } = useModel('/assets/statue.glb');
if (error) {
console.error('Error loading model:', error);
return null;
}
if (!asset) return null;
return (
<Gltf asset={asset} key={asset.id}>
<Modify.Node path="**[light]">
<Modify.Light remove />
</Modify.Node>
</Gltf>
);
};
修改灯光
<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>
多规则修改同节点同组件时,更具体的路径优先,可先定义通用规则,再指定例外。