10. WebGPU 旋转变换

news2024/12/24 2:21:35

单位圆是半径为 1.0 的圆。

下图是一个单位圆。 [注释1]
在这里插入图片描述

在上图中,当围绕圆拖动蓝色手柄时,X 和 Y 位置会发生变化,代表该点在圆上的位置。且在顶部,Y 为 1,X 为 0。在右侧,X 为 1,Y 为 0。

如果你还记得三年级的基础数学,将某个东西乘以 1,它会保持不变。所以 123 * 1 = 123。很基础,对吧?好吧,一个单位圆,一个半径为 1.0 的圆也是 1 的一种形式。它是一个旋转的 1。所以可以用这个单位圆乘以一些东西,在某种程度上它有点像乘以 1。

我们将从单位圆上的任意点获取 X 和 Y 值,并将顶点位置乘以之前示例中的值。

下边是着色器的修改。

struct Uniforms {
  color: vec4f,
  resolution: vec2f,
  translation: vec2f,
  rotation: vec2f,
};
 
struct Vertex {
  @location(0) position: vec2f,
};
 
struct VSOutput {
  @builtin(position) position: vec4f,
};
 
@group(0) @binding(0) var<uniform> uni: Uniforms;
 
@vertex fn vs(vert: Vertex) -> VSOutput {
  var vsOut: VSOutput;
 
  // Rotate the position
  let rotatedPosition = vec2f(
    vert.position.x * uni.rotation.x - vert.position.y * uni.rotation.y, //here
    vert.position.x * uni.rotation.y + vert.position.y * uni.rotation.x  //here
  );
 
  // Add in the translation
 // let position = vert.position + uni.translation;
  let position = rotatedPosition + uni.translation;
 
  // convert the position from pixels to a 0.0 to 1.0 value
  let zeroToOne = position / uni.resolution;
 
  // convert from 0 <-> 1 to 0 <-> 2
  let zeroToTwo = zeroToOne * 2.0;
 
  // covert from 0 <-> 2 to -1 <-> +1 (clip space)
  let flippedClipSpace = zeroToTwo - 1.0;
 
  // flip Y
  let clipSpace = flippedClipSpace * vec2f(1, -1);
 
  vsOut.position = vec4f(clipSpace, 0.0, 1.0);
  return vsOut;
}

更新了 JavaScript 增加 uniform 的大小。

  // color, resolution, translation
  //const uniformBufferSize = (4 + 2 + 2) * 4;
  // color, resolution, translation, rotation, padding
  const uniformBufferSize = (4 + 2 + 2 + 2) * 4 + 8; //here
  const uniformBuffer = device.createBuffer({
    label: 'uniforms',
    size: uniformBufferSize,
    usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
  });
 
  const uniformValues = new Float32Array(uniformBufferSize / 4);
 
  // offsets to the various uniform values in float32 indices
  const kColorOffset = 0;
  const kResolutionOffset = 4;
  const kTranslationOffset = 6;
  const kRotationOffset = 8; //here
 
  const colorValue = uniformValues.subarray(kColorOffset, kColorOffset + 4);
  const resolutionValue = uniformValues.subarray(kResolutionOffset, kResolutionOffset + 2);
  const translationValue = uniformValues.subarray(kTranslationOffset, kTranslationOffset + 2);
  const rotationValue = uniformValues.subarray(kRotationOffset, kRotationOffset + 2); //here

我们需要UI用于显示。这篇文章不是关于制作 UI 的教程,所以我只打算使用一个。首先是一些 HTML 给 单位圆 一个占位

  <body>
    <canvas></canvas>
    <div id="circle"></div>
  </body>

然后是一些用于定位的 CSS

#circle {
  position: fixed;
  right: 0;
  bottom: 0;
  width: 300px;
  background-color: var(--bg-color);
}

最后是使用它的 JavaScript。

import UnitCircle from './resources/js/unit-circle.js'; //here
 
