WebGL矩阵变换

目录

变换矩阵:旋转

变换矩阵:平移

4×4的旋转矩阵 

示例代码:

gl.uniformMatrix4fv()规范

 平移:相同的策略

 变换矩阵:缩放


 

变换矩阵:旋转

对于简单的变换,你可以使用数学表达式来实现。但是当情形逐渐变得复杂时,你很快就会发现利用表达式运算实际上相当繁琐。比如,下图显示了一个“旋转后平移”的过程,如果使用数学表达式,我们就需要两种变换的等式叠加,获得一个新的等式,然后在顶点着色器中实现。

 但是如果这样做,每次都需要进行一次新的变换,我们就需要重新求取一个新的等式,然后实现一个新的着色器,这当然很不科学。好在我们可以使用另一个数学工具——变换矩阵来完成这项工作。变换矩阵非常适合操作计算机图形。

如下图所示,矩阵是一个矩形的二维数组,数字按照行(水平方向)和列(垂直方向)排列,数字两侧的方括号表示这些数字是一个整体(一个矩阵)。我们将使用矩阵来表示前面的计算过程。

在解释如何使用变换矩阵来替代数学表达式之前,你需要理解矩阵和矢量的乘法。矢量就是由多个分量组成的对象,比如顶点的坐标(0.0,0.5,1.0)。

矩阵和矢量的乘法可以写成如下等式一的形式(虽然乘号“×”通常被忽略不写,但是为了强调,本书中我们总是明确地将这个符号写出来)。可见,将矩阵(中间)和矢量(右边)相乘,就获得了一个新的矢量(左边)。注意矩阵的乘法不符合交换律,也就是说,A×B和B×A并不相等。

上式中的这个矩阵具有3行3列,因此又被称为3×3矩阵。矩阵右侧是一个由x、y、z组成的矢量(为了与矢量相乘,矢量被写成列的形式,其仍然表示点的坐标)。矢量具有3个分量,因此被称为三维矢量。再次说明,数字两侧的方括号表示这些数字是一个整体(一个矢量)。

在本例中,矩阵与矢量相乘得到的新矢量,其三个分量为x'、y'、z',其值如下等式二所示。注意,只有在矩阵的列数与矢量的行数相等时,才可以将两者相乘。 

        x'=ax+by+cz

        y'=dx+ey+fz

        z'=gx+hy+iz

 现在,为了理解矩阵是如何代替数学表达式的,下面将矩阵等式与数学表达式(如下等式三,即WebGL非矩阵变换_山楂树の的博客-CSDN博客 中的等式R4)进行比较。

等式三:

        x' = x cosβ - y sinβ

        y' = x sinβ + y cosβ

        z' = z

与比较关于x'的表达式进行比较:

        x'=ax+by+cz

        x'=x cos β-y sin β

这样的话,如果设a=cosβ,b=-sinβ,c=0,那么这两个等式就完全相同了。再来看一下y':

        y'=dx+ey+fz

        y'=x sin β+y cos β

这样的话,设d=sinβ,e=cosβ,f=0,两个等式也就完全相同了。最后的关于z'的等式更简单,设g=0,h=0,i=1即可。

 接下来,将这些结果代入到等式一中,得到等式四:

 

这个矩阵就被称为变换矩阵(transformation matrix),因为它将右侧的矢量(x,y,z)“变换”为了左侧的矢量(x',y',z')。上面这个变换矩阵进行的变换是一次旋转,所以这个矩阵又可以被称为旋转矩阵

可以看到,等式三中矩阵的元素都是等式二中的系数。一旦你熟悉这种矩阵表示法,进行变换就变得非常简单了。变换矩阵的概念在三维图形学中非常重要。

变换矩阵在三维计算机图形学中应用得如此广泛,以致于着色器本身就实现了矩阵和矢量相乘的功能。但是,在我们修改着色器代码以采用矩阵之前,先来快速浏览一遍(除了旋转矩阵的)其他几种变换矩阵。

变换矩阵:平移

显然,如果我们使用变换矩阵来表示旋转变换,我们就也应该使用它来表示其他变换,比如平移。比较一下等式二和平移的数学表达式,如下所示:

 这里第二个等式的右侧有常量项Tx,第一个等式中没有,这意味着我们无法通过使用一个3×3的矩阵来表示平移。为了解决这个问题,我们可以使用一个4×4的矩阵,以及具有第4个分量(通常被设为1.0)的矢量。也就是说,我们假设点p的坐标为(x,y,z,1),平移之后的点p'的坐标为(x',y',z',1),如等式等式五所示:

