WebGL矩阵变换

news2024/11/25 7:08:09

目录

变换矩阵:旋转

变换矩阵:平移

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 radians
  var 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 vertices

  var 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.coloradmin.cn/o/937293.html

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

相关文章

【坑】Vue中带有__ob__: Observer的数组无法遍历的问题

控制台可以打印出数据但是渲染不出结构 解决办法&#xff1a; setTimeout(() > {Bus.$emit(shareRes, this.result.filter(item > item.id id)) }, 500)替换 Bus.$emit(shareRes, this.result.filter(item > item.id id))总结 解决和总结 好像和__ob__.Observe无…

电路原理分析2:应急照明灯电路

k是线圈&#xff0c;1-2&#xff08;常开&#xff09;和2-3&#xff08;常闭&#xff09;是2个触点。 1、220v交流电正常供电时&#xff0c;变压器触头位置提供12v的电压&#xff0c;这个时候&#xff0c;v2二极管是导通状态&#xff0c;所以线圈k吸合&#xff0c;这个时候1-2…

基于Java+SpringBoot+Vue前后端分离社区医院管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

利用 IDEA IDE 的轻量编辑模式快速查看和编辑工程外的文本文件

作为程序员, 我们都知道 IDE 的很好用的, 它的文本编辑器功能也非常的强大, 用起来非常便捷. 在长年累月的使用中, 我们也变得对其非常熟悉, 以致于使用起其它简单地轻量级的文本编辑器来, 比如什么记事本, Notepad, UltraEdit 等等呀, 觉得既不方便又不熟悉. 关键是很多的操作…

探索Java的ReentrantLock:实现并发锁的强大力量

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final&#xff0c;一名热爱技术的在校学生。 &#x1f4dd;个人主页&#xff1a;个人主页1 || 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;java系列 &#x1f4e7;如果文章知识点有错误的地方&#xff0c;…

单例模式:保证一个类仅有一个实例

单例模式&#xff1a;保证一个类仅有一个实例 欢迎来到设计模式系列的第二篇文章&#xff01;在上一篇中&#xff0c;我们已经对设计模式有了初步的了解&#xff0c;今天让我们深入研究第一个模式——单例模式。 什么是单例模式&#xff1f; 在软件开发中&#xff0c;我们经…

pyinstaller打包openvino 2021.4.2

打包准备 1. 测试环境准备 conda create -n opinstall python3.7 -y conda activate opinstall pip install openvino2021.4.2 pip install pyinstaller PyCharm新建openvino_install&#xff0c;选择虚拟环境opinstall&#xff0c;编写测试代码 app.py import numpy as n…

mysql基础下

文章目录 1.创建表和管理表1. 一条数据存储的过程2.MySQL中的数据类型3.创建表4.修改表5.重命名表6.删除表7.清空表 2.数据处理1. 插入数据2.更新数据3. 删除数据4. 将查询结果插入到表中 3. 约束1.约束概述2.非空约束3.唯一性约束4. 主键&#xff08;PRIMARY KEY&#xff09;约…

山西电力市场日前价格预测【2023-08-28】

日前价格预测 预测明日&#xff08;2023-08-28&#xff09;山西电力市场全天平均日前电价为319.70元/MWh。其中&#xff0c;最高日前电价为371.80元/MWh&#xff0c;预计出现在19: 15。最低日前电价为278.59元/MWh&#xff0c;预计出现在13: 00。 价差方向预测 1&#xff1a; …

秋招打卡016(0827)

文章目录 前言一、今天学习了什么&#xff1f;二、关于问题的答案1.牛客网面经2.美团后端一面3.动态规划 总结 前言 提示&#xff1a;这里为每天自己的学习内容心情总结&#xff1b; Learn By Doing&#xff0c;Now or Never&#xff0c;Writing is organized thinking. 先多…

基于海洋捕食者算法优化的BP神经网络(预测应用) - 附代码

基于海洋捕食者算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于海洋捕食者算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.海洋捕食者优化BP神经网络2.1 BP神经网络参数设置2.2 海洋捕食者算法应用 4.测试结果&…

企业使用CRM如何统一销售流程管理?

销售流程我们可以理解为&#xff0c;销售人员从寻找潜在客户到最终达成交易的一系列步骤。很多企业通过CRM系统来进行销售流程管理&#xff0c;提高销售效率&#xff0c;实现销售目标。下面我们就来说说&#xff0c;CRM如何进行销售流程管理。 制定统一的销售流程&#xff1a;…

关于 Camera 预览和录像画质不一样的问题分析

1、问题背景 基于之前安卓平台的一个项目&#xff0c;客户有反馈过一个 Camera app 预览的效果&#xff0c;和录像效果不一致的问题。 这里的预览是指打开 Camera app 后直接出图的效果&#xff1b;录像的效果则是指打开 Camera app 开启录像功能&#xff0c;录制一段视频&…

【算法专题突破】双指针 - 快乐数(3)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;202. 快乐数 - 力扣&#xff08;Leetcode&#xff09; 这道题的题目也很容易理解&#xff0c; 看一下题目给的示例就能很容易明白&#xff0c; 但是要注意一个点&#…

一篇带你了解npm的原理

npm 是 JavaScript世界的包管理工具,并且是 Node.js平台的默认包管理工具。通过 npm可以安装、共享、分发代码,管理项目依赖关系。 npm的原理 npm据称成为世界最大的包管理器?原因真的只是用户友好? 一、npm init 用来初始化一个简单的package.json文件。package.json文件…

9.3 功率放大电路的安全运行

在功率放大电路中&#xff0c;功放管既要流过大电流&#xff0c;又要承受高电压。例如&#xff0c;在 OCL 电路中&#xff0c;只有功放管满足式&#xff08;9.2.13&#xff09;所示极限值的要求&#xff0c;电路才能正常工作。因此&#xff0c;所谓功率放大电路的安全运行&…

微服务学习资料

文章目录 参考资料一. 微服务概述1. CAP理论2. BASE理论3. SpringBoot 与 SpringCloud对比 二. 服务注册&#xff1a;Zookeeper,Eureka,Nacos,Consul1. Nacos两种健康检查方式&#xff1f;2. nacos中负责负载均衡底层是如何实现的3. Nacos原理4. 临时实例和持久化(非临时)实例 …

【数据结构与算法篇】手撕八大排序算法之交换排序

​&#x1f47b;内容专栏&#xff1a; 《数据结构与算法篇》 &#x1f428;本文概括&#xff1a;常见交换排序包括冒泡排序与快速排序&#xff0c;本篇讲述冒泡排序与快速排序的思想及实现、复杂度分析。 &#x1f43c;本文作者&#xff1a; 花 蝶 &#x1f438;发布时间&#…

表和Json的相互操作

目录 一、表转Json 1.使用 for json path 2.如何返回单个Json 3.如何给返回的Json增加一个根节点呢 4.如何给返回的Json增加上一个节点 二、对Json基本操作 1.判断给的字符串是否是Json格式 2.从 JSON 字符串中提取标量值 3. 从 JSON 字符串中提取对象或数组 4. 更…

计网-All

路由器的功能与路由表的查看_路由器路由表_傻傻小猪哈哈的博客-CSDN博客路由基础-直连路由、静态路由与动态路由的概念_MikeVane-bb的博客-CSDN博客路由器的功能与路由表的查看_路由器路由表_傻傻小猪哈哈的博客-CSDN博客 直连路由就是路由器直接连了一个网段&#xff0c;他就…