Metal 学习笔记五:3D变换

在上一章中,您通过在 vertex 函数中计算position,来平移顶点和在屏幕上移动对象。但是,在 3D 空间中,您还想执行更多操作,例如旋转和缩放对象。您还需要一个场景内摄像机,以便您可以在场景中移动。
要移动、缩放和旋转三角形,您将使用矩阵 - 一旦您掌握了一个三角形,就可以一次旋转具有数千个三角形的模型!
对于我们这些不是数学天才的人来说,向量和矩阵可能有点可怕。幸运的是,在使用数学时,您不必总是需要知道引擎盖下的内容。为了提供帮助,本章的重点不是数学,而是矩阵。在学习本章的过程中,您将逐渐扩展您的线性代数知识,因为您将了解矩阵可以为您做什么以及如何操作它们。
变换
查看下面的图片:

使用矢量图像编辑器 Affinity Designer,您可以通过一系列仿射变换来缩放和旋转猫。Affinity Designer 不会单独计算每个位置,而是创建一个包含变换组合的变换矩阵。然后,它将变换应用于每个元素。
注: 仿射表示完成变换后,所有平行线都保持平行。
当然,没有人愿意移动、缩放和旋转猫,因为它们可能会咬人。因此,您将平移、缩放和旋转三角形。
开始项目和设置
➤ 打开并运行位于本章 starter 文件夹中的 starter 项目。

此项目渲染一个三角形两次,而不是渲染一个四边形。
在 Renderer 的 draw(in:) 中,您将看到两个 draw 调用(每个三角形一个)。渲染器将 position 传递给 vertex 函数,将 color 传递给 fragment 函数;它对每个三角形执行此操作。灰色三角形位于其原始位置,红色三角形应用变换。
➤ 在继续下一步之前,请确保您理解 Renderer 的 draw(in:) 中的代码和 Shaders.metal 中的 vertex 函数。
ContentView.swift 现在位于 SwiftUI 视图组中,它在Metal View上显示网格,以便您可以更轻松地可视化顶点位置。
平移
starter项目展示两个三角形:
• 没有任何变换的灰色三角形。
• 使用position = simd_float3(0.3, -0.4, 0) 平移的红色三角形。

在上一章的第一个挑战中,您计算了着色器函数中每个顶点的位置。更常见的计算机图形范例是,在顶点缓冲区中设置模型每个顶点的位置,通常从文件加载,然后将矩阵发送到顶点着色器,着色器包含模型当前位置、旋转和缩放。
顶点和矩阵
您可以更好地将position描述为位移矢量[0.3, -0.4, 0]。每个顶点从 x 方向上移动 0.3 个单位,在 y 方向上移动 -0.4 个单位。
在下图中,蓝色箭头是矢量。

左侧蓝色箭头是值为 [-1, 2] 的向量。右侧的蓝色箭头(靠近 cat 的箭头)也是值为 [-1, 2] 的向量。position(point)是空间中的位置,而向量是空间中的位移。换句话说,向量包含移动的数量和方向。如果你要用蓝色向量来移动猫,它最终会到达点 (2, 4)。这是猫的位置 (3, 2) 加上向量 [-1, 2]。
此 2D 向量是一个 1x2 矩阵。它有一列和两行。
注: 您可以按行或列对矩阵进行排序。simd 库按列优先顺序构造矩阵,这意味着列在内存中是连续的。simd_double2x4 是包含两列四行的矩阵。
矩阵是二维数组。即使是单个数字 1 也是一个 1×1 矩阵。实际上,数字1是独一无二的,因为用任何数字乘以1都是它本身。所有方阵(行数和列数相等的矩阵),都有一个矩阵具有和1的概念相等的矩阵——单位矩阵。任何向量或矩阵乘以单位矩阵,都是它本身。
4×4 单位矩阵如下所示(全是 0,对角线 1 除外):

