Metal 投影变换:正交投影

引言

Hi, 大家好,我是一牛。相信认真阅读过我之前的 Metal 系列文章的朋友们,已经打下了扎实的 Metal 基础。这些基础知识将成为我们深入学习更高级内容的坚实基石。因此,如果同学们还不太理解这些基础内容,请再好好的研究一番。从现在起,我们要进入3D 渲染的学习,探索更真实的世界!

今天,我将教会大家如何实现正交投影,将三维世界的物体显示在屏幕上。

模型矩阵

模型矩阵常用于将顶点从模型空间转换到世界坐标。它包含了以下变换

  • 平移

  • 旋转

  • 缩放

    在本例中我们将图片的顶点绕Y轴转动,对应的矩阵是:
    R y ( θ ) = cos ⁡ ( θ ) 0 sin ⁡ ( θ ) 0 0 1 0 0 − sin ⁡ ( θ ) 0 cos ⁡ ( θ ) 0 0 0 0 1 \mathbf{R}_y(\theta) = \begin{bmatrix} \cos(\theta) & 0 & \sin(\theta) & 0 \\ 0 & 1 & 0 & 0 \\ -\sin(\theta) & 0 & \cos(\theta) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} Ry(θ)= cos(θ)0−sin(θ)00100sin(θ)0cos(θ)00001

swift 复制代码
// angle 是弧度
func rotateY(angle: Float) -> simd_float4x4 {
    let cos = cosf(angle)
    let sin = sinf(angle)
    var matrix = matrix_identity_float4x4
    matrix.columns.0 = simd_float4(cos, 0, -sin, 0)
    matrix.columns.2 = simd_float4(sin, 0, cos, 0)
    return matrix
}

视图矩阵

视图矩阵用于将顶点从世界空间转换到相机空间(观察空间)。描述了相机的位置和方向。因此视图矩阵需要以下步骤构建:

  1. 相机的位置 eye
  2. 相机视线方向 g
  3. 相机的向上方向 up

我们需要将相机的位置eye 挪到原点 (0,0,0) ,将相机的视线方向指向 +Z (0,0,1) 方向,将相机的向上方向 up 指向 +Y (0,1,0) , 最后将 g 叉乘 up (使用r向量表示 )指向+X (1,0,0) 方向。
V = r x r y r z − ( r ⋅ e y e ) u p x u p y u p z − ( u p ⋅ e y e ) g x g y g z − ( g ⋅ e y e ) 0 0 0 1 \mathbf{V} = \begin{bmatrix} r_x & r_y & r_z & -(\mathbf{r} \cdot \mathbf{eye}) \\ up_x & up_y & up_z & -(\mathbf{up} \cdot \mathbf{eye}) \\ g_x & g_y & g_z & -(\mathbf{g} \cdot \mathbf{eye}) \\ 0 & 0 & 0 & 1 \end{bmatrix} V= rxupxgx0ryupygy0rzupzgz0−(r⋅eye)−(up⋅eye)−(g⋅eye)1

swift 复制代码
private func viewMatrix(eye: SIMD3<Float>, center: SIMD3<Float>, up: SIMD3<Float>) -> matrix_float4x4 {
    let g = normalize(center - eye)
    let gazeCrossUp = normalize(cross(g, up))
    var matrix = matrix_identity_float4x4
    matrix.columns.0 = vector_float4(gazeCrossUp.x, up.x, g.x, 0)
    matrix.columns.1 = vector_float4(gazeCrossUp.y, up.y, g.y, 0)
    matrix.columns.2 = vector_float4(gazeCrossUp.z, up.z, g.z, 0)
    matrix.columns.3 = vector_float4(-dot(gazeCrossUp, eye),-dot(up, eye),   -dot(g, eye), 1)
    return matrix
}

正交投影矩阵

所谓正交投影,也叫平行投影,我们在中学也学过,用于将三维场景中的点映射到二维平面上。它的特点是投影过程中不会产生透视效果,物体的比例在投影前后保持不变,与距离无关。

正交投影矩阵通常需要以下步骤得到:

  1. 创建一个长方体,使用left、right、top、bottom、far、near分别表示左平面、右平面、上平面、下平面、近平面和远平面。

  2. 将长方体的近平面中心点平移到原点。

  3. 将长方体做缩放变化,X方向缩放到-1, 1、Y方向缩放到-1, 1、Z方向0, 1

