WebGL非矩阵变换

目录

平移

示例代码:

齐次坐标矢量的最后一个分量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.0gl.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 verticesvar 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> elementvar 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 verticesvar 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.mzph.cn/news/60022.shtml

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

相关文章

下岗吧,Excel

ChatGPT的诞生使Excel公式变得过时。通过使用 ChatGPT 的代码解释器你可以做到&#xff1a; 分析数据创建图表 这就像用自然语言与电子表格交谈一样。我将向大家展示如何使用 ChatGPT 执行此操作并将结果导出为Excel格式&#xff1a; 作为示例&#xff0c;我将分析并创建美国…

阿里云效和阿里在线idea使用

阿里云效 https://flow.aliyun.com/all?page1 阿里在线idea&#xff1a;https://ide.aliyun.com/ 在云效中创建的项目可以在在线idea 打开 运行中的项目 设置ssh 设置以后可以使用云效率的代码构建来构建代码 设置 添加自有云或者体验5h

元素居中的方法总结

目录 垂直居中 行内元素垂直居中 单行文本垂直居中 1.line-height: 200px; 多行文本垂直居中 1.tablevertical-align:middle 块级元素垂直居中 1.display: flex;align-items: center; 2.使用position top margin-top 水平居中 行内元素水平居中 1.text-align:cente…

如何让你的jupyter notebook 排版得像Word(Markdown和网页文件写法)

案例背景 很多时候我们在jupyter notebook里面的写代码&#xff0c;画图&#xff0c;但是文字分析什么的写在里面纯文本不好看&#xff0c;需要进行排版&#xff0c;那么就得用markdown的写法&#xff0c;如何还想居中或者更花里胡哨的字体&#xff0c;那就得要网页文件的一些…

阿里云将关停代销业务

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 阿里云自从逐渐分拆独立之后&#xff0c;做了很多调整。最近它又做了一个大动作&#xff1a;据DoNews消息&#xff0c;阿里云将会在今年9月30日之前&#xff0c;全面关停代销业务。 这件事实际上…

mysql profiling profiles profile

要想优化一条 Query&#xff0c;我们就需要清楚的知道这条 Query 的性能瓶颈到底在哪里&#xff0c;是消耗的 CPU计算太多&#xff0c;还是需要的的 IO 操作太多&#xff1f;要想能够清楚的了解这些信息&#xff0c;在 MySQL 5.0 和 MySQL 5.1正式版中已经可以非常容易做到了&a…

剑走偏锋:非传统问题在面试中的应对策略

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

滑动窗口系列4-Leetcode322题零钱兑换-限制张数-暴力递归到动态规划再到滑动窗口

这个题目是Leecode322的变种&#xff0c;322原题如下&#xff1a; 我们这里的变化是把硬币变成可以重复的&#xff0c;并且只有coins数组中给出的这么多的金币&#xff0c;也就是说有数量限制&#xff1a; package dataStructure.leecode.practice;import java.util.Arrays; i…

用NeRFMeshing精确提取NeRF网络中的3D网格

准确的 3D 场景和对象重建对于机器人、摄影测量和 AR/VR 等各种应用至关重要。 NeRF 在合成新颖视图方面取得了成功&#xff0c;但在准确表示底层几何方面存在不足。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 我们已经看到了最新的进展&#xff0c;例如 NVIDIA 的…

【c++】VC编译出的版本,发布版本如何使用

目录 使用release类型进行发布 应用程序无法正常启动 0xc000007b 版本对应 vcruntime140d 应用版本 参考文章 使用release类型进行发布 应用程序无法正常启动 0xc000007b "应用程序无法正常启动 0xc000007b" 错误通常是一个 Windows 应用程序错误&#xf…

【网络】多路转接——poll | epoll

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《网络》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 书接上文五种IO模型 | select。 poll | epoll &#x1f367;poll&#x1f9c1;认识接口&#x1f9c1;简…

多目标应用:基于多目标向日葵优化算法(MOSFO)的微电网多目标优化调度MATLAB

一、微网系统运行优化模型 参考文献&#xff1a; [1]李兴莘,张靖,何宇,等.基于改进粒子群算法的微电网多目标优化调度[J].电力科学与工程, 2021, 37(3):7 二、多目标向日葵优化算法 多目标向日葵优化算法&#xff08;Multi-objective sunflower optimization&#xff0c;MOS…

idea远程debug调试

背景 有时候我们线上/测试环境出现了问题&#xff0c;我们本地跑却无法复现问题&#xff0c;使用idea的远程debug功能可以很好的解决该问题 配置 远程debug的服务&#xff0c;我们使用Springboot项目为例(SpringCloud作为微服务项目我们可以可以使用本地注册到远程项目&…

Android中级——消息机制

消息机制 概念ThreadLocalMessageQueueLooperHandlerrunOnUiThread() 概念 MessageQueue&#xff1a;采用单链表的方法存储消息列表Looper&#xff1a;查询MessageQueue是否有新消息&#xff0c;有则处理&#xff0c;无则等待ThreadLocal&#xff1a;用于Handler获取当前线程的…

NSS [羊城杯 2020]easyser

NSS [羊城杯 2020]easyser 开题。很容易让人觉得环境坏了。 不要慌&#xff0c;无从下手时。看源码、扫目录、抓包。一套操作下来&#xff0c;发现几个可以下手的路由。 /index.php /robots.txt 访问 /star1.php&#xff0c;一说到百度&#xff0c;就猜测是否存在SSRF。 源码中…

【ES6】JavaScript 中的数组方法reduce

reduce() 是一个 JavaScript 中的数组方法&#xff0c;它会对数组的每个元素执行一个提供的 reducer 函数&#xff0c;将其减少到一个单一的值。 这是 reduce() 的基本用法&#xff1a; //(method) Array<number>.reduce(callbackfn: (previousValue: number, currentV…

<C++> STL_list

1.list的介绍 list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指向 其前一个元素和后一个元素。list与…

Quickstart: MinIO for Linux

单节点部署教程 1.安装Minio服务端 //wget下载二进制文件 wget https://dl.min.io/server/minio/release/linux-amd64/minio //赋予权限 chmod x minio //将minio可执行文件移入usr/local/bin目录下&#xff0c;使得minio可以全局执行 sudo mv minio /usr/local/bin/ 2.启动Mi…

多线程学习之解决线程同步的实现方法

一、卖票的多线程实现 需求&#xff1a;共有100张票&#xff0c;而它有3个窗口卖票&#xff0c;请设计一个程序模拟该电影院卖票 代码实现&#xff1a; /*** Author&#xff1a;kkoneone11* name&#xff1a;SellTicket1* Date&#xff1a;2023/8/26 11:32*/ public class S…

电子电路学习笔记之NCP304LSQ37T1G ——超低电流电压检测器

超低电流电压检测器是一种专门用于检测极小电流值的设备。它们常用于电子元件或电路中&#xff0c;用于监测电流的存在和程度。这些检测器通常具有高灵敏度和高精度&#xff0c;能够测量微安级别或更小的电流。 超低电流电压检测器的应用领域广泛&#xff0c;例如电池管理系统…