Skip to content

场景实例化与变换

同一个 glTF 模型可以在场景里摆多份。Handle<WorldAsset> 是一张提货单,克隆它不复制资产本体——多份实例共享同一份网格和材质数据,只各自维护自己的变换。

同一模型摆多份

rust
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn((
        Camera3d::default(),
        Transform::from_xyz(2.0, 1.5, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
    ));

    commands.spawn((
        DirectionalLight {
            contact_shadows_enabled: true,
            ..default()
        },
        Transform::from_xyz(4.0, 25.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
    ));

    let helmet =
        asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));

    for i in 0..3 {
        let x = (i as f32 - 1.0) * 1.5;
        commands.spawn((
            WorldAssetRoot(helmet.clone()),
            Transform::from_xyz(x, 0.0, 0.0),
        ));
    }
}

Listing 23-4:同一个头盔摆三份——克隆 Handle 不复制资产(examples/listing-23-04.rs)

console
cargo run -p ch23-gltf --example listing-23-04

三只头盔一字排开。它们的网格数据在 GPU 上只有一份,渲染时用不同的变换矩阵画三次——这就是实例化的开销优势。

给实例加变换

WorldAssetRoot 实体上的 Transform 会影响整个 glTF 场景的所有子实体。缩放、旋转、位移,和第 12 章的手艺完全一样:

rust
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn((
        Camera3d::default(),
        Transform::from_xyz(3.0, 2.0, 4.0).looking_at(Vec3::ZERO, Vec3::Y),
    ));

    commands.spawn((
        DirectionalLight {
            contact_shadows_enabled: true,
            ..default()
        },
        Transform::from_xyz(4.0, 25.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
    ));

    let helmet =
        asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));

    commands.spawn((
        WorldAssetRoot(helmet.clone()),
        Transform::from_xyz(-1.5, 0.0, 0.0).with_scale(Vec3::splat(0.8)),
    ));

    commands.spawn((
        WorldAssetRoot(helmet.clone()),
        Transform::from_xyz(0.0, 0.0, 0.0),
    ));

    commands.spawn((
        WorldAssetRoot(helmet),
        Transform::from_xyz(1.5, 0.0, 0.0)
            .with_scale(Vec3::splat(1.2))
            .with_rotation(Quat::from_rotation_y(0.5)),
    ));
}

Listing 23-5:三只头盔——缩小、原大、放大并旋转(examples/listing-23-05.rs)

console
cargo run -p ch23-gltf --example listing-23-05

注意 Transform 是挂在 WorldAssetRoot 实体上的,glTF 场景展开后的子实体各自还有自己的局部变换(来自 glTF 节点)。最终的渲染变换是层级传播的结果——第 12 章的 GlobalTransform 规矩原样适用。

为什么 clone() 是安全的

Handle::clone() 只增加引用计数,不复制资产数据。这和第 14 章讲的 Handle 机制完全一致:同一个 Handle<WorldAsset> 的多个克隆指向 Assets<Scene> 里的同一份数据。销毁一个实例(despawn)只减少引用计数,资产在最后一个 Handle 销毁前不会被回收。