该矩阵的乘法的结果如下等式六:

x'=ax+by+cz +d

y'=ex+fy+gz+h

z'=ix+jy+kz + l

1=mx+ny+oz+ p

根据最后一个式子1=mx+ny+oz+p,很容易求算出系数m=0,n=0,o=0,p=1。这些方程都有常数项d、h、l和p,看上去比较适合平移等式(因为平移等式也有常数项)。平移等式如下所示,我们将它与等式六进行比较:

x'=x+Tx

y'=y+Ty

z'=z+Tz

比较x',可知a=1,b=0,c=0,d=Tx;类似地,比较y',可知e=0,f=1,g=0,h=Ty;比较z',可知i=0,j=0,k=1,l=Tz。这样,你就可以写出表示平移的矩阵,又称为平移矩阵,如等式七所示:

4×4的旋转矩阵 

至此,我们已经成功地创建了一个旋转矩阵和一个平移矩阵,这两个矩阵的作用与此前示例程序中的数学表达式的作用是一样的,那就是计算变换后的顶点坐标。在“先旋转再平移”的情形下,我们需要将两个矩阵组合起来(你应该记得,这也是我们使用矩阵的初衷),然而旋转矩阵(3×3矩阵)与平移矩阵(4×4矩阵)的阶数不同。我们不能把两个阶数不一样的矩阵组合起来,所以得使用某种手段,使这两个矩阵的阶数一致。

将旋转矩阵从一个3×3矩阵转变为一个4×4矩阵,只需要将等式三等式六比较一下即可。 

        x'=x cos β-y sin β

        y'=x sin β+y cos β

        z'=z


        x'=ax+by+cz+d

        y'=ex+fy+gz+h

        z'=ix+jy+kz+l

        1=mx+ny+oz+p

例如,当你通过比较x'=x cosβ- y sinβ与x'=ax+by+cz+d时,可知a=cosβ,b=-sinβ,c=0,d=0。以此类推,求得y'和z'等式中的系数,最终得到4×4的旋转矩阵,如等式八所示: 

这样,我们就可以使用相同阶数(4×4)的矩阵来表示平移和旋转,实现了最初的目标! 

示例代码:

 在创建了4×4的旋转矩阵之后,我们使用旋转矩阵来重写之前的示例程序,令三角形绕Z轴逆时针旋转90度。例如下显示了本例的代码,其运行结果与 WebGL非矩阵变换_山楂树の的博客-CSDN博客 的旋转实例完全一致。

var VSHADER_SOURCE ='attribute vec4 a_Position;\n' +'uniform mat4 u_xformMatrix;\n' +'void main() {\n' +'  gl_Position = u_xformMatrix * a_Position;\n' +'}\n';var FSHADER_SOURCE ='void main() {\n' +'  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +'}\n';var ANGLE = 90.0;
// var tx = 0.5, ty = 0.5, tz=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 radian = Math.PI * ANGLE / 180.0; // Convert to radiansvar cosB = Math.cos(radian), sinB = Math.sin(radian);// 旋转矩阵var xformMatrix = new Float32Array([cosB, sinB, 0.0, 0.0,-sinB, cosB, 0.0, 0.0,0.0,  0.0, 1.0, 0.0,0.0,  0.0, 0.0, 1.0]);// 平移矩阵// var xformMatrix = new Float32Array([//   1.0, 0.0, 0.0, 0.0,//   0.0, 1.0, 0.0, 0.0,//   0.0, 0.0, 1.0, 0.0,//   tx, ty, tz, 1.0// ]);var u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');if (!u_xformMatrix) {console.log('Failed to get the storage location of u_xformMatrix');return;}gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix);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 false;}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;
}

