WebGL非矩阵变换

news2024/11/17 13:28:22

目录

平移

示例代码:

齐次坐标矢量的最后一个分量w 

旋转

p的坐标,可得等式 R1:

使用r、α、β来表示点p'的坐标,可得等式 R2:

利用三角函数两角和公式,可得等式 R3:

最后,将p的坐标等式代入上式,消除r和α,可得等式 R4:

三角函数两角和公式 

示例代码:


平移

考虑一下,为了平移一个三角形,你需要对它的每一个顶点做怎样的操作?答案是,你需要对顶点坐标的每个分量(x和y),加上三角形在对应轴(如X轴或Y轴)上平移的距离。比如,将点p(x,y,z)平移到p' (x',y',z'),在X轴、Y轴、Z轴三个方向上平移的距离分别为Tx,Ty,Tz,其中Tz为0。

那么在坐标的对应分量上,直接加上这些T值,就可以确定p'的坐标了

x'=x+Tx

y'=y+Ty

z'=z+Tz

如图所示: 

 我们只需要着色器中为顶点坐标的每个分量加上一个常量就可以实现上面的等式。显然,这是一个逐顶点操作(per-vertex operation)而非逐片元操作,上述修改应当发生在顶点着色器,而不是片元着色器中。

xyz 各移动 0.5 0.5 0

示例代码:

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'uniform vec4 u_Translation;\n' +
  'void main() {\n' +
  '  gl_Position = a_Position + u_Translation;\n' +
  '}\n';

var FSHADER_SOURCE =
  'void main() {\n' +
  '  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
  '}\n';

// x y z 各移动 0.5 0.5 0
var Tx = 0.5, Ty = 0.5, Tz = 0.0;

function main() {
  var canvas = document.getElementById('webgl');

  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }

  var n = initVertexBuffers(gl);
  if (n < 0) {
    console.log('Failed to set the positions of the vertices');
    return;
  }

  var u_Translation = gl.getUniformLocation(gl.program, 'u_Translation');
  if (!u_Translation) {
    console.log('Failed to get the storage location of u_Translation');
    return;
  }
  gl.uniform4f(u_Translation, Tx, Ty, Tz, 0.0); // 这里第四个分量必须是 0.0

  gl.clearColor(0, 0, 0, 1);

  gl.clear(gl.COLOR_BUFFER_BIT);

  gl.drawArrays(gl.TRIANGLES, 0, n);
}

function initVertexBuffers(gl) {
  var vertices = new Float32Array([
    0, 0.5,   -0.5, -0.5,   0.5, -0.5
  ]);
  var n = 3; // The number of vertices

  var vertexBuffer = gl.createBuffer();
  if (!vertexBuffer) {
    console.log('Failed to create the buffer object');
    return -1;
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return -1;
  }
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(a_Position);

  return n;
}

首先,main()函数中定义了等式3.1中三角形在各轴方向上的平移距离: 

因为Tx、Ty、Tz对于所有顶点来说是固定(一致)的,所以我们使用uniform变量u_Translation来表示三角形的平移距离。首先,获取uniform变量的存储位置:

 然后将数据传给着色器:

 

注意,gl.uniform4f()函数需接收齐次坐标,所以我们把最后一个参数被设为0.0。这么做的具体原因将在稍后讨论。

现在来看一下修改后的顶点着色器:我们新定义了uniform变量u_Translation,用来接收了三角形在各轴方向上的平移距离。该变量的类型是vec4,这样它就可以与vec4类型的顶点坐标a_Position直接相加,然后赋值给同样是vec4类型的gl_Position。记住,GLSL ES中的赋值操作只能发生在相同类型的变量之间。 

 在做完准备工作之后,我们就直奔主题:在顶点着色器中,按照上述等式,为a_Position变量的每个分量(x,y,z)加上u_Translation变量中对应方向的平移距离(Tx,Ty,Tz),并赋值给gl_Position。

