2

我目前正在制作一个古怪的复古飞行模拟游戏,我的 3d 项目遇到了一些问题,因为我找不到任何关于该主题的可靠通用文档。

在给出以下信息的情况下,如何将我的游戏世界中的简单位置矢量转换为屏幕上的二维矢量:

摄像头定位 摄像头方向(见下文) 视野 屏幕高度和宽度(以及纵横比)

我也在寻找一种存储方向的方法,我已经编写了一个基本的向量库,但我不确定如何存储旋转以供相机(和投影代码)使用以及旋转的实际处理游戏中的对象。我目前正在考虑使用四元数,但是否可以(并且很容易)使用四元数而不是矩阵进行投影转换?

代码中的实现四元数有什么好的来源吗?我必须为复数编写一个单独的库吗?

感谢您的时间和任何帮助:)

4

1 回答 1

8

注意:长答案!

我在 Love2D 中做过一个类似的项目,它运行得非常快,所以我看不出在 Lua 中自己做数学而不是使用 OpenGL(无论如何都没有公开)的问题。

与评论相反,您不应该气馁。3D 方向和透视背后的数学原理实际上非常简单,只要您对它有所了解。

对于方向,四元数可能是矫枉过正。我发现要使用旋转进行 3D 投影,只需要Vec2Vec3Camera类。虽然在数学上存在一些细微的差异,但实际上,向量的向量构成了非常合适的变换矩阵,而变换矩阵则构成了非常合适的方向。作为向量向量的矩阵的好处是您只需要编写一个类来处理两者。

要投影一个向量v,请考虑相机的 3 个参数:

  • loc, aVec3为相机的位置
  • trans, a Mat3by3(也称为 a Vec3of Vec3's) 表示相机方向 的倒数
    • (免责声明:使用矩阵进行相机定位在技术上被认为是有害的,因为小的舍入误差会累积,但在实际使用中没问题)
  • zoom,用于确定透视的比例因子。A z(相对于相机)zoom相当于在 2D 中;也就是说,从角度看没有缩放。

投影是这样工作的:

function Camera:project(v)
    local relv -- v positioned relative to the camera, both in orientation and location
    relv = self.trans * (v - self.loc) -- here '*' is vector dot product
    if relv.z > 0 then
        -- v is in front of the camera
        local w -- perspective scaling factor
        w = self.zoom / relv.z
        local projv -- projected vector
        projv = Vec2(relv.x * w, relv.y * w)
        return projv
    else
        -- v is behind the camera
        return nil
    end
end

这假设 Vec2(0, 0) 对应于窗口的中心,而不是角落。设置它是一个简单的翻译。

trans应该从单位矩阵开始:Vec3(Vec3(1, 0, 0), Vec3(0, 1, 0), Vec3(0, 0, 1))并以增量方式计算,每次改变方向时进行小的调整。

我觉得你已经知道矩阵的基础知识,但如果你不知道,那么这个想法是这样的:矩阵是向量的向量,至少在这种情况下,它可以被认为是一个坐标系。每个向量可以被认为是坐标系的一个轴。通过更改矩阵的元素(它们是向量并且被认为是矩阵的列),您可以更改该坐标系中坐标的含义。在正常使用中,向量的第一个分量表示向右移动,第二个分量表示向上,第三个分量表示向前。但是,使用矩阵,您可以使每个组件指向任意方向。点积的定义是

function Vec3.dot(a, b) return a.x * b.x + a.y + b.y + a.z * b.z end

对于矩阵

Vec3(axis1, axis2, axis3)

给定点积的定义,用向量点缀的矩阵v将产生

axis1 * v.x + axis2 * v.y + axis3 * v.z

这意味着 的第一个元素v表示axis1要移动多少个,第二个元素表示axis2要移动多少个,第三个元素表示要移动多少个axis3,最终结果是v,如果它被表达在标准坐标而不是矩阵的坐标中。当我们将矩阵与向量相乘时,我们正在改变向量分量的含义。从本质上讲,它是一个陈述的数学表达,比如“任何在右边的东西现在都不那么靠右了,更靠前了”或任何类似的东西。一句话,矩阵转换了一个空间。

回到手头的任务,要theta使用矩阵以角度表示“俯仰”(即围绕 x 轴)的旋转,您可以编写:

function pitchrotation(theta)
    return Vec3(
        -- axis 1
        -- rotated x axis
        -- we're rotating *around* the x axis, so it stays the same
        Vec3(
            1,
            0,
            0
        ),
        -- axis 2
        -- rotated y axis
        Vec3(
            0,
            math.cos(theta),
            math.sin(theta)
        ),
        -- axis 3
        -- rotated z axis
        Vec3(
            0,
            -math.sin(theta),
            math.cos(theta)
        )
    )
end

对于“偏航”(围绕 y 轴):

function yawrotation(theta)
    return Vec3(
        -- axis 1
        -- rotated x axis
        Vec3(
            math.cos(theta),
            0,
            math.sin(theta)
        ),
        -- axis 2
        -- rotated y axis
        -- we're rotating *around* the y axis, so it stays the same
        Vec3(
            0,
            1,
            0
        ),
        -- axis 3
        -- rotated z axis
        Vec3(
            -math.sin(theta),
            0,
            math.cos(theta)
        )
    )
end

最后是“滚动”(围绕 z 轴),这在飞行模拟中特别有用:

function rollrotation(theta)
    return Vec3(
        -- axis 1
        -- rotated x axis
        Vec3(
            math.cos(theta),
            math.sin(theta),
            0
        ),
        -- axis 2
        -- rotated y axis
        Vec3(
            -math.sin(theta),
            math.cos(theta),
            0
        ),
        -- axis 3
        -- rotated z axis
        -- we're rotating *around* the z axis, so it stays the same
        Vec3(
            0,
            0,
            1
        )
    )
end

如果您在脑海中想象这对 x、y 和 z 轴的影响,那么所有这些余弦和正弦以及符号翻转可能会开始变得有意义。他们都在那里是有原因的。

最后,我们到达了难题的最后一步,即应用这些旋转。矩阵的一个很好的特点是它很容易复合。您可以非常轻松地转换转换 - 您只需转换每个轴!A通过矩阵变换现有矩阵B

function combinematrices(a, b)
    return Vec3(b * a.x, b * a.y, b * a.z) -- x y and z are the first second and third axes
end

这意味着如果你想对你的相机应用一个改变,你可以简单地使用这个矩阵组合机制来旋转每一帧的方向。您的相机类的这些函数将提供一种简单的方法来进行更改:

function Camera:rotateyaw(theta)
    self.trans = combinematrices(self.trans, yawrotation(-theta))
end

我们使用负 theta 是因为我们希望 trans与相机的方向相反,用于投影。您可以使用俯仰和滚动来实现类似的功能。

有了所有这些构建块,您就应该准备好在 Lua 中编写 3D 图形代码了。您将要尝试使用zoom- 我通常使用500,但这确实取决于应用程序。

缺少的一件,如果没有 OpenGL 确实无法完成,那就是深度测试。如果您要绘制线框点以外的任何东西,那么没有真正的好方法可以确保所有内容都按正确的顺序绘制。您可以进行排序,但这效率低下,并且它不能处理一些必须逐像素进行排序的极端情况,而这正是 OpenGL 所做的。

快乐编码!希望这有帮助!

于 2013-01-02T11:57:36.530 回答