...
 
  const gui = new GUI();
  gui.onChange(render);
  gui.add(settings.translation, '0', 0, 1000).name('translation.x');
  gui.add(settings.translation, '1', 0, 1000).name('translation.y');
 
  const unitCircle = new UnitCircle(); //here
  document.querySelector('#circle').appendChild(unitCircle.domElement); //here
  unitCircle.onChange(render); //here
 
  function render() {
    ...
 
    // Set the uniform values in our JavaScript side Float32Array
    resolutionValue.set([canvas.width, canvas.height]);
    translationValue.set(settings.translation);
    rotationValue.set([unitCircle.x, unitCircle.y]); //here
 
    // upload the uniform values to the uniform buffer
    device.queue.writeBuffer(uniformBuffer, 0, uniformValues);

下边是显示结果。拖动圆圈上的手柄进行旋转或拖动滑块进行平移。

在这里插入图片描述

为什么它会起作用?这里用数学解释一下。

rotatedX = a_position.x * u_rotation.x - a_position.y * u_rotation.y;
rotatedY = a_position.x * u_rotation.y + a_position.y * u_rotation.x;

假设有一个矩形并且想要旋转它。在开始旋转之前,右上角位于( 3.0,-9.0)。让我们在单位圆上从 3 点钟方向顺时针 30 度取一个点。
在这里插入图片描述

上图圆上的位置是x = 0.87, y = 0.50

 3.0 * 0.87 - -9.0 * 0.50 =  7.1
 3.0 * 0.50 + -9.0 * 0.87 = -6.3

这正是它旋转后的地方
在这里插入图片描述

同样的取顺时针60度
在这里插入图片描述

圆圈上的位置是 0.87 和 0.50

 3.0 * 0.50 - -9.0 * 0.87 =  9.3
 3.0 * 0.87 + -9.0 * 0.50 = -1.9

可以看到,当顺时针旋转该点时,X 值变大,Y 值变小。如果继续超过 90 度,X 将再次开始变小,而 Y 将开始变大。这种模式会交替轮换。

单位圆上的点还有另一个名字。它们被称为正弦和余弦。所以对于任何给定的角度,可以像这样获取正弦和余弦。

function printSineAndCosineForAnAngle(angleInDegrees) {
  const angleInRadians = angleInDegrees * Math.PI / 180;
  const s = Math.sin(angleInRadians);
  const c = Math.cos(angleInRadians);
  console.log('s =', s, 'c =', c);
}

如果将代码复制并粘贴到 JavaScript 控制台并键入 printSineAndCosignForAngle(30) ,会看到它打印出 s = 0.50 c = 0.87 (注意:我对数字进行了四舍五入)

如果把它们放在一起,可以将顶点位置旋转到想要的任何角度。只需将旋转设置为要旋转到的角度的正弦和余弦。

  ...
  const angleInRadians = angleInDegrees * Math.PI / 180;
  rotation[0] = Math.cos(angleInRadians);
  rotation[1] = Math.sin(angleInRadians);

下边把代码改成只有一个旋转参数。

  const degToRad = d => d * Math.PI / 180;
 
  const settings = {
    translation: [150, 100],
    rotation: degToRad(30),//here
  };
 
  const radToDegOptions = { min: -360, max: 360, step: 1, converters: GUI.converters.radToDeg };
 
  const gui = new GUI();
  gui.onChange(render);
  gui.add(settings.translation, '0', 0, 1000).name('translation.x');
  gui.add(settings.translation, '1', 0, 1000).name('translation.y');
  gui.add(settings, 'rotation', radToDegOptions);
 
 // const unitCircle = new UnitCircle();
 // document.querySelector('#circle').appendChild(unitCircle.domElement);
 // unitCircle.onChange(render);
 
  function render() {
    ...
 
    // Set the uniform values in our JavaScript side Float32Array
    resolutionValue.set([canvas.width, canvas.height]);
    translationValue.set(settings.translation); //here
    // rotationValue.set([unitCircle.x, unitCircle.y]);
    rotationValue.set([
        Math.cos(settings.rotation), //here
        Math.sin(settings.rotation), //here
    ]);

拖动滑块以平移或旋转。

在这里插入图片描述

我希望这很符合直觉。接下来是一个更简单的。缩放变换。

注释1

什么是弧度(radians)?

弧度是用于圆、旋转和角度的测量单位。就像我们可以以英寸、码、米等为单位测量距离一样,我们可以以度数或弧度来测量角度。

您可能知道公制测量的数学比英制测量的数学更容易。从英寸到英尺,我们除以 12。从英寸到码,我们除以 36。我不了解你,但我无法在脑海中除以 36。使用公制就容易多了。从毫米换算成厘米,我们除以 10。从毫米换算成米,我们除以 1000。我可以在脑海中除以 1000。

弧度与度数相似。角度使数学变得困难。弧度使数学变得简单。一个圆有 360 度,但只有 2π 弧度。所以一整圈是 2π 弧度。半圈是 1π 弧度。 1/4 圈,即 90 度是 1/2π 弧度。所以如果你想将某物旋转 90 度,只需使用 Math.PI * 0.5 。如果你想将其旋转 45 度,请使用 Math.PI * 0.25 等。

如果您开始考虑弧度,几乎所有涉及角度、圆或旋转的数学运算都非常简单。所以试试吧。使用弧度而不是角度,UI 显示除外。

这个单位圆有 +Y 向下以匹配我们的像素空间也是 Y 向下。 WebGPU 的正常剪辑空间是 +Y 向上。在上一篇文章中,我们已经在着色器中翻转了 Y。

原文地址

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

互联网JAVA工程师必背面试题和项目面试通关要点(带答案)

前言 大家好&#xff0c;前几天我将各个大厂的高频考点以及 Java 岗需掌握的核心知识整理出的 Java 核心知识大全分享给了大家&#xff0c;今天为了能让看过这份资料的小伙伴更加好地去理解和灵活运用。小编今天将把前段时间和那份资料一块儿收集的 1100 道 Java 岗大厂面试真…

你知道Unity IoC Container是如何创建对象的吗?

ty是微软P&P推出的一个开源的IoC框架&#xff0c;最新的官方版本是2.0。Unity之前的版本建立在一个称为ObjectBuild的组件上&#xff0c;熟悉EnterLib的读者&#xff0c;相信对ObjectBuild不会感到陌生。对于EnterLib 5.0之前的版本&#xff0c;ObjectBuild可以说是所有App…

Linux:初识

1 诞生 创始人&#xff1a;林纳斯 托瓦兹 时 间&#xff1a;1991年&#xff0c;上大学期间 2 Linux的组成 linux主要由内核、系统级应用程序组成 图2-1 Linux系统结构&#xff08;图自黑马程序员课程&#xff09; 内核&#xff1a;对硬件进行调度&#xff0c;比如调度CPU、内…

CSDN原力值解析:功能作用、获取方法、积分对应等级关系详解

开篇声明&#xff1a;本博主非官方人员&#xff0c;也是非所谓的 CSDN 内容合伙人&#xff0c;所以本文博主站在一个中立的角度、以博主自身的主观观点的角度来解答 CSDN 的一个叫做 “原力值” 东西&#xff0c;本文欢迎随时在留言区讨论&#xff0c;但是拒绝硬杠&#xff0c;…

推挽电路应用

1. 推挽电路&#xff0c;常用上N下P型 2. 输出与输入同相&#xff1a; 输入低电平&#xff0c;输出低电平&#xff0c;输出受输入限制&#xff0c;输入输出同相 输入高电平&#xff0c;输出低电平&#xff0c;输出受输入限制&#xff0c;输入输出同相 3. N管烧坏原因分析 当…

华为诺亚极简网络,靠13层就拿下83%精度(附源代码)

点击蓝字 关注我们 关注并星标 从此不迷路 计算机视觉研究院 公众号ID&#xff5c;计算机视觉研究院 学习群&#xff5c;扫码在主页获取加入方式 论文地址&#xff1a;https://arxiv.org/pdf/2305.12972.pdf 项目代码&#xff1a;https://github.com/huawei-noah/VanillaNet ht…

【裸机开发】系统时钟分路 PLL2_PFDx、PLL3_PFDx 配置实验(二)—— 寄存器分析篇

上一篇介绍了 系统时钟的来源、时钟树 以及 PLL1 分路的配置步骤。我们注意到&#xff0c;PLL2、PLL3是固定倍频&#xff0c;无法修改&#xff0c;但是他们下分的 PFDx 分路是可以修改的。只不过我们在初始化的时候&#xff0c;依然按照官方给定的频率进行初始化。 目录 一、了…

2023年,程序员如何构建持续增长的被动收入?

大家好&#xff0c;我是晓衡&#xff01; 我致力于帮助开发者通过技术&#xff0c;实现被动收入&#xff0c;并利用复利效应获得收益最大化。 经过 6 年的探索和实践&#xff0c;取得了一定的成效。 01 起点 我是从 2018 年 7 月份开始全职做这个 Cocos 内容公众号&#xff0c;…

room数据库升级

直接添加表字段&#xff0c;不升级会报异常如上图。 1.表字段或减少表字段 2.增加表 如上等情况需要升级数据库 方法如下&#xff08;以添加表字段为例&#xff09;&#xff1a; Database( entities [XXInfo::class], version 2 // 旧版本为1 ) init { …

Linux:grep、wc命令和管道符

1、grep命令&#xff1a;从文件中根据关键词过滤文件行&#xff0c;语法&#xff1a; grep [-n] 关键词 文件路径 选项-n是可选的&#xff0c;表示在结果中输出匹配到的行的行号关键词&#xff1a;必填&#xff0c;表示要过滤的关键词文件路径&#xff1a;必填&#xff0c;表示…

海思如何编译驱动

一、安装海思的SDK 这一步在海思的说明文档中有&#xff0c;运行sdk.unpack 二、配置内核 进入osdrv/opensource/kernel/ 根据里面的说明文档&#xff0c;没有内核就去下载内核&#xff0c;如果在www.kernel.org网站下载内核十分慢&#xff0c;推荐使用镜像列表下载&#xff0c…

web动画(Animation) - 过渡效果transition

内容目录&#xff1a; 过渡动画&#xff1b;过渡动画的属性&#xff1b; 一、过渡动画 过渡&#xff08;transition&#xff09;作用&#xff1a;- 通过过渡可以指定一个属性发生变化时的切换方式- 通过过渡可以创建一些非常好的效果&#xff0c;提升用户的体验现在我们通过一…

Qt6之样式表2

一、样式选择器类型 一般情况下组件最终都会产生父子、子孙等关系&#xff0c;此时样式选择器类型非常重要&#xff0c;它决定着你的类型是否互相直接独立、互相影响和便捷高效的快速设置样式。 1、如下图常见的一个工具栏&#xff0c;切换时鼠标划过是灰色&#xff0c;选中后是…

QTYX量化系统实战案例分享|每日增量涨停股池叠加形态分析-202306第三弹

前言 “实战案例分享系列”是和大家分享一些股票量化分析工具QTYX在实战中的应用案例&#xff08;包括失败的案例&#xff09;&#xff0c;这样能够帮助大家更好地去理解QTYX中的功能设计&#xff0c;也能更好地帮助大家搭建出属于自己的量化交易系统。 关于QTYX的使用攻略可以…

给大家分享下什么是「API接口」

作为产品经理&#xff0c;了解清楚接口的相关知识是非常有必要的&#xff0c;毕竟总不想被技术大佬认为自己时什么都不懂的需求搬运工。那就往下看下去吧 -----拿去餐馆吃饭的例子 模拟网络请求流程 厨师是后端提供API&#xff0c;服务员是前端请求调用API&#xff0c;我们是用…

Pyside6-第八篇-QLabel文本标签

本篇是Pyside6的第八篇&#xff0c;本章来看看另一个知识点。 文本标签QLabel。它不仅仅可以用于文本&#xff0c;还可以显示富文本和图像。它是一个多功能的小部件&#xff0c;可以根据需要显示不同类型的内容。 部分源码 class QLabel(QFrame):"""QLabel(self…

T9481 T8300 T8110C罗克韦尔自动化可信通信接口

​ T9481 T8300 T8110C罗克韦尔自动化可信通信接口 T9481 T8300 T8110C罗克韦尔自动化可信通信接口 DCS有哪些通讯方式&#xff1f;各有什么缺点&#xff1f; dcs作为大型控制系统&#xff0c;它采用的通信方式无非就是数字通信和模拟通信。数字通信它在DCS使用就是在监视层和…

PCB封装设计实践和文件模板

在之前的文章 详解AD(Altium Designer 23)中的机械层 - 1、详解AD(Altium Designer 23)中的机械层 - 2 中&#xff0c;我们详细介绍了AD中机械层的相关设置&#xff0c;今天结合PCB封装的设计&#xff0c;给出一些实践建议&#xff0c;并分享一个文件模板。 PCB封装设计 PCB&…

SpringBoot 集成 canal

什么是 Canal 阿里巴巴 B2B 公司&#xff0c;因为业务的特性&#xff0c;卖家主要集中在国内&#xff0c;买家主要集中在国外&#xff0c;所以衍生出了同步杭州和美国异地机房的需求&#xff0c;从 2010 年开始&#xff0c;阿里系公司开始逐步的尝试基于数据库的日志解析&#…

Linux常用指令和知识

ls 显示工作目录底下的所有文件/文件夹 使用命令ls, 会直接显示HOME目录下的所有文件 如果不加任何参数,那么ls指定的目录是初始的HOME目录(因为初始的工作目录为HOME目录): 使用ls / 会显示根目录底下的所有文件 如何查看根目录: 三个参数: -a -h -l -a 选项表示all的意思,列…