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.rhkb.cn/news/108558.html

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

相关文章

无涯教程-进程 - 组会话控制

在本章中&#xff0c;我们将熟悉进程组&#xff0c;会话和作业控制。 进程组(Process Groups ) - 进程组是一个或多个进程的集合&#xff0c;一个进程组由一个或多个共享相同进程组标识符(PGID)的进程组成。 会话(Sessions) - 它是各种进程组的集合。…

QT学习笔记-开发环境编译Qt MySql数据库驱动与交叉编译Qt MySql数据库驱动

QT学习笔记-开发环境编译Qt MySql数据库驱动与交叉编译Qt MySql数据库驱动 0、背景1、基本环境2、开发环境编译Qt MySql数据库驱动2.1 依赖说明2.2 MySQL驱动编译过程 3、交叉编译Qt MySql数据库驱动3.1 依赖说明3.3.1 如何在交叉编译服务器上找到mysql.h及相关头文件3.3.2 如果…

保姆级 Keras 实现 Faster R-CNN 十

保姆级 Keras 实现 Faster R-CNN 十 一. 建议区域矩形二. 定义 ProposalLyaer1. __init__函数2. build 函数3. call 函数3.1 生成 anchor_box3.2 找出 anchor 处最大分数, 最大分数对应的 anchor_box 和修正参数3. 3 修正 anchor_box3.4 完成 call 函数 4. compute_output_shap…

二级MySQL(九)——表格数据处理练习

在Mysql中&#xff0c;可以用INSERT或【REPLACE】语句&#xff0c;向数据库中已一个已有的表中插入一行或多行记录。 在Mysql中&#xff0c;可以用【DELETE】或【TRUNCATE】语句删除表中的所有记录。 在Mysql中&#xff0c;可以用【UPDATE】语句来修改数据表中的记录。 为了完…

windows系统 Fooocus 图片生成模型 ,4-6GB显存即可玩,27S/p

安装步骤: 1.下载程序代码框架,大小2GB ,下载 ​​​​​​https://github.com/lllyasviel/Fooocus/releases/download/1.0.35/Fooocus_win64_1-1-1035.7z 2.下载模型文件sd_xl_base_1.0_0.9vae.safetensors ,大小6GBhttps://huggingface.co/stabilityai/stable-diffusion-x…

[完美解决]Vue项目运行时出现this[kHandle] = new _Hash(algorithm, xofLen)