首先来看看顶点着色器:

u_xformMatrix变量表示等式八中的旋转矩阵,a_Position变量表示顶点的坐标(即等式八中右侧的矢量),二者相乘得到变换后的顶点坐标,与等式八中相同。 

示例程序中,你可以在一行代码中完成矢量相加的运算(gl_Position=a_Position+u_Translate)。同样,你也可以在一行代码中完成矩阵与矢量相乘的运算(gl_Position=u_xformMatrix * a_Position)。这时因为着色器内置了常用的矢量和矩阵运算功能,这种强大特性正是专为三维计算机图形学而设计的。

由于变换矩阵是4×4的,GLSL ES需要知道每个变量的类型,所以我们将u_xformMatrix定义为mat4类型。如你所料,mat4类型的变量就是4×4的矩阵。

JavaScript按照等式八计算旋转矩阵,然后将其传给u_xformMatrix。

 这段代码首先计算了90度的正弦值和余弦值这两个值需要被用来构建旋转矩阵;之后创建了Float32Array类型的xformMatrix变量表示旋转矩阵。与GLSL ES不同,JavaScript并没有专门表示矩阵的类型,所以你需要使用类型化数组Float32Array。我们在数组中存储矩阵的每个元素,但问题是:矩阵是二维的,其元素按照行和列进行排列,而数组是一维的,其元素只是排成一行。这里,我们可以按照两种方式在数组中存储矩阵元素:按行主序(row major oder)和按列主序(column major order),如下图所示。

WebGL和OpenGL一样,矩阵元素是按列主序存储在数组中的。比如,图3.27所示的矩阵存储在数组中就是这样的:[a, e, i, m, b, f, j, n, c, g, k, o, d, h, l, p] 。本例中,旋转矩阵也是按照这样的顺序存储在Float32Array类型的数组中的。

最后,我们使用gl.uniformMatrix4fv()函数,将刚刚生成的数组传给u_xformMatrix变量。注意,函数名的最后一个字母是v,表示它可以向着色器传输多个数据值。

gl.uniformMatrix4fv()规范

 平移:相同的策略

如你所见,4×4的矩阵不仅可以用来表示平移,也可以用来表示旋转。不管是平移还是旋转,你都使用如下形式来进行矩阵和矢量的运算以完成变换:<新坐标>=<变换矩阵> * <旧坐标>,比如在着色器中:

这意味着,如果我们改变数组xformMatrix中的元素,使之成为一个平移矩阵,那么就可以实现平移操作,其效果就和之前使用数学表达式进行的平移操作一样。 

因此,修改4x4旋转矩阵代码,将旋转角度修改为与平移相关的变量:

我们还需重写创建矩阵的代码,记住,矩阵是按列主序存储的。虽然xformMatrix现在是一个平移矩阵了,但我们仍使用这个变量名。因为对于着色器而言,旋转矩阵和平移矩阵其实是一回事。最后,你不会用到ANGLE变量,把与旋转相关的代码注释掉: 

 变换矩阵:缩放

最后,我们来学习缩放变换矩阵。仍然假设最初的点p,经过缩放操作之后变成了p'。

假设在三个方向X轴,Y轴,Z轴的缩放因子S 

x'=S× x

y'=Sy y

z'=Sz z

将上式与等式六作比较,可知缩放操作的变换矩阵: 

和之前的例子一样,我们只要将缩放矩阵传给xformMatrix变量,就可以直接使用4x4旋转矩阵中的着色器对三角形进行缩放操作了。下面这个示例程序会将三角形在垂直方向上拉伸到1.5倍,如图所示。 

 

 

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

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

相关文章

【微服务】02-集成事件与MediatR