3D 变换矩阵有 4 行和 4 列。变换矩阵左上角的 3×3 矩阵中包含缩放和旋转信息,平移信息在最后一列中。将向量和矩阵相乘时,左侧矩阵或向量的列数必须等于右侧的行数。例如,您不能将 float3 乘以 float4×4。
矩阵的魔力
当您将矩阵相乘时,您将它们合并为一个矩阵。然后,您可以将向量乘以此矩阵来变换向量。例如,您可以设置旋转矩阵和平移矩阵。然后,您可以使用以下代码计算转换后的位置:
translationMatrix * rotationMatrix * positionVector
矩阵乘法从右向左进行。在这里,旋转应用于平移之前的位置。
这是线性代数的基础——如果你想继续学习计算机图形学,你需要更全面地理解线性代数。目前,了解设置转换矩阵的概念可以让你走很长的路。
创建矩阵
➤ 打开 Renderer.swift,找到你在 draw(in:) 中渲染第一个灰色三角形的位置。
➤ 将position代码从:
var position = simd_float3(0, 0, 0)
renderEncoder.setVertexBytes(&position,length: MemoryLayout<SIMD3<Float>>.stride,index: 11)
改为:
var translation = matrix_float4x4()
translation.columns.0 = [1, 0, 0, 0]
translation.columns.1 = [0, 1, 0, 0]
translation.columns.2 = [0, 0, 1, 0]
translation.columns.3 = [0, 0, 0, 1]
var matrix = translation
renderEncoder.setVertexBytes(&matrix,length: MemoryLayout<matrix_float4x4>.stride,index: 11)
在这里,您将创建一个单位矩阵和一个要发送到 GPU 的渲染命令。
➤ 找到第二个红色三角形的position代码并更改:
position = simd_float3(0.3, -0.4, 0)
renderEncoder.setVertexBytes(&position,length: MemoryLayout<SIMD3<Float>>.stride,index: 11)
为:
let position = simd_float3(0.3, -0.4, 0)
translation.columns.3.x = position.x
translation.columns.3.y = position.y
translation.columns.3.z = position.z
matrix = translation
renderEncoder.setVertexBytes(&matrix,length: MemoryLayout<matrix_float4x4>.stride,index: 11)
您将使用此矩阵来平移顶点着色器中的position。
➤ 打开 Shaders.metal,然后更改:
constant float3 &position [[buffer(11)]])
为:
constant float4x4 &matrix [[buffer(11)]])
您将接收传入着色器中的矩阵。
➤ 在 vertex 函数中,更改:
float3 translation = in.position.xyz + position;
为:
float3 translation = in.position.xyz + matrix.columns[3].xyz;
使用矩阵的第四列作为位移向量。
➤ 构建并运行。到目前为止,输出是相同的。

请记住,此矩阵还将保存旋转和缩放信息。要计算position,您需要执行矩阵乘法,而不是添加平移位移向量。
➤ 将 vertex 函数的内容改为:
float4 translation = matrix * in.position;
VertexOut out {.position = translation
};
return out;
➤ 构建并运行应用程序,您会发现仍然没有变化。
现在,您可以在 Renderer 中向矩阵添加缩放和旋转,而无需每次更改着色器函数。
缩放
➤ 打开 Renderer.swift,然后在 draw(in:) 中找到第二个红色三角形中设置 matrix 的位置。
➤ 在 matrix = translation 之前,添加以下内容:
let scaleX: Float = 1.2
let scaleY: Float = 0.5
let scaleMatrix = float4x4([scaleX, 0, 0, 0],[0, scaleY, 0, 0],[0, 0, 1, 0],[0, 0, 0, 1])
您可以像这样初始化一个矩阵,将列定义为数组,而不是像在“平移”中所做的那样分配给列。scaleMatrix.columns.0 现在包含 [1.2, 0, 0, 0]。
无需过多地进行数学研究,就可以使用此代码来设置缩放矩阵。
➤ 将 matrix = translation更改为: matrix = scaleMatrix
将平移矩阵乘以缩放矩阵,而不是乘以平移矩阵。
➤ 构建并运行应用程序。

在 vertex 函数中,矩阵将三角形的每个顶点乘以 x 和 y 缩放因子。
➤ 将 matrix = scaleMatrix 更改为: matrix = translation * scaleMatrix
此代码平移缩放的三角形。
➤ 构建并运行应用程序。

旋转
执行旋转的方式与缩放类似。
➤ 将 matrix = translation * scaleMatrix,改为:
let angle = Float.pi / 2.0
let rotationMatrix = float4x4([cos(angle), -sin(angle), 0, 0],[sin(angle), cos(angle), 0, 0],[0, 0, 1, 0],[0, 0, 0, 1])
matrix = rotationMatrix
在这里,您可以设置围绕 z 轴旋转的角度(以弧度为单位)。
注意:Float.pi / 2.0 与 90° 相同,均为 1.5708 弧度。弧度是计算机图形学中的标准单位。这是将度数转换为弧度的公式:度数 * pi / 180 = 弧度。
➤ 构建并运行,您将看到红色三角形的每个顶点如何围绕原点 [0, 0, 0] 旋转 90°。