因为a_Position和u_Translation变量都是vec4类型的,所以你可以直接使用+号,两个的矢量的对应分量会被同时相加,如下图所示。方便的矢量相加运算是GLSL ES提供的特性之一。

齐次坐标矢量的最后一个分量w 

最后,来解释一下齐次坐标矢量的最后一个分量w。gl_Position是齐次坐标,具有4个分量。如果齐次坐标的最后一个分量是1.0,那么它的前三个分量就可以表示一个点的三维坐标。在本例中,如上图所示,平移后点坐标第4分量w1+w2必须是1.0 (因为点的位置坐标平移之后还是一个点位置坐标),而w1是1.0(它是平移前点坐标第4分量),所以平移矢量本身的第4分量w2只能是0.0,这就是为什么gl.uniform4f()的最后一个参数为0.0。 

最后,调用gl.drawArrays(gl.TRIANGLES,0,n)执行顶点着色器,每次执行都会进行以下3步:

1.将顶点坐标传给a_Position;

2.向a_Position加上u_Translation;

3.结果赋值给gl_Position。

一旦顶点着色器执行完毕,目的就达到了:每个顶点在同一个方向上平移了相同的距离,整个图形(本例中为三角形)也就被平移了。

旋转

旋转比平移稍微复杂一些,因为描述一个旋转本身就比描述一个平移复杂。为了描述一个旋转,你必须指明:

● 旋转轴(图形将围绕旋转轴旋转)。

● 旋转方向(方向:顺时针或逆时针)。

● 旋转角度(图形旋转经过的角度)。

本文这样来表述旋转操作:绕Z轴,逆时针旋转了β角度。这种表述方式同样适用于绕X轴和Y轴的情况。

在旋转中,关于“逆时针”的约定是:如果β是正值,观察者在Z轴正半轴某处,视线沿着Z轴负方向进行观察,那么看到的物体就是逆时针旋转的,如下图所示。这种情况又可称作正旋转(positive rotation)。我们也可以使用右手来确认旋转方向(正如右手坐标系一样):右手握拳,大拇指伸直并使其指向旋转轴的正方向,那么右手其余几个手指就指明了旋转的方向,因此正旋转又可以称为右手法则旋转(right-hand-rule rotation)。

上面我们计算了平移的数学表达式,现在来看旋转的数学表达式。根据下图,假设点p(x,y,z)旋转β角度之后变为了点p'(x',y',z'):首先旋转是绕Z轴进行的,所以z坐标不会变,可以直接忽略;然后,x坐标和y坐标的情况有一些复杂。

上图中,r是从原点到点p的距离,而α是X轴旋转到点p的角度。用这两个变量计算出点p的坐标,转换等式如下。

p的坐标,可得等式 R1:

        x = r cosα

        y = r sinα


使用r、α、β来表示点p'的坐标,可得等式 R2:

        x' = r cos(α + β)

        y' = r sin(α + β)


利用三角函数两角和公式,可得等式 R3:

        x' = r (cosα cosβ - sinα sinβ)

        y' = r (sinα cosβ + cosα sinβ)


最后,将p的坐标等式代入上式,消除r和α,可得等式 R4:

        x' = x cosβ - y sinβ

        y' = x sinβ + y cosβ

        z' = z

三角函数两角和公式 

  • sin(a+b) = sina cosb + cosa sinb
  • sin(a-b) = sina cosb - cosa sinb
  • cos(a+b) = cos cosb - sina sinb
  • cos(a-b) = cosa cosb + sina sinb

我们可以把sinβ和cosβ的值传给顶点着色器,然后在着色器中根据等式R4计算旋转后的点坐标,就可以实现旋转这个点的效果了。使用JavaScript内置的Math对象的sin()和cos()方法来进行三角函数运算。

下图显示了下面示例代码的运行结果,可见,三角形绕Z轴逆时针旋转了90度。

示例代码:

