2

我正在使用 Rust Vulkano Vulkan API 绑定制作类似 Minecraft 的游戏来渲染游戏。我有一个玩家围绕 Y 轴或 X 轴旋转,专门使用 FPS 视图矩阵实现。视图矩阵的预期输出是一个 4x4 浮点齐次矩阵 ( [[f32; 4]; 4])。

我尝试使用cgmath库和nalgebra库作为 FPS 视图矩阵的实现,尽管我决定手动实现它仅nalgebra用于矩阵运算。

# cargo.toml

vulkano = "0.18.0"
vulkano-shaders = "0.13"
vulkano-win = "0.18.0"
winit = "0.22.0"

nalgebra = "0.21.1"
nalgebra-glm = "0.7.0"
extern crate nalgebra as na;

use na::{
    Point3,
    Rotation3,
    Matrix4,
};

use crate::Dimension;

type Matrix3D = [[f32; 4]; 4];

// generates the mvp matrix for meshes and other pipelines
pub fn gen_mvp(&self, dimensions: Dimension<u32>) -> (Matrix3D, Matrix3D, Matrix3D) {
    let proj = Perspective3::new(dimensions.aspect() as f32, 3.14 / 2.0, 0.1, 1000.0);

    let eye = &self.position;
    let target: Vector3<f32> = Vector3::new(1.0, 0.0, 1.0);
    let up = Vector3::new(0.0, -1.0, 0.0);
    println!("ROT MAT: {:?}", self.rotation.euler_angles());
    println!("POS MAT: {:?}", self.position.coords.data);
    println!("ROT POS: {:?}", self.rotation.transform_vector(&target));

    let d = self.rotation.transform_vector(&target).data;
    println!("TARGET : {:?}", self.rotation.transform_vector(&target));

    let roty = Rotation3::from_euler_angles(self.rotation.euler_angles().0, self.rotation.euler_angles().1, 0.0);
    // let view = Isometry3::look_at_lh(eye, &(&self.position + &roty * &target), &up);
    let view = Self::fpv_view(eye, &self.rotation);
    // let rot = UnitQuaternion::new(Vector3::new(0.0, self.rotation.euler_angles().1, self.rotation.euler_angles().2));
    // let crd = self.position.coords.data;
    // let view_pos = Isometry3::from_parts(Translation3::new(crd[0], crd[1], crd[2]), UnitQuaternion::new(Vector3::new(0.0, 0.0, 0.0)));
    // let view_rot = Isometry3::from_parts(Translation3::new(0.0, 0.0, 1.0), rot);
    // let view = view_rot * view_pos;

    // let model = glm::identity::<f32, >();
    let model = Matrix4::identity();

    // let eye    = Point3::new(0.0, 0.0, 1.0);
    // let target = Point3::new(1.0, 0.0, 0.0);
    // let view   = Isometry3::look_at_rh(&eye, &target, &Vector3::y());
    // let model  = Isometry3::identity();  // the world

    // let proj = perspective (self.fov, dimensions.aspect() as f32, 0.1 , 1000.0);
    // let view = Matrix4::from_angle_x(self.rotation.x) * Matrix4::from_angle_y(self.rotation.y) *
    //     Matrix4::look_at(Point3::new(self.position.x, self.position.y, 1.0+self.position.z), self.position.into(), Vector3::new(0.0, -1.0, 0.0));
    // let world = Matrix4::identity();

    let proj_matrix = proj.as_matrix();
    let view_matrix = view;

    let proj_cooked: &[f32] = proj_matrix.as_slice();
    let view_cooked: &[f32] = view.as_slice();
    let model_cooked: &[f32] = model.as_slice();

    let proj_dt;
    let view_dt;
    let model_dt;

    unsafe {
        assert_eq!(proj_cooked.len(), 16);
        assert_eq!(view_cooked.len(), 16);
        assert_eq!(model_cooked.len(), 16);

        proj_dt = *(proj_cooked.as_ptr() as *const Matrix3D);
        view_dt = *(view_cooked.as_ptr() as *const Matrix3D);
        model_dt = *(model_cooked.as_ptr() as *const Matrix3D);
    }

    (proj_dt, view_dt, model_dt)
}

fn fpv_view(eye: &Point3<f32>, rot: &Rotation3<f32>) -> Matrix4<f32> {
    let (roll, pitch, yaw) = rot.euler_angles();
    let e = &eye.coords.data;

    // -- snip --

    let xrot = Matrix4::new(
        1.0,         0.0,         0.0,         0.0,
        0.0, pitch.cos(),-pitch.sin(),         0.0,
        0.0, pitch.sin(), pitch.cos(),         0.0,
        0.0,         0.0,         0.0,         1.0,
    );

    let yrot = Matrix4::new(
         yaw.cos(),       0.0, yaw.sin(),       0.0,
               0.0,       1.0,       0.0,       0.0,
        -yaw.sin(),       0.0, yaw.cos(),       0.0,
               0.0,       0.0,       0.0,       1.0,
    );

    // also tried to transpose the translation matrix too

    // let translation = Matrix4::new(
    //        1.0,    0.0,    0.0,   0.0,
    //        0.0,    1.0,    0.0,   0.0,
    //        0.0,    0.0,    1.0,   0.0,
    //      -e[0],  -e[1],  -e[2],   1.0,
    // );

    let translation = Matrix4::new(
          1.0,    0.0,    0.0, -e[0],
          0.0,    1.0,    0.0, -e[1],
          0.0,    0.0,    1.0, -e[2],
          0.0,    0.0,    0.0,   1.0,
    );

    // also tried inverting the view matrix

    // (translation * yrot * xrot).try_inverse().unwrap()

    translation * yrot * xrot
}

我也尝试使用传统的look_at(eye, target, up)功能。

结果要么是奇怪的剪裁/翘曲,要么是无法正确旋转。我试图找到一些外部资源,例如 MVP 矩阵教程、问题和论坛,但解决方案总是导致奇怪的渲染。

更新 1

我目前在游戏中构建的环境只是一个大的 64x64x64 块大小的空心块,纹理仅在块边界上,没有启用 alpha 混合的面部剔除。

这是我能得到的最接近 y 轴旋转围绕想象中的大圆路径旋转的地方,它应该只是围绕玩家旋转。

pub fn gen_mvp(&self, dimensions: Dimension<u32>) -> (Matrix3D, Matrix3D, Matrix3D) {
    let proj = Perspective3::new(dimensions.aspect() as f32, 3.14 / 2.0, 0.1, 1000.0);

    let eye = &self.position;
    let target: Vector3<f32> = Vector3::new(0.0, 0.0, 1.0);
    let up = Vector3::new(0.0, -1.0, 0.0);

    let roty = Rotation3::from_euler_angles(self.rotation.euler_angles().0, self.rotation.euler_angles().1, 0.0);
    let view = Isometry3::look_at_lh(eye, &(&self.position + (&roty * &target)), &up);

    let model = Matrix4::identity();

    let proj_matrix = proj.as_matrix();
    let view_matrix = view.to_homogeneous();

    let proj_cooked: &[f32] = proj_matrix.as_slice();
    let view_cooked: &[f32] = view_matrix.as_slice();
    let model_cooked: &[f32] = model.as_slice();

    let proj_dt;
    let view_dt;
    let model_dt;

    unsafe {
        assert_eq!(proj_cooked.len(), 16);
        assert_eq!(view_cooked.len(), 16);
        assert_eq!(model_cooked.len(), 16);

        proj_dt = *(proj_cooked.as_ptr() as *const Matrix3D);
        view_dt = *(view_cooked.as_ptr() as *const Matrix3D);
        model_dt = *(model_cooked.as_ptr() as *const Matrix3D);
    }

    (proj_dt, view_dt, model_dt)
}
4

1 回答 1

1

代码不起作用的主要原因是我误解了玩家转换的两个主要部分。首先,我认为模型矩阵仅用于自定义,独立于世界,转换。但是我发现模型矩阵(与世界 MVP 矩阵一起)也被用来围绕相机旋转世界(它保持静态朝向 -z 方向)。其次,纯数值数学库中使用的大多数旋转都是四元数,这意味着仅更改两个轴会影响第三个轴。误认为四元数,我决定唯一的方法是创建手动纯欧拉角旋转矩阵。

这是生成的代码,如下所示:

// camera.rs

pub struct Camera {
    pub position: Point3<f32>,
    pub rotation: Rotation<f32>,  // manual pure euler angle rotation
    rot_speed: f32,
    trans_speed: f32,
    // fovy: Rotation<f32, Dim>,
}

// -- snip --

// generates the mvp matrix for meshes and other pipelines
pub fn gen_mvp(&self, dimensions: Dimension<u32>) -> (Matrix3D, Matrix3D, Matrix3D) {
    let proj = Perspective3::new(dimensions.aspect() as f32, 3.14 / 2.0, 0.1, 1000.0);

    let eye = Point3::new(0.0, 0.0, 0.0);
    let target = Point3::new(0.0, 0.0, -1.0);
    let up = Vector3::new(0.0, -1.0, 0.0);
    let view = Isometry3::look_at_lh(&eye, &target, &up);

    let crd = &self.position.coords.data;
    let model = Translation3::new(crd[0], crd[1], crd[2]);
    let model = model.to_homogeneous() * self.rotation.matrix();

    let proj_matrix = proj.as_matrix();
    let view_matrix = view.to_homogeneous();
    let model_matrix = model.try_inverse().unwrap();

    let proj_cooked: &[f32] = proj_matrix.as_slice();
    let view_cooked: &[f32] = view_matrix.as_slice();
    let model_cooked: &[f32] = model_matrix.as_slice();

    let proj_dt;
    let view_dt;
    let model_dt;

    unsafe {
        assert_eq!(proj_cooked.len(), 16);
        assert_eq!(view_cooked.len(), 16);
        assert_eq!(model_cooked.len(), 16);

        proj_dt = *(proj_cooked.as_ptr() as *const Matrix3D);
        view_dt = *(view_cooked.as_ptr() as *const Matrix3D);
        model_dt = *(model_cooked.as_ptr() as *const Matrix3D);
    }

    (proj_dt, view_dt, model_dt)
}

// datatype.rs

// -- snip --

#[derive(Copy, Clone, Debug)]
pub struct Rotation<T: Copy + Debug + PartialEq + Float> {
    pub x: T,
    pub y: T,
    pub z: T,
}

impl Rotation<f32> {
    pub fn new(x: f32, y: f32, z: f32) -> Self {
        Self {
            x,
            y,
            z,
        }
    }

    pub fn matrix(&self) -> Matrix4<f32> {
        let sx = self.x.sin();
        let cx = self.x.cos();
        let sy = self.y.sin();
        let cy = self.y.cos();
        let sz = self.z.sin();
        let cz = self.z.cos();

        Matrix4::new( // z
             cz, -sz, 0.0, 0.0,
             sz,  cz, 0.0, 0.0,
            0.0, 0.0, 1.0, 0.0,
            0.0, 0.0, 0.0, 1.0,
        ) * Matrix4::new( // y
             cy, 0.0,  sy, 0.0,
            0.0, 1.0, 0.0, 0.0,
            -sy, 0.0,  cy, 0.0,
            0.0, 0.0, 0.0, 1.0,
        ) * Matrix4::new( // x
            1.0, 0.0, 0.0, 0.0,
            0.0,  cx, -sx, 0.0,
            0.0,  sx,  cx, 0.0,
            0.0, 0.0, 0.0, 1.0,
        )
    }
}

我要感谢大家澄清模型矩阵和代数Rotation3(reddit)。我希望没有人会掉进这个令人困惑的坑里。

于 2020-06-24T16:20:08.107 回答