文章目录 1.集成事件1.1 定义1.2 集成事件工作原理1.3 总结 2.使用RabbitMQ来实现EventBus2.1 RabbitMQ安装2.2 CAP框架实现RabbitMQ2.2.1 CAP框架实现架构2.2.2 CAP框架实现原理 3.MediatR3.1 使用Mediator实现命令查询职责分离模式(CQRS)3.1.1 核心对象 3.2 处理领域事件3.2.…

Java线程 - 详解(1)

一&#xff0c;创建线程 方法一&#xff1a;继承Thread类 class MyThread extends Thread{Overridepublic void run() {System.out.println("线程1");} }public class Test {public static void main(String[] args) {MyThread myThread new MyThread();myThread.…

【C语言】2023.8.27C语言入学考试复盘总结

前言 本篇文章记录的是对于2023年8月27日的 C语言 的入学考试的整理总结 成绩&#xff1a;220/240 题目&#xff1a;9/12 错题整理 首先先对于我没做出来的三道题做一个整理 错题1&#xff1a;7-4 分段函数PLUS 题干 以下是一个二元分段函数&#xff0c;请你根据所给的函…

android系统启动流程之init启动分析

先根据上图来描述下安卓整个系统的启动流程&#xff1a; 当上电时&#xff0c;系统先执行BootRom, 加载引导程序执行。 然后进入bootloader&#xff0c;在安卓系统中基本上这个bootloader是uboot, 通过uboot引导启动内核&#xff0c;此时运行在kernel空间&#xff0c;这时的i…

基于全新电脑环境安装pytorch的GPU版本

前言&#xff1a; 距离第一次安装深度学习的GPU环境已经过去了4年多&#xff08;当时TensorFlow特别麻烦&#xff09;&#xff0c;现在发现安装pytorch的GPU版本还是很简单方便的&#xff0c;流程记录如下。 安装步骤&#xff1a; 步骤一&#xff1a;官网下载Anaconda Free…

研磨设计模式day11观察者模式

目录 场景 代码示例 定义 观察者模式的优缺点 本质 何时选用 简单变型-区别对待观察者 场景 我是一家报社&#xff0c;每当我发布一个新的报纸时&#xff0c;所有订阅我家报社的读者都可以接收到 代码示例 报纸对象 package day11观察者模式;import java.util.Observ…

Python+TinyPNG熊猫网站自动化的压缩图片

前言 本篇在讲什么 PythonTinyPNG自动化处理图片 本篇需要什么 对Python语法有简单认知 依赖Python2.7环境 依赖TinyPNG工具 本篇的特色 具有全流程的图文教学 重实践&#xff0c;轻理论&#xff0c;快速上手 提供全流程的源码内容 ★提高阅读体验★ &#x1f449;…

AI + Milvus:将时尚应用搭建进行到底

在上一篇文章中&#xff0c;我们学习了如何利用人工智能技术&#xff08;例如开源 AI 向量数据库 Milvus 和 Hugging Face 模型&#xff09;寻找与自己穿搭风格相似的明星。在这篇文章中&#xff0c;我们将进一步介绍如何通过对上篇文章中的项目代码稍作修改&#xff0c;获得更…

[管理与领导-53]:IT基层管理者 - 8项核心技能 - 8 - 持续改进

前言&#xff1a; 管理者存在的价值就是制定目标&#xff0c;即目标管理、通过团队&#xff08;他人&#xff09;拿到结果。 要想通过他人拿到结果&#xff1a; &#xff08;1&#xff09;目标&#xff1a;制定符合SMART原则的符合业务需求的目标&#xff0c;团队跳一跳就可以…

Microsoft正在将Python引入Excel

Excel和Python这两个世界正在碰撞&#xff0c;这要归功于Microsoft的新集成&#xff0c;以促进数据分析和可视化 Microsoft正在将流行的编程语言Python引入Excel。该功能的公共预览版现已推出&#xff0c;允许Excel用户操作和分析来自Python的数据。 “您可以使用 Python 绘图…