➤ 将 matrix = rotationMatrix 替换为:
matrix = translation * rotationMatrix * scaleMatrix
此代码首先缩放每个顶点,然后旋转,然后平移。
➤ 构建并运行。

矩阵运算的顺序很重要。尝试更改顺序,看看会发生什么。
缩放和旋转发生在原点 (坐标 [0, 0, 0])。但是,有时您可能希望围绕不同的点进行旋转。例如,当三角形处于其原始位置(即与灰色三角形相同的位置和旋转角度)时,让我们围绕三角形的最右边点旋转三角形。
要旋转三角形,您需要设置一个平移矩阵,其中向量位于原点和最右侧点之间,执行以下步骤:
1. 使用平移矩阵平移所有顶点。
2. 旋转。
3. 再次平移回来。
➤ 在对红色小三角设置矩阵之前,添加以下代码:
translation.columns.3.x = triangle.vertices[2].x
translation.columns.3.y = triangle.vertices[2].y
translation.columns.3.z = triangle.vertices[2].z
将变换矩阵设置为(从原点)移动到(灰色)三角形的第三个顶点,即最右侧的点。
记住这些步骤。第 1 步是按距原点的距离平移所有顶点。您可以通过将矩阵设置为顶点的向量值并使用平移矩阵的逆矩阵来实现此目的。
在执行以下每个步骤后,不要忘记构建并运行应用程序,以便您可以查看矩阵乘法的作用。
➤ 将matrix = translation * rotationMatrix * scaleMatrix 更改为: matrix = translation.inverse
此代码将最右侧的顶点放在原点处,以相同的距离平移所有其他顶点。

➤ 将您刚刚输入的代码更改为:
matrix = rotationMatrix * translation.inverse
三角形绕原点旋转 90°。

➤ 将您刚刚输入的代码更改为:
matrix = translation * rotationMatrix * translation.inverse
匪夷所思!您正在执行按最右侧顶点与原点的距离平移每个顶点的所有步骤。之后,您将旋转每个顶点并再次将其平移回来,使三角形围绕其最右侧的点旋转。

