简单的3D投影和定向处理?

Fas*_*cia 2 3d lua projection matrix quaternions

我正在研究一个古怪的复古飞行模拟器,我在我的3D项目中遇到了一些问题,因为我找不到关于这个主题的任何可靠的通用文档.

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

摄像机将摄像机方向(见下文)定位为视野,屏幕的高度和宽度(以及宽高比)

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

代码中的实现四元数是否有任何好的来源?我是否必须为复数编写一个单独的库?

谢谢你的时间和任何帮助:)

小智 7

小心:长期回答!

我在Love2D中做了一个类似的项目,它运行速度非常快,所以我没有看到自己在Lua中自己做数学的问题,而不是使用OpenGL(无论如何都没有暴露).

与评论相反,你不应该气馁.一旦你对它有了感觉,3D方向和透视背后的数学实际上非常简单.

对于方向,四元数可能是矫枉过正的.我发现,做3D投影与旋转,只有Vec2,Vec3Camera需要的类.虽然在数学上存在一些细微差别,但在实用性中,矢量矢量可以制作出非常合适的变换矩阵,并且变换矩阵可以制作出非常合适的方向.矩阵是向量的向量有一个好处,你只需要编写一个类来处理它们.

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

  • loc,a Vec3为相机的位置
  • trans中,Mat3by3(也被称为Vec3Vec3"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
Run Code Online (Sandbox Code Playgroud)

这假设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
Run Code Online (Sandbox Code Playgroud)

并且对于"偏航"(围绕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
Run Code Online (Sandbox Code Playgroud)

最后"滚动"(绕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
Run Code Online (Sandbox Code Playgroud)

如果你想象一下头部的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
Run Code Online (Sandbox Code Playgroud)

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

function Camera:rotateyaw(theta)
    self.trans = combinematrices(self.trans, yawrotation(-theta))
end
Run Code Online (Sandbox Code Playgroud)

我们使用负θ,因为我们希望trans 与摄像机的方向相反,用于投影.您可以使用俯仰和滚动来制作类似的功能.

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

如果没有OpenGL,真正无法实现的缺失就是深度测试.如果你正在绘制除线框之外的任何东西,那么确实没有一种好方法可以确保所有内容都以正确的顺序绘制.你可以排序,但这样效率很低,并且它不能处理一些必须逐像素处理的极端情况,这就是OpenGL的作用.

快乐的编码!希望有用!