Git向远程仓库与推送以及拉取远程仓库

理解分布式版本控制系统 1.中央服务器 我们⽬前所说的所有内容&#xff08;⼯作区&#xff0c;暂存区&#xff0c;版本库等等&#xff09;&#xff0c;都是在本地也就是在你的笔记本或者计算机上。⽽我们的 Git 其实是分布式版本控制系统&#xff01;什么意思呢? 那我们多人…

JavaWeb 速通JQuery

目录 一、JQuery快速入门 1.基本介绍 : 2.入门案例 : 二、JQuery对象 1.基本介绍 : 2.DOM对象 --> JQuery对象 : 3.JQuery对象 --> DOM对象 : 三、JQuery选择器 1.简介 : 2.基本选择器 : 3.层次选择器 : 4.过滤选择器 : 4.1 基础过滤选择器 4.2 内容过滤选择…

Vue中ElementUI结合transform使用时,发现弹框定位不准确问题

在近期开发中&#xff0c;需要将1920*1080放到更大像素大屏上演示&#xff0c;所以需要使用到transform来对页面进行缩放&#xff0c;但是此时发现弹框定位出错问题&#xff0c;无法准备定位到实际位置。 查看element-ui官方文档无果后&#xff0c;打算更换新的框架进行开发&am…

国产系统下开发QT程序总结

国产系统下开发QT程序总结 1. 国产系统简介 开发国产系统客户端的过程中&#xff0c;会出现兼容性问题。以下介绍Kylin和UOS环境下开发QT程序&#xff0c; 首先麒麟和统信这两个系统基于Ubuntu开发的。所以在Ubuntu开发理论上在国产系统上也能运行。芯片架构又分为amd,arm,mi…

飞凌嵌入式受邀参加「RISC-V芯片应用交流会」并发表主题演讲

8月23日下午&#xff0c;在第三届RISC-V中国峰会现场&#xff0c;由赛昉科技主办的「RISC-V芯片应用交流会」吸引了诸多行业伙伴和专家到场参与。此次会议旨在分享赛昉科技高性能RISC-V芯片的软件生态、应用产品、解决方案等全面进展&#xff0c;共同探讨RISC-V芯片的未来发展和…

javaee spring 自动注入,如果满足条件的类有多个如何区别

如图IDrinkDao有两个实现类 方法一 方法二 Resource(name“对象名”) Resource(name"oracleDrinkDao") private IDrinkDao drinkDao;

Java --- 异常处理

目录 一、什么是异常 二、异常抛出机制 三、如何对待异常 四、 Java异常体系 4.1、Throwable 4.2、Error 4.2、Exception 4.2.1、编译时异常 4.2.2、运行时期异常 五、异常处理 5.1、捕获异常&#xff08;try-catch&#xff09; 5.1.2、catch中异常处理方式 …

你对SPA单页面的理解,它的优缺点分别是什么?如何实现SPA应用呢?

一、什么是SPA SPA&#xff08;single-page application&#xff09;&#xff0c;翻译过来就是单页应用SPA是一种网络应用程序或网站的模型&#xff0c;它通过动态重写当前页面来与用户交互&#xff0c;这种方法避免了页面之间切换打断用户体验在单页应用中&#xff0c;所有必…

TCP的三次握手 四次挥手以及TCP的11种状态

三次握手流程&#xff1a; 客户端给服务端发送数据时&#xff0c;数据包中带有一个头&#xff0c;这个头就是前几十个字节&#xff0c;就是下面这张图。从源端口号&#xff0c;目的端口号&#xff0c;一直到序列号&#xff0c;直到Options。第一个包会将这前十几个字节中的SYN置…

12. 完整模型训练套路

12.1 CIFAR 10 model 网络模型 ① 下面用 CIFAR 10 model网络来完成分类问题&#xff0c;网络模型如下图所示。 12.2 DataLoader加载数据集 import torchvision from torch import nn from torch.utils.data import DataLoader# 准备数据集 train_data torchvision.dataset…