旋转代码,其结构与上面平移很像,只不过顶点着色器中进行的是旋转而不是平移操作。片元着色器和平移中完全相同,我们将它省略了。此外,为了配合顶点着色器的改动,main()函数也有几处改动。注意顶点着色器中实现了等式R4。 

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'uniform float u_CosB, u_SinB;\n' +
  'void main() {\n' +
  /* 下面两行为拓展,旋转的同时再平移 */
  // '  gl_Position.x = (a_Position.x * u_CosB - a_Position.y * u_SinB) + u_move.x;\n' +
  // '  gl_Position.y = (a_Position.x * u_SinB + a_Position.y * u_CosB) + u_move.y;\n' +
  '  gl_Position.x = a_Position.x * u_CosB - a_Position.y * u_SinB;\n' +
  '  gl_Position.y = a_Position.x * u_SinB + a_Position.y * u_CosB;\n' +
  '  gl_Position.z = a_Position.z;\n' +
  '  gl_Position.w = 1.0;\n' +
  '}\n';

var FSHADER_SOURCE =
  'void main() {\n' +
  '  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
  '}\n';

// 旋转90度
var ANGLE = 90.0; 

function main() {
  // Retrieve <canvas> element
  var canvas = document.getElementById('webgl');

  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }

  var n = initVertexBuffers(gl);
  if (n < 0) {
    console.log('Failed to set the positions of the vertices');
    return;
  }

  var radian = Math.PI * ANGLE / 180.0; // 转换为弧度
  var cosB = Math.cos(radian);
  var sinB = Math.sin(radian);

  var u_CosB = gl.getUniformLocation(gl.program, 'u_CosB');
  var u_SinB = gl.getUniformLocation(gl.program, 'u_SinB');
  // var u_move = gl.getUniformLocation(gl.program, 'u_move');
  if (!u_CosB || !u_SinB) {
    console.log('Failed to get the storage location of u_CosB or u_SinB');
    return;
  }
  gl.uniform1f(u_CosB, cosB);
  gl.uniform1f(u_SinB, sinB);
  // gl.uniform3f(u_move, 0.5, 0.5, 0);

  gl.clearColor(0, 0, 0, 1);

  gl.clear(gl.COLOR_BUFFER_BIT);

  gl.drawArrays(gl.TRIANGLES, 0, n);
}

function initVertexBuffers(gl) {
  var vertices = new Float32Array([
    0, 0.5,   -0.5, -0.5,   0.5, -0.5
  ]);
  var n = 3; // The number of vertices

  var vertexBuffer = gl.createBuffer();
  if (!vertexBuffer) {
    console.log('Failed to create the buffer object');
    return -1;
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return -1;
  }
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(a_Position);

  return n;
}

由于目的是为了将三角形旋转90度,我们得事先计算90度的正弦值和余弦值。在JavaScript中算出这两个值,再传给顶点着色器的两个uniform变量。

你也可以将旋转的角度传入顶点着色器,并在着色器中计算正弦值和余弦值。但是,实际上所有顶点旋转的角度都是一样的,在JavaScript中算好正弦值和余弦值,然后再传递进去,只需要计算一次,效率更高。

上面进行平移变换时,齐次坐标的x、y、z、w分量是作为整体进行加法运算的;而进行旋转变换时,为了计算等式R4,需要单独访问a_Position的每个分量。我们使用点操作符“.”来访问分量,如a_Position.x、a_Position.y或a_Position.z(如下图所示)。

同样,也可以用点操作符向数组的分量赋值访问gl_Position分量,并写入变换后的点坐标分量值。比如,按照等式3.3进行计算x'=xcosβ-ysinβ并赋值给gl_Position的x分量:

 相似地,可以如下计算y':

根据等式R4,还需要将z原封不动地赋给z',以及将最后一个w分量设为1.0 

现在来看一下JavaScript代码中的main()函数:它和平移代码中几乎完全一样,唯一的不同之处就是,本例向顶点着色器传入了cosβ和sinβ值(而非平移距离Tx等)。我们使用JavaScript内置的Math.sin()和Math.cos()函数来计算β的正弦和余弦值。但是,这两个方法必须接受弧度制(而不是角度制)的参数,所以我们还得先把β值从角度制转为弧度制:将角度值90乘以π然后除以180,访问Math.PI可以获得π的值。 