vue项目运行bug解决办法 一、问题内容二、问题出现的原因三、解决方法1、方法一(推荐)2、方法二(可以解决&#xff0c;但不太推荐) 一、问题内容 在github寻找一些vue项目clone到本地时候&#xff0c;npm i没有问题&#xff0c;但是npm run serve 或者npm run dev的时候会出现…

【mq】如何保证消息可靠性

文章目录 mq由哪几部分组成rocketmqkafka 为什么需要这几部分nameserver/zookeeper可靠性 broker可靠性 生产者消费者 mq由哪几部分组成 rocketmq kafka 这里先不讨论Kafka Raft模式 比较一下&#xff0c;kafka的结构和rocketmq的机构基本上一样&#xff0c;都需要一个注册…

计算机竞赛 基于GRU的 电影评论情感分析 - python 深度学习 情感分类

文章目录 1 前言1.1 项目介绍 2 情感分类介绍3 数据集4 实现4.1 数据预处理4.2 构建网络4.3 训练模型4.4 模型评估4.5 模型预测 5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于GRU的 电影评论情感分析 该项目较为新颖&#xff0c;适合作为竞…

Postman中参数区别及使用说明

一、Params与Body 二者区别在于请求参数在http协议中位置不一样。Params 它会将参数放入url中以&#xff1f;区分以&拼接Body则是将请求参数放在请求体中 后端接受数据: 二、body中不同格式 2.1 multipart/form-data key - value 格式输入&#xff0c;主要特点是可以上…

(三)行为模式:5、中介者模式(Mediator Pattern)(C++示例)

目录 1、中介者模式&#xff08;Mediator Pattern&#xff09;含义 2、中介者模式的UML图学习 3、中介者模式的应用场景 4、中介者模式的优缺点 &#xff08;1&#xff09;优点 &#xff08;2&#xff09;缺点 5、C实现中介者模式的实例 1、中介者模式&#xff08;Media…

基于unity的轻量配置工具开发

工具结构&#xff1a;针对每张表格生成一个表格类&#xff0c;其中默认包含一个list和字典类型参数记录表格数据&#xff0c;初始化项目时将list中的数据转为按id索引的dictionary&#xff0c;用于访问数据。额外包含一个同名Temp后缀的类&#xff0c;记录表格的字段、备注等信…

Leetcode:238. 除自身以外数组的乘积【题解超详细】

纯C语言实现&#xff08;小白也能看明白&#xff09; 题目 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数…

Apache Poi 实现Excel多级联动下拉框

由于最近做的功能&#xff0c;需要将接口返回的数据列表&#xff0c;输出到excel中&#xff0c;以供后续导入&#xff0c;且网上现有的封装&#xff0c;使用起来都较为麻烦&#xff0c;故参考已有做法封装了工具类。 使用apache poi实现excel联动下拉框思路 创建隐藏单元格&a…

计算机组成原理学习笔记-精简复习版

一、计算机系统概述 计算机系统硬件软件 计算机硬件的发展&#xff1a; 第一代计算机&#xff1a;(使用电子管)第二代计算机&#xff1a;(使用晶体管)第三代计算机&#xff1a;(使用较小规模的集成电路)第四代计算机&#xff1a;(使用较大规模的集成电路) 冯诺依曼体系结构…

指针C语言

1指针方式 1.int a,*p&a; 2. int a;int *p&a;特点&#xff1a; 1.指针变量与类型无关&#xff0c;在TC占2字节&#xff0c;在VC下占四字节 2.指针变量的引用 1.直接引用 2.间接引用 注意*的运算对象必须为地址 *p1; //相当于取p指针指向的值然后&#xff0b;1 int …

keepalived+lvs(DR)

目录 一&#xff0c;作用 二&#xff0c;调度器配置 1&#xff0c;安装keepalived 2&#xff0c; 安装ipvsadm 3&#xff0c; 配置keepalived 4. 查看lvs节点状态 5&#xff0c; web节点配置 1.1 调整ARP参数 1.2 配置虚拟IP地址 1.3添加回环路由 1.4安装nginx并写…

机器学习实战之模型的解释性:Scikit-Learn的SHAP和LIME库详解

引言&#xff1a;机器学习模型的“黑箱”困境 机器学习模型的崛起让我们惊叹不已&#xff01;不论是预测房价、识别图片中的猫狗&#xff0c;还是推荐给你喜欢的音乐&#xff0c;这些模型都表现得非常出色。但是&#xff0c;有没有想过&#xff0c;这些模型到底是如何做出这些决…

echarts 甘特图一组显示多组数据

<template><el-button type"primary" click"addlin">添加线</el-button><el-button type"success" click"addArea">添加区域</el-button><div ref"echart" id"echart" class&qu…

自学设计模式(类图、设计原则、单例模式 - 饿汉/懒汉)

设计模式需要用到面向对象的三大特性——封装、继承、多态&#xff08;同名函数具有不同的状态&#xff09; UML类图 eg.—— 描述类之间的关系&#xff08;设计程序之间画类图&#xff09; : public; #: protected; -: private; 下划线: static 属性名:类型&#xff08;默认值…

Linux(基础IO、文件权限、Makefile)

目录 1、man 手册 1.1 汉化 1.2 具体使用 2、文件权限 2.1 权限理解 2.2 文件详细信息查询 2.3 权限更改 3、常用函数接口 3.1 open 3.2 read 3.3 write 3.4 close 3.5 函数使用示例 4、make与Makefile 4.1 make 与 Makefile区别 4.2 Makefile的编写 5、vim简…