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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/26384.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据集笔记:新加坡LTA MRT 车站出口、路灯 等位置数据集

1 MRT 车站出口 data.gov.sg &#xff08;geojson格式&#xff09; 1.1 kml格式 data.gov.sg 2 路灯 data.govsg ——geojson data.gov.sg——kml 版本 3 道路摄像头数据集 data.gov.sg 4 自行车道网络 data.gov.sg 5 学校区域 data.gov.sg 6 自行车停车架&#xff…

【弹性计算】弹性裸金属服务器和神龙虚拟化(一):功能特点

弹性裸金属服务器和神龙虚拟化&#xff08;一&#xff09;&#xff1a;功能特点 特征一&#xff1a;分钟级交付特征二&#xff1a;兼容 VPC、SLB、RDS 等云平台全业务特征三&#xff1a;兼容虚拟机镜像特征四&#xff1a;云盘启动和数据云盘动态热插拔特征五&#xff1a;虚拟机…

发展中的脑机接口:SSVEP特征提取技术

一、简介 脑机接口&#xff08;BCI&#xff09;是先进的系统&#xff0c;能够通过分析大脑信号与外部设备之间建立通信&#xff0c;帮助有障碍的人与环境互动。BCI通过分析大脑信号&#xff0c;提供了一种非侵入式、高效的方式&#xff0c;让人们与外部设备进行交流。BCI技术越…

EasyRTC:支持任意平台设备的嵌入式WebRTC实时音视频通信SDK解决方案

随着互联网技术的飞速发展&#xff0c;实时音视频通信已成为各行各业数字化转型的核心需求之一。无论是远程办公、在线教育、智慧医疗&#xff0c;还是智能安防、直播互动&#xff0c;用户对低延迟、高可靠、跨平台的音视频通信需求日益增长。 一、WebRTC与WebP2P&#xff1a;实…

为AI聊天工具添加一个知识系统 之127 详细设计之68 编程 核心技术:Cognitive Protocol Language 之2

问题 Q1396、根据我们的讨论&#xff0c;我前面给出的文字表述在用词准确性上以及完整性&#xff08;忽略细节&#xff09; 您觉得有问题吗&#xff1f;有用词错误和 缺项的问题吗 Q1397、请对具体术语的数学定义或工程实现方案进行深度扩展说明 Q1398、 请为全部映射关系提供…

ELK接入SpringBoot【Docker Compose】

安装Docker-Compose curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-uname -s-uname -m -o /usr/local/bin/docker-compose 随便找个地&#xff0c;创建docker-compose.yml文件&#xff0c;把这坨文本复制进去 version: 3 services:el…

基于javaweb的SSM+Maven幼儿园管理系统设计和实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

NAT 代理服务 内网穿透

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 一&#xff1a;&#x1f525; NAT 技术背景二&#xff1a;&#x1f525; NAT IP 转换过程三&#xff1a;&#x1f525; NAPT四&#xff1a;&#x1f525; 代理服务器&#x1f98b; 正向…

Apache IoTDB 树表双模型直播回顾(下)

2 月 26 日面向 Apache IoTDB 树表双模型的功能特性、适用场景、建模选择和未来规划&#xff0c;田原同学通过直播进行了全面解答。以下为直播讲稿&#xff08;下&#xff09;&#xff0c;干货满满&#xff0c;建议收藏⬇️⬇️ ⚡️注意&#xff1a; 1. 功能演示部分请直接查看…

LabVIEW中交叉关联算法

交叉关联算法通过统计多通道信号间的相关性&#xff0c;抑制各通道独立的本底噪声&#xff0c;保留共有的有效信号成分。其数学本质为对多个通道信号进行两两相乘并累加&#xff0c;最终通过归一化处理得到降噪后的输出信号。 这个VI演示了如何在LabVIEW中执行信号的互相关分析…

手撸大模型-基础篇 简单线性回归模型预测房价

# NumPy Pandas Matplotlib import numpy as np import matplotlib.pyplot as plt 双特征&#xff0c;矩阵化 1. Min-Max 归一化及其逆操作 1.1 输入数据归一化 def normalize1(sample, data): max_value np.max(data) min_value np.min(data) return (samp…

使用UA-SPEECH和TORGO数据库验证自动构音障碍语音分类方法

使用UA-SPEECH和TORGO数据库验证自动构音障碍语音分类方法 引言 原文:On using the UA-Speech and TORGO databases to validate automatic dysarthric speech classification approaches 构音障碍简介 构音障碍是一种由于脑损伤或神经疾病(如脑瘫、肌萎缩侧索硬化症、帕金森…

React底层原理详解

React中Element&Fiber对象、WorkInProgress双缓存、Reconcile&Render&Commit、第一次挂载过程详解 在面试中介绍React底层原理时&#xff0c;需遵循逻辑清晰、层次分明、重点突出的原则&#xff0c;结合技术深度与实际应用场景。以下是结构化回答模板&#xff1a;…

el-table修改表格颜色

文章目录 一、el-table属性修改表格颜色1.1、header-row-class-name修改表头行颜色1.2、header-row-style修改表头样式1.3、row-class-name修改行颜色 二、el-table-column属性修改表格颜色2.1、class-name修改整列的颜色2.2、label-class-name修改列标题颜色 本文讲解vue修改e…

Graphics View画一个可调速的风机(pyqt)

效果如图&#xff1a; 风机具备调节转速的功能&#xff0c;转速通过扇叶旋转的快慢来区别&#xff0c;共分为四档&#xff0c;其中零档为静止状态&#xff0c;而一、二、三档则依次增加转速。在代码中&#xff0c;BlowerWrapper 类包含了可旋转的扇叶、风机外框以及选项三个主要…

SP脏迹Dirt生成器

常用生成器之一 用于模型表面生成污垢和脏迹 添加一个填充图层 添加黑色遮罩 添加生成器 选择Dirt 调整强度 如果UV有接缝就把Use Triplanar打开

AnyDesk 远程桌面控制软件 v9.0.2

AnyDesk 是一款功能强大的跨平台远程桌面控制软件。它支持 Windows、MacOS、Linux 和 Android 系统&#xff0c;同时 iOS 也在其支持范围内。其主要功能包括远程桌面控制&#xff0c;用户能远程访问其他计算机或移动设备&#xff0c;用于远程协作、支持和教授等&#xff0c;可实…

关于延迟任务线程池,Java提供的ScheduledThreadPoolExecutor,Spring提供的ThreadPoolTaskScheduler

今天讲解定时任务、延迟任务的线程池使用方式&#xff1a; 1、从 java 角度 2、从 Spring 框架角度 文章目录 Java提供&#xff1a;ScheduledExecutorService接口&#xff08;创建延迟任务线程池&#xff09;① 用法1&#xff1a;1. 重写afterExecute方法2. .schedule()方法调度…

Python PDF文件拆分-详解

目录 使用工具 将PDF按页数拆分 将PDF的每一页拆分为单独的文件 将PDF按指定页数拆分 根据页码范围拆分PDF 根据指定内容拆分PDF 将PDF的一页拆分为多页 在日常生活中&#xff0c;我们常常会遇到大型的PDF文件&#xff0c;这些文件可能难以发送、管理和查阅。将PDF拆分成…

链表的概念及功能实现

一、链表之单向链表 前面我们使用顺序储存结构实现的顺序表&#xff0c;虽然查询的时候很快&#xff0c;但在进行元素的增加或者删除的时候:比较麻烦&#xff0c;需要你去移动大量的元素把数据删除或者增加。 链表里的数据是以结点方式来表示的&#xff0c;每一个结点的组成是…