2.0 r − l 0 0 − r + l r − l 0 2.0 t − b 0 − t + b t − b 0 0 1.0 f − n − n f − n 0 0 0 1.0 \begin{bmatrix} \frac{2.0}{\text{r} - \text{l}} & 0 & 0 & -\frac{\text{r} + \text{l}}{\text{r} - \text{l}} \\ 0 & \frac{2.0}{\text{t} - \text{b}} & 0 & -\frac{\text{t} + \text{b}}{\text{t} - \text{b}} \\ 0 & 0 & \frac{1.0}{\text{f} - \text{n}} & -\frac{\text{n}}{\text{f} - \text{n}} \\ 0 & 0 & 0 & 1.0 \end{bmatrix} r−l2.00000t−b2.00000f−n1.00−r−lr+l−t−bt+b−f−nn1.0

swift 复制代码
func createOrthographicMatrix(left: Float, right: Float, bottom: Float, top: Float, nearPlane: Float, farPlane: Float) -> simd_float4x4 {
    var matrix = simd_float4x4(0.0)
    matrix[0][0] = 2.0 / (right - left)
    matrix[0][3] = -(right + left) / (right - left)
    matrix[1][1] = 2.0 / (top - bottom)
    matrix[1][3] = -(top + bottom) / (top - bottom)
    matrix[2][2] = 1.0 / (farPlane - nearPlane)
    matrix[2][3] = -nearPlane / (farPlane - nearPlane)
    matrix[3][3] = 1.0
    return matrix
}

应用投影变换

swift 复制代码
let radians = rotationAngle * .pi / 180
let projectionMatrix = createOrthographicMatrix(left: -1, right: 1, bottom: -1, top: 1, nearPlane: 0.1, farPlane: 100)
let rotationMatrix = rotateY(angle: radians)
let viewMatrix = viewMatrix(eye: SIMD3<Float>(0, 0, -5), center: SIMD3<Float>(0, 0, 0), up: SIMD3<Float>(0, 1, 0))
var uniform = Uniforms(projectionMatrix: projectionMatrix, rotationMatrix: rotationMatrix, viewMatrix: viewMatrix)
commanderEncoder?.setVertexBytes(&uniform, length: MemoryLayout<Uniforms>.stride, index: 1)
c++ 复制代码
// 着色器函数
struct Uniforms {
    float4x4 projectionMatrix;
    float4x4 rotationMatrix;
    float4x4 viewMatrix;
};
vertex RasterizerData vertexShader(uint vertexID [[vertex_id]],
                           constant Vertex *vertices [[buffer(0)]],
                           constant Uniforms &uniforms [[buffer(1)]]
                                   ) {
    RasterizerData out;
    out.position = uniforms.projectionMatrix * uniforms.viewMatrix * uniforms.rotationMatrix * vertices[vertexID].position;
    out.textureCoordinate = vertices[vertexID].textureCoordinate;
    return out;
}

将Model-View-Projection矩阵传递给着色器,按照顺序 模型变换 -> 视图变换 -> 投影变换。

结语

通过Model-View-Projection变换,我们可以将三维世界的点映射到二维屏幕上。而学好正交投影变换也为我们继续学习透视投影变换的打下扎实基础。

本项目已开源 欢迎大家点赞、收藏。

参考资料

Games 101

相关推荐
AI创界者10 小时前
PilotTTS 一键整合包(Win/Mac):8G 显存畅跑,实测解锁情绪与副语言的精准控制
人工智能·macos·aigc·音视频
AirDroid_cn14 小时前
系统终端与iTerm2字体看起来不一样?macOS Sequoia统一渲染指南
macos
初级代码游戏15 小时前
easy Photo Clean公测版:快速清理iPhone照片 邀请公测
ios·iphone
库奇噜啦呼15 小时前
【iOS】RunLoop学习
学习·ios
影寂ldy16 小时前
WinForm PictureBox控件 + ImageList组件 完整笔记
开发语言·笔记·swift
黑科技iOS上架17 小时前
iOS应用周末提交什么情况算卡审
经验分享·ios
JiaWen技术圈18 小时前
2026 年的 macOS 磁盘清理方法
macos
lichong95118 小时前
让AI自己用电脑!Cua:后台操作鼠标键盘,Mac/Windows/Linux全支持
人工智能·macos·ai·计算机外设·agent·提示词
A尘埃19 小时前
批处理命令(Linux/Mac、Windows项目启动脚本)
linux·windows·macos