10. WebGPU 旋转变换

单位圆是半径为 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。

原文地址

猜你喜欢

转载自blog.csdn.net/xuejianxinokok/article/details/131183645
10.
今日推荐