当前位置: 首页 > news >正文

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变换

在上一章中&#xff0c;您通过在 vertex 函数中计算position&#xff0c;来平移顶点和在屏幕上移动对象。但是&#xff0c;在 3D 空间中&#xff0c;您还想执行更多操作&#xff0c;例如旋转和缩放对象。您还需要一个场景内摄像机&#xff0c;以便您可以在场景中移动。 要移动…...

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 文本内容&#xff0c;用户…...

32位,算Cache地址

32位&#xff0c;算Cache地址...

C++蓝桥杯基础篇(六)

片头 嗨~小伙伴们&#xff0c;大家好&#xff01;今天我们来一起学习蓝桥杯基础篇&#xff08;六&#xff09;&#xff0c;练习相关的数组习题&#xff0c;准备好了吗&#xff1f;咱们开始咯&#xff01; 第1题 数组的左方区域 这道题&#xff0c;实质上是找规律&#xff0c;…...

React 常见面试题及答案

记录面试过程 常见问题&#xff0c;如有错误&#xff0c;欢迎批评指正 1. 什么是虚拟DOM&#xff1f;为什么它提高了性能&#xff1f; 虚拟DOM是React创建的一个轻量级JavaScript对象&#xff0c;表示真实DOM的结构。当状态变化时&#xff0c;React会生成新的虚拟DOM&#xf…...

和鲸科技推出人工智能通识课程解决方案,助力AI人才培养

2025年2月&#xff0c;教育部副部长吴岩应港澳特区政府邀请&#xff0c;率团赴港澳宣讲《教育强国建设规划纲要 (2024—2035 年)》。在港澳期间&#xff0c;吴岩阐释了教育强国目标的任务&#xff0c;并与特区政府官员交流推进人工智能人才培养的办法。这一系列行动体现出人工智…...

免费使用 DeepSeek API 教程及资源汇总

免费使用 DeepSeek API 教程及资源汇总 一、DeepSeek API 资源汇总1.1 火山引擎1.2 百度千帆1.3 阿里百炼1.4 腾讯云 二、其他平台2.1 华为云2.2 硅基流动 三、总结 DeepSeek-R1 作为 2025 年初发布的推理大模型&#xff0c;凭借其卓越的逻辑推理能力和成本优势&#xff0c;迅速…...

网络安全-使用DeepSeek来获取sqlmap的攻击payload

文章目录 概述DeepSeek使用创建示例数据库创建API测试sqlmap部分日志参考 概述 今天来使用DeepSeek做安全测试&#xff0c;看看在有思路的情况下实现的快不快。 DeepSeek使用 我有一个思路&#xff0c;想要测试sqlmap工具如何dump数据库的&#xff1a; 连接mysql数据库&#…...

网络原理--TCP/IP(2)

我们在之前已经介绍到TCP协议的核心机制二,接下来我们将继续介绍其他的核心机制。 核心机制三:连接管理 即建立连接,断开连接,在正常情况下,TCP要经过三次握⼿建⽴连接,四次挥⼿断开连接。 建立连接:TCP是通过“三次握手” 在生活中的握手就是打招呼,,但握手操作没有…...

Ragflow与Dify之我见:AI应用开发领域的开源框架对比分析

本文详细介绍了两个在AI应用开发领域备受关注的开源框架&#xff1a;Ragflow和Dify。Ragflow专注于构建基于检索增强生成&#xff08;RAG&#xff09;的工作流&#xff0c;强调模块化和轻量化&#xff0c;适合处理复杂文档格式和需要高精度检索的场景。Dify则旨在降低大型语言模…...

文件上传漏洞绕过WAF

文件上传漏洞绕过WAF学习笔记 1. WAF检测原理 WAF&#xff08;Web应用防火墙&#xff09;通过以下方式拦截文件上传攻击&#xff1a; 关键字匹配&#xff1a;检测文件名、内容中的敏感词&#xff08;如<?php、eval&#xff09;。 扩展名黑名单&#xff1a;拦截.php、.jsp…...

[含文档+PPT+源码等]精品基于Python实现的vue3+Django计算机课程资源平台

基于Python实现的Vue3Django计算机课程资源平台的背景&#xff0c;可以从以下几个方面进行阐述&#xff1a; 一、教育行业发展背景 1. 教育资源数字化趋势 随着信息技术的快速发展&#xff0c;教育资源的数字化已成为不可逆转的趋势。计算机课程资源作为教育领域的重要组成部…...

Qt 开源音视频框架模块之QtAV播放器实践

Qt 开源音视频框架模块QtAV播放器实践 1 摘要 QtAV是一个基于Qt的多媒体框架&#xff0c;旨在简化音视频播放和处理。它是一个跨平台的库&#xff0c;支持多种音视频格式&#xff0c;并提供了一个简单易用的API来集成音视频功能。QtAV的设计目标是为Qt应用程序提供强大的音视…...

【前端】XML,XPATH,与HTML的关系

XML与HTML关系 XML&#xff08;可扩展标记语言&#xff09;和 HTML&#xff08;超文本标记语言&#xff09;是两种常见的标记语言&#xff0c;但它们有不同的目的和用途。它们都使用类似的标记结构&#xff08;标签&#xff09;&#xff0c;但在设计上存在一些关键的差异。 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 添加并激活环境…...

市场加速下跌,但监管「坚冰」正在消融

作者&#xff1a;Techub 热点速递 撰文&#xff1a;Yangz&#xff0c;Techub News 与近日气温逐步回暖不同&#xff0c;自 2 月 25 日比特币跌破 9 万美元以来&#xff0c;加密货币市场行情一路下滑。今日 10 时 50 分左右&#xff0c;比特币更是跌破 8 万美元大关&#xff0c…...

7.2 - 定时器之计算脉冲宽度实验

文章目录 1 实验任务2 系统框图3 软件设计 1 实验任务 本实验任务是通过CPU私有定时器来计算按键按下的时间长短。 2 系统框图 参见7.1。 3 软件设计 注意事项&#xff1a; 定时器是递减计数的&#xff0c;需要考虑StartCount&#xff1c;EndCount的情况。 /***********…...

Imagination DXTP GPU IP:加速游戏AI应用,全天候畅玩无阻

日前&#xff0c;Imagination 推出了最新产品——Imagination DXTP GPU IP&#xff0c;在智能手机和其他功耗受限设备上加速图形和AI工作负载时&#xff0c;保证全天候的电池续航。它是我们最新D系列GPU的最终产品&#xff0c;集成了自2022年发布以来引入的一系列功能&#xff…...

关于流水线的理解

还是不太理解&#xff0c;我之前一直以为&#xff0c;对axis总线&#xff0c;每一级的寄存器就像fifo一样&#xff0c;一级一级的分级存储最后一级需要的数据。&#xff08;现在看来&#xff0c;我这个理解应该也是没有问题的&#xff09; 如下图&#xff0c;一开始是在解析axi…...

采样算法二:去噪扩散隐式模型(DDIM)采样算法详解教程

参考 https://arxiv.org/pdf/2010.02502 一、背景与动机 去噪扩散隐式模型&#xff08;DDIM&#xff09; 是对DDPM的改进&#xff0c;旨在加速采样过程同时保持生成质量。DDPM虽然生成效果优异&#xff0c;但其采样需迭代数百至数千次&#xff0c;效率较低。DDIM通过以下关键…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例

文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...