参考
https://zhuanlan.zhihu.com/p/387152681
相关文章:
Metal 学习笔记五:3D变换
在上一章中,您通过在 vertex 函数中计算position,来平移顶点和在屏幕上移动对象。但是,在 3D 空间中,您还想执行更多操作,例如旋转和缩放对象。您还需要一个场景内摄像机,以便您可以在场景中移动。 要移动…...
unity学习56:旧版legacy和新版TMP文本输入框 InputField学习
目录 1 旧版文本输入框 legacy InputField 1.1 新建一个文本输入框 1.2 InputField 的子物体构成 1.3 input field的的component 1.4 input Field的属性 2 过渡 transition 3 控件导航 navigation 4 占位文本 placeholder 5 文本 text 5.1 文本内容,用户…...
32位,算Cache地址
32位,算Cache地址...
C++蓝桥杯基础篇(六)
片头 嗨~小伙伴们,大家好!今天我们来一起学习蓝桥杯基础篇(六),练习相关的数组习题,准备好了吗?咱们开始咯! 第1题 数组的左方区域 这道题,实质上是找规律,…...
React 常见面试题及答案
记录面试过程 常见问题,如有错误,欢迎批评指正 1. 什么是虚拟DOM?为什么它提高了性能? 虚拟DOM是React创建的一个轻量级JavaScript对象,表示真实DOM的结构。当状态变化时,React会生成新的虚拟DOM…...
和鲸科技推出人工智能通识课程解决方案,助力AI人才培养
2025年2月,教育部副部长吴岩应港澳特区政府邀请,率团赴港澳宣讲《教育强国建设规划纲要 (2024—2035 年)》。在港澳期间,吴岩阐释了教育强国目标的任务,并与特区政府官员交流推进人工智能人才培养的办法。这一系列行动体现出人工智…...
免费使用 DeepSeek API 教程及资源汇总
免费使用 DeepSeek API 教程及资源汇总 一、DeepSeek API 资源汇总1.1 火山引擎1.2 百度千帆1.3 阿里百炼1.4 腾讯云 二、其他平台2.1 华为云2.2 硅基流动 三、总结 DeepSeek-R1 作为 2025 年初发布的推理大模型,凭借其卓越的逻辑推理能力和成本优势,迅速…...
网络安全-使用DeepSeek来获取sqlmap的攻击payload
文章目录 概述DeepSeek使用创建示例数据库创建API测试sqlmap部分日志参考 概述 今天来使用DeepSeek做安全测试,看看在有思路的情况下实现的快不快。 DeepSeek使用 我有一个思路,想要测试sqlmap工具如何dump数据库的: 连接mysql数据库&#…...
网络原理--TCP/IP(2)
我们在之前已经介绍到TCP协议的核心机制二,接下来我们将继续介绍其他的核心机制。 核心机制三:连接管理 即建立连接,断开连接,在正常情况下,TCP要经过三次握⼿建⽴连接,四次挥⼿断开连接。 建立连接:TCP是通过“三次握手” 在生活中的握手就是打招呼,,但握手操作没有…...
Ragflow与Dify之我见:AI应用开发领域的开源框架对比分析
本文详细介绍了两个在AI应用开发领域备受关注的开源框架:Ragflow和Dify。Ragflow专注于构建基于检索增强生成(RAG)的工作流,强调模块化和轻量化,适合处理复杂文档格式和需要高精度检索的场景。Dify则旨在降低大型语言模…...
文件上传漏洞绕过WAF
文件上传漏洞绕过WAF学习笔记 1. WAF检测原理 WAF(Web应用防火墙)通过以下方式拦截文件上传攻击: 关键字匹配:检测文件名、内容中的敏感词(如<?php、eval)。 扩展名黑名单:拦截.php、.jsp…...
[含文档+PPT+源码等]精品基于Python实现的vue3+Django计算机课程资源平台
基于Python实现的Vue3Django计算机课程资源平台的背景,可以从以下几个方面进行阐述: 一、教育行业发展背景 1. 教育资源数字化趋势 随着信息技术的快速发展,教育资源的数字化已成为不可逆转的趋势。计算机课程资源作为教育领域的重要组成部…...
Qt 开源音视频框架模块之QtAV播放器实践
Qt 开源音视频框架模块QtAV播放器实践 1 摘要 QtAV是一个基于Qt的多媒体框架,旨在简化音视频播放和处理。它是一个跨平台的库,支持多种音视频格式,并提供了一个简单易用的API来集成音视频功能。QtAV的设计目标是为Qt应用程序提供强大的音视…...
【前端】XML,XPATH,与HTML的关系
XML与HTML关系 XML(可扩展标记语言)和 HTML(超文本标记语言)是两种常见的标记语言,但它们有不同的目的和用途。它们都使用类似的标记结构(标签),但在设计上存在一些关键的差异。 XML…...
ubuntu服务器安装VASP.6.4.3
ubuntu服务器安装VASP.6.4.3 1 安装Intel OneAPI Base Toolkit和Intel OneAPI HPC Toolkit1.1 更新并安装环境变量1.2 下载Intel OneAPI Base Toolkit和Intel OneAPI HPC Toolkit安装包1.3 安装 Intel OneAPI Base Toolkit1.4 安装 Intel OneAPI HPC Toolkit1.5 添加并激活环境…...
市场加速下跌,但监管「坚冰」正在消融
作者:Techub 热点速递 撰文:Yangz,Techub News 与近日气温逐步回暖不同,自 2 月 25 日比特币跌破 9 万美元以来,加密货币市场行情一路下滑。今日 10 时 50 分左右,比特币更是跌破 8 万美元大关,…...
7.2 - 定时器之计算脉冲宽度实验
文章目录 1 实验任务2 系统框图3 软件设计 1 实验任务 本实验任务是通过CPU私有定时器来计算按键按下的时间长短。 2 系统框图 参见7.1。 3 软件设计 注意事项: 定时器是递减计数的,需要考虑StartCount<EndCount的情况。 /***********…...
Imagination DXTP GPU IP:加速游戏AI应用,全天候畅玩无阻
日前,Imagination 推出了最新产品——Imagination DXTP GPU IP,在智能手机和其他功耗受限设备上加速图形和AI工作负载时,保证全天候的电池续航。它是我们最新D系列GPU的最终产品,集成了自2022年发布以来引入的一系列功能ÿ…...
关于流水线的理解
还是不太理解,我之前一直以为,对axis总线,每一级的寄存器就像fifo一样,一级一级的分级存储最后一级需要的数据。(现在看来,我这个理解应该也是没有问题的) 如下图,一开始是在解析axi…...
采样算法二:去噪扩散隐式模型(DDIM)采样算法详解教程
参考 https://arxiv.org/pdf/2010.02502 一、背景与动机 去噪扩散隐式模型(DDIM) 是对DDPM的改进,旨在加速采样过程同时保持生成质量。DDPM虽然生成效果优异,但其采样需迭代数百至数千次,效率较低。DDIM通过以下关键…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...