在程序中,我们首先计算旋转角β的弧度值,然后计算sinβ和cosβ的值,最后将结果传入顶点着色器。 

如果你觉得示例程序的实现(使用两个uniform变量分别接收cosβ和sinβ)效率不是最优的,你也可以将这两个值作为一个数组传入着色器。比如,你可以这样定义uniform变量:

然后这样传入cosβ和sinβ的值:

这样,在顶点着色器中,就可以使用u_CosBSinB.x和u_CosBSinB.y来获取cosβ和sinβ的值。 

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

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

相关文章

8.27周报

文章目录 前言论文阅读摘要介绍模型算法 总结 前言 本周学习了GAN论文《Generative Adversarial Nets》&#xff0c;了解GAN主要由两部分组成&#xff1a;生成器和判别器&#xff0c;知道生成器G和判别器D的作用及原理&#xff0c;相比于其他的生成模型&#xff0c;了解GAN的优…

【java】【项目实战】[外卖六]套餐管理业务开发

目录 一、新增套餐 1 需求分析 2 数据模型 3 代码实现 3.1 实体类SetmealDish 3.2 SetmealDto 3.3 SetmealDishMapper 3.4 SetmealDishService 3.5 SetmealDishServiceImpl 3.6 SetmealController 3.7 DishController 3.8 SetmealService 3.9 SetmealServiceImp…

4.21 用了 TCP 协议,数据一定不会丢吗?

目录 数据包的发送流程: 建立连接时丢包 流量控制丢包 网卡丢包 RingBuffer过小导致丢包 网卡性能不足 接收缓冲区丢包 两端之间的网络丢包 ping命令查看丢包&#xff1a; mtr命令&#xff1a; 发生丢包了怎么办 用了TCP协议就一定不会丢包吗​编辑 这类丢包问题怎…

Mysql--技术文档--基本概念--《世界上最流行的关系型数据库之一》

官方网址 MySQL 阿丹&#xff1a; 作为关系型数据库管理的老大哥&#xff0c;一个合格的程序员多多少少一定要了解mysql库。 官方解释 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c;属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管…

浙大MBA冲刺上岸经验分享:紧急备战的大龄考生

前些天我在朋友圈分享了一组我们前段时间在浙大开学前同学聚会的照片。结果&#xff0c;许多朋友都开始询问与浙大有关的情况&#xff0c;并且他们最关心的问题集中在以下几点&#xff1a;我去年备考花费了多少时间&#xff1f;要不要报班学习&#xff1f;现在开始备考还有机会…

postman-使用Postman的模拟服务来模拟(mock)后端数据,完成前端模拟API调用

最近项目上比较忙&#xff0c;任务多时间紧&#xff0c;导致后端开发任务繁多&#xff0c;无法及时开发完毕&#xff0c;但是前端同学已经把对应功能开发完成&#xff0c;需要进行前后端联调来验证API及一些交互问题&#xff1b;这不能因为后端的进度来影响前端的工作完成情况&…

c++ 判断基类指针指向的真实对象类型

在 c 面向对象使用中&#xff0c;我们常常会定义一个基类类型的指针&#xff0c;在运行过程中&#xff0c;这个指针可能指向一个基类类型的对象&#xff0c;也可能指向的是其子类类型的对象&#xff0c;那现在问题来了&#xff0c;我们如何去判断这个指针到底执行了一个什么类型…

FreeMarker使用

说明&#xff1a;FreeMake可以通过设置一个模板&#xff0c;使用一些语法规则&#xff0c;可根据返回的VO数据填充到这个模板中&#xff0c;生成一个静态。这个技术&#xff0c;在项目中可以实现如预览页面的功能&#xff0c;将查询完成的VO数据按照这个模板填充&#xff0c;生…

Redis 10 大数据类型

