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://xiahunao.cn/news/1619705.html

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

相关文章

Unity中抛物线C#实现的两种方式

在游戏中会出现抛物线的方式&#xff08;如&#xff1a;炮弹轨迹&#xff0c;扔手雷等&#xff09; 下面是实现了两种方式&#xff0c;在学习之路上不停探索。欢迎指出不足&#xff0c;共同进步 实现— 根据开始位置跟结束位置做抛物线 using UnityEngine; using System.Col…

iPhone游戏 Fragger 扔手雷 通关

这个小游戏我觉得根愤怒的小鸟有的一拼,而且它的操作方式又很类似早期的PC网络游戏疯狂坦克. 我在疯狂坦克中的排名是1000以内,所以我是金牌,玩这个游戏又找到了当年的感觉. 以图片记录一该游戏中的关卡,个人觉得它们都是不错的设计.

Unity实用案例之——“吃鸡”手雷弹道模拟

最近吃鸡游戏火啊&#xff0c;至今也吃了好几晚的鸡了&#xff0c;无奈手雷就是丢不准&#xff0c;从窗户丢雷丢几个弹出几个&#xff0c;各种误伤自己人……而别人家的手雷&#xff1a; 一般的游戏里手雷都是盲投&#xff0c;不过一般游戏也不会对弹道有这么精确的要求&…

Unity3D——射击游戏(多地图,多人物,枪支切换,驾车,扔手雷等功能,堪比小型和平精英)

演示1: 演示2: 演示3: 演示4&#xff1a; 源代码和运行程序 链接&#xff1a;https://pan.baidu.com/s/1QZ9UGWPAHO1zRgW5qNCjcw?pwd4m31 提取码&#xff1a;4m31 本款游戏是一个多场景&#xff0c;多角色&#xff0c;多枪支可选的枪战游戏&#xff0c;类似于市面上的小型…

C# textBox1.Text=““与textBox1.Clear()的区别

一、区别 textbox.Text "" 和 textbox.Clear() 都可以用于清空文本框的内容&#xff0c;但它们之间有一些细微的区别。 textbox.Text "": 这种方式会将文本框的 Text 属性直接设置为空字符串。这样会立即清除文本框的内容&#xff0c;并将文本框显示为空…

几个nlp的小任务(生成任务(摘要生成))

几个nlp的小任务生成任务——摘要生成 安装库选择模型加载数据集展示数据集数据预处理 tokenizer注意特殊的 token处理组成预处理函数调用map,对数据集进行预处理微调模型,设置参数设置数据收集器,将处理好的数据喂给模型封装测评方法将参数传给 trainer,开始训练安装库 选…

Vue3(开发h5适配)

在开发移动端的时候需要适配各种机型&#xff0c;有大的&#xff0c;有小的&#xff0c;我们需要一套代码&#xff0c;在不同的分辨率适应各种机型。 因此我们需要设置meta标签 <meta name"viewport" content"widthdevice-width, initial-scale1.0">…

JDK介绍

JDK,JRE和JVM之间的关系 JVM是运行环境&#xff0c;JRE是含运行环境和相关的类库&#xff0c;跟node环境是一个意思 JDK目录介绍 目录名称说明bin该路径下存放了JDK的各种工具命令。javac和java就放在这个目录。conf该路径下存放了JDK的相关配置文件include该路径下存放了一些…

【算法训练-链表】合并两个有序链表、合并K个有序链表

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;首先&#xff0c;链表对应的数据结构在这篇Blog中&#xff1a;【基本数据结构 一】线性数据结构&#xff1a;链表&#xff0c;基于对基础知识的理解来进行题目解答。…

alias命令手册

alias&#xff1a;查看和定义别名 功能描述 alias命令用来设置指令的别名。使用alias时&#xff0c;用户可以使用单引号 ‘ ‘ 将原来的命令引起来&#xff0c;防止特殊字符导致错误。 alias命令的作用只局限于该次登入的操作。若要每次登入都能够使用这些命令别名&#xff0c;…

Ubuntu学习之alias命令

Ubuntu学习之alias命令 1.1 alias功能介绍 当我们经常需要在命令窗键入复杂冗长的命令时&#xff0c;alias就派上用场啦。alias允许用户为命令创建简单的名称或缩写&#xff0c;哪怕这个缩写只有一个字符。即为指令设置别名。 1.2 alias语法 语法&#xff1a;alias [name”va…

linux alias命令路径,Linux alias命令

本文概述 Linux的” alias”命令将shell中的一个字符串替换为另一个字符串。这是一个shell内置命令。它将复杂的命令转换为更简单的命令, 换句话说, 通过将其替换为更简单的命令来创建快捷方式。 在命令行中使用”alias”会创建一个临时的”alias”。临时alias仅在退出外壳程序…

git alias

git alias 其实之前就用过一些 alias&#xff0c;比如说 git reflog show 就是 git log -g --abbrev-commit --prettyoneline 的 alias&#xff0c;一般 alias 可以存储到 git 的 config 文件&#xff0c;repo 等级的在 .git 下&#xff0c;global 的一般在 ~/.gitconfig 或者…

【ubuntu】alias命令

目录 1 alias的作用 2 语法 &#xff08;1&#xff09;简单命令 &#xff08;2&#xff09;多条命令 3 alias永久化 &#xff08;1&#xff09;启动vim编辑器 &#xff08;2&#xff09;进入编辑模式 &#xff08;3&#xff09;退出 &#xff08;4&#xff09;source使…

Elasticsearch:Index alias

现在让我们来谈谈 Elasticsearch 最简单和最有用的功能之一&#xff1a;别名 &#xff08;alias)。为了区分这里 alias 和文章 “Elasticsearch : alias 数据类型”&#xff0c;这里的别名&#xff08;alias&#xff09;指的是 index 的别名。 别名正是他们听起来的样子; 它们是…

Linux alias 的用法

Linux alias 的用法 作者: Sway 1. 啥是alias alias的英文意思是别名. 通俗来说 alias 的概念是让方便你写一段非常非常小的小程序 如 : sway:~$ alias alias lsls --colorauto这里的意思是当你输入 ls 的时候就等同输入 ls --colorauto 但是当我们切换用户的时候 alias …

Nginx中alias与root的区别

目录&#xff1a; 一、区别二、举例说明1三、举例说明2 一、区别 Nginx指定文件路径有两种方式root和alias&#xff0c;这两者的用法区别在于对URI的处理方法不同。 二、举例说明1 alias&#xff1a; location /i/{ alias /usr/local/nginx/html/admin/&#xff1b;} #若…

详解nginx的root与alias

文章目录 1. 结论2. 详解root2.1 基本用法2.2 location的最左匹配原则2.3 index2.4 nginx location解析url工作流程2.5 末尾/ 3. 详解alias3.1 基本用法 4. 特殊情况4.1 alias指定文件4.2 root指定文件 nginx版本: 1.18.0 1. 结论 location命中后 如果是root&#xff0c;会把…

Linux命令之alias

在Linux中&#xff0c;alias命令的功能是设置命令的别名&#xff0c;用以简写命令&#xff0c;提高操作效率。根据参数的不同&#xff0c;该命令可查看已设定的别名&#xff0c;或为命令设置新的别名。对于用户自定义别名&#xff0c;仅当前登录期内有效&#xff1b;也可修改配…