Skip to content

Camera3d:透视的世界

正交投影“远近一样大”的脾气,在 2D 里是美德——图层本来就不该有近大远小。可一进三维就成了灾难:没有纵深,走廊是一面墙,深渊是一块地板。三维世界的取景器需要另一条换算规则:透视投影(perspective projection)——离相机越近的东西占画面越大,平行的街道在远方收束于一点,跟人眼和真实摄影机一个原理。

Bevy 里这条规则就是 Projection 的另一个变体 Perspective(PerspectiveProjection),而 Camera3d 默认带的正是它。PerspectiveProjection 四个字段:

字段含义默认
fov纵向视场角(弧度):取景锥上下张开的角度π/4(45°)
aspect_ratio画面宽高比,引擎随视口自动维护1.0
near近裁剪面:比这更近的不画0.1
far远裁剪面:比这更远的不画1000.0

跟正交的取景“框”不同,透视的取景体是一个锥台(frustum,视锥):从相机出发张开 fov 角度、被 nearfar 两刀截断的四棱台。fov 就是变焦环——调大是广角(视野宽、边缘畸变),调小是长焦(视野窄、画面压缩);near = 0.1 也解释了和 2D 的一个分别:三维相机身后和贴脸的东西不入镜,这正是第 4 节 default_3d()near 设为 0 而 2D 版本设为 −1000 的道理。

示意图:左侧正交投影的取景体是一个长方体,投影线互相平行;右侧透视投影的取景体是从相机眼点张开 fov 角、被 near 和 far 截断的锥台

Figure 13-7:两种取景体(侧视)——正交的长方体与透视的锥台

特效棚验机

百闻不如一见,而“看”恰好是本章练熟了的本事:同一条走廊,两台机器架在同一个点、朝同一个方向,左监正交、右监透视,分屏对比。布景用了几位第 21 章才正式登场的演员——Mesh3d(三维网格)、MeshMaterial3d<StandardMaterial>(材质)和 DirectionalLight(平行光),今天先借来当模特,看个轮廓就好:

rust
fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    // 走廊:地面一条,灯笼柱两列纵深排开
    // Mesh3d 与 StandardMaterial 是第 21 章的主角,今天先借来当模特
    commands.spawn((
        Mesh3d(meshes.add(Plane3d::default().mesh().size(16.0, 64.0))),
        MeshMaterial3d(materials.add(Color::srgb(0.22, 0.26, 0.22))),
    ));
    for i in 0..8 {
        for x in [-3.0, 3.0] {
            commands.spawn((
                Mesh3d(meshes.add(Cuboid::new(1.0, 3.0, 1.0))),
                MeshMaterial3d(materials.add(Color::srgb(0.95, 0.75, 0.25))),
                Transform::from_xyz(x, 1.5, -4.0 * i as f32),
            ));
        }
    }
    // 棚顶大灯
    commands.spawn((
        DirectionalLight::default(),
        Transform::from_xyz(8.0, 16.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
    ));

    // 两台机位架在同一点、朝同一方向,只差投影
    let stand = Transform::from_xyz(0.0, 6.0, 14.0).looking_at(Vec3::new(0.0, 1.0, -8.0), Vec3::Y);

    // 左监:正交投影——平行线永远平行,远近一样大
    commands.spawn((
        Camera3d::default(),
        OrthoLens,
        stand,
        Camera {
            order: 0,
            ..default()
        },
        Projection::Orthographic(OrthographicProjection {
            scaling_mode: ScalingMode::FixedVertical {
                viewport_height: 14.0,
            },
            ..OrthographicProjection::default_3d()
        }),
    ));
    // 右监:透视投影——Camera3d 的默认,纵向视角 45°
    commands.spawn((
        Camera3d::default(),
        PerspectiveLens,
        stand,
        Camera {
            order: 1,
            ..default()
        },
    ));

    println!("老雷:特效棚验机——左监平行机位,右监透视机位,自己看哪边有纵深。");
}

Listing 13-11:一条走廊、两种投影,分屏对照(examples/listing-13-11.rs)

几处看点。机位 Transform 用了 looking_at(目标点, Vec3::Y)——给定立足点和盯住的目标,旋转自动算好,第 12 章手搓 Quat::from_rotation_arc 的活它一行包办,三维运镜的标配。正交那台用 default_3d() 打底(第 4 节的功课),FixedVertical { viewport_height: 14.0 } 锁定纵向看 14 个世界单位;透视那台什么都没配——Camera3d 默认就是 45° 透视。分屏系统与第 5 节逐字相同,手艺通用。

console
cargo run -p ch13-cameras --example listing-13-11
text
老雷:特效棚验机——左监平行机位,右监透视机位,自己看哪边有纵深。

两边画面判若两个世界:

  • 右监(透视):两列灯笼柱近大远小,向画面深处收束,地面是一块收窄的梯形——一条有纵深的走廊;
  • 左监(正交):八根柱子前后完全叠成两摞,看不出哪根近哪根远,两列柱子的间距从近到远分毫不变,地面是一块端端正正的矩形。深度被投影整个抹平了。

分屏对比:左半屏正交投影下两列柱子叠成两条笔直的柱带、地面是矩形;右半屏透视投影下同样的柱子近大远小、向远处收束

Figure 13-8:同一条走廊、同一个机位——左监正交抹平深度,右监透视给出纵深

左监不是坏画面——它就是工程制图里的“立面图”。战棋、模拟经营那一路的等距视角游戏,用的正是 3D 场景配正交投影:要的就是“格子永远一样大”的秩序感。哪种投影是对的,取决于你要哪种真实。

Projection 是统一的组件。注意两台 Camera3d 装的是同一个 Projection 枚举——投影流派与相机维度是两个独立的开关:Camera3d 配正交(等距游戏)、Camera2d 的默认正交,都是同一个组件的不同变体。第 3 节学的 area 报点对正交永远好使,而 viewport_to_world_2d 换成透视后会沿视线“打一道射线”出去——那是第 25 章拾取的开场白。

特效棚验收完毕。三维的光影、材质、模型是第四部分的正餐,本章的任务只是把取景器交到你手上——拿到 Camera3d 这张入场券,回头杀青。