1. which 10 1. redis字符串 2. redis 列表 3. redis哈希表 4. redis集合 5. redis有序集合 6. redis地理空间 7. redis基数统计 8. redis位图 9. redis位域 10. redis流 2. 获取redis常见操作指令 官网英文&#xff1a;https://redis.io/commands 官网中文&#xff1a;https:/…

python函数学习

def add(num1,num2):resultnum1num2print(f"函数add输出的结果是{result}")return result resultadd(int(num1), int(num2)) print(f"调用def add(num1,num2):这个函数最终返回的结果是: {result}")# 函数返回值 ②无返回值&#xff08;也就是说是返回值类…

python下载bilibili视频,下载合集,下载选集

一. 内容简介 bilibili视频下载&#xff0c;下载合集&#xff0c;下载选集 二. 软件环境 2.1vsCode 2.2Anaconda version: conda 22.9.0 2.3代码 链接&#xff1a;https://pan.baidu.com/s/1tO8xSmaqqoTxHI9P_UkDBw?pwd1234 提取码&#xff1a;1234 三.主要流程 3.1 …

Linux系统:CentOS 7 CA证书服务器部署

目录 一、理论 1.CA认证中心 2.CA证书服务器部署 二、实验 1. CA证书服务器部署 一、理论 1.CA认证中心 &#xff08;1&#xff09;概念 CA &#xff1a;CertificateAuthority的缩写&#xff0c;通常翻译成认证权威或者认证中心&#xff0c;主要用途是为用户发放数字证…

C语言-内存分布(STM32内存分析)

C/C内存分布 一、内存组成二、静态区域文本段 &#xff08;Text / 只读区域 RO&#xff09;已初始化读写数据段&#xff08;RW data -- Initialized Data Segment&#xff09;未初始化数据段&#xff08;BSS -- Block Started by Symbol&#xff09; 三、动态区域堆&#xff08…

如何复刻稚晖君的ctrl-FOC-lite

一、simpleFOC版本工程使用clion重新打开为“Cmake”工程&#xff1a; 1、我删除了simpleFOC版本工程文件夹下的cmake-build-debug、.idea文件夹&#xff1b; 2、使用clion重新打开为“Cmake”工程&#xff0c;配置均按照稚晖君的教程进行的配置。 3、使用stm32cubeMX6.5版本重…

数仓--------简单了解

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

[Android]JNI的基础知识

目录 1.什么是JNI 2.配置JNI开发环境NDK 3.创建Native C类型的项目 4. 了解CMakeLists.txt 文件 5.了解native-lib.cpp 文件 6.在 Android 的 MainActivity 中调用 native-lib.cpp 中实现的本地方法 1.什么是JNI JNI&#xff08;Java Native Interface&#xff09;是一…

SciencePlots 基本语法及特点

文章目录 简介安装 LaTeXSciencePlots 绘图示例 简介 用户有时需要根据期刊的配图绘制要求进行诸如字体、刻度轴、轴脊、图例等图层属性的定制化修改&#xff0c;耗时的同时也会容易导致用户忽略一些图层细节要求。 SciencePlots 作为一个专门用于科研论文绘图的第三方拓展工…

设计模式第九讲:常见重构技巧 - 去除不必要的!=

设计模式第九讲&#xff1a;常见重构技巧 - 去除不必要的! 项目中会存在大量判空代码&#xff0c;多么丑陋繁冗&#xff01;如何避免这种情况&#xff1f;我们是否滥用了判空呢&#xff1f;本文是设计模式第九讲&#xff0c;讲解常见重构技巧&#xff1a;去除不必要的! 文章目录…

机房安全之道:构筑坚固的网络防线

引言&#xff1a; 在数字化时代&#xff0c;机房成为了许多组织和企业的核心基础设施&#xff0c;承载着重要的数据和应用。然而&#xff0c;随着网络攻击日益猖獗&#xff0c;机房的安全性显得尤为重要。本文将深入探讨如何构建坚固的网络防线&#xff0c;保护机房免受攻击的方…