webgl变换矩阵理论详解

news2024/7/6 19:08:09

文章目录

  • 前言
  • 矩阵运算
    • 矩阵加减
    • 矩阵数乘
    • 矩阵乘矩阵
    • 矩阵转置
    • 逆矩阵
    • 正交矩阵
  • 矩阵变换的一般规则
    • 行主序和列主序
    • 行向量和列向量
    • 复杂变换时的顺序
  • 变换矩阵进行图形变换
    • uniform传递矩阵
    • 平移
    • 缩放
    • 旋转
  • 组合变换实例
  • 总结


前言

在webgl中将图形进行平移、旋转、缩放等操作时可以在着色器中使用数学表达式来操作,但是这样并不是最好的方式,当进行的变换比较复杂,如“旋转后平移再缩放”这样的场景,每次都要先重新计算表达式,然后在着色器中去更改它,这样很不方便,因此,出现了另一种数学手段——变换矩阵,变换矩阵非常适合处理计算机图形。本文对变换矩阵的理论进行讲解。


矩阵运算

矩阵是按照行列排列的一系列数值得的集合,由 m × n 个数排成的m行n列的数表称为m行n列的矩阵,简称m × n 矩阵,如下图。当 m 和 n 相同则称这个方阵为 m 阶矩阵(方阵)。

在这里插入图片描述

矩阵加减

应该注意的是只有同型矩阵之间才可以进行加减,即为行数和列数都必须一样的两个矩阵才可以进行加减运算。矩阵加减运算的规则是将将两个矩阵对应位置上的元素相加减:

在这里插入图片描述

矩阵数乘

矩阵和标量相乘,返回一个新矩阵,新矩阵的各个元素等于原矩阵各个元素与标量的乘积:
在这里插入图片描述

矩阵乘矩阵

两个矩阵的乘法仅当第一个矩阵A的列数和另一个矩阵B的行数相等时才能定义。如A是m×n矩阵和B是n×p矩阵,它们的乘积C是一个m×p矩阵,规则如下:

在这里插入图片描述

矩阵相乘满足分配律和结合律,但不满足交换律:

在这里插入图片描述
在这里插入图片描述

矩阵转置

把矩阵A的行和列互相交换所产生的矩阵称为A的转置矩阵 A T A^{T} AT
在这里插入图片描述

逆矩阵

设A是一个n阶矩阵,若存在另一个n阶矩阵B,使得: AB=BA=E ,则称方阵A可逆,并称方阵B是A的逆矩阵,单位矩阵是一个对角线上的元素都为1的方阵,其余元素为 0,比如下面就是一个 3 阶单位矩阵:

在这里插入图片描述

  • 不是所有的矩阵都存在逆矩阵,逆矩阵首先必须是方阵,而且存在与其相乘结果为单位矩阵的矩阵
  • 在图形学中,将进行矩阵变换的坐标再乘以该变换矩阵的逆矩阵,可以将变换后的坐标再还原回去,实现撤销的效果

正交矩阵

假设有一个方阵M,当且仅当 M 与其转置矩阵M^T的乘积等于单位矩阵时,称其为正交矩阵。即:
在这里插入图片描述
即:
在这里插入图片描述
此时方阵M为一个正交矩阵,矩阵正交需要满足以下两个条件:

  • 矩阵的每一行都是单位向量
  • 矩阵的某一行和其他行向量相互垂直,点积为 0

两个向量a = [a1, a2,…, an]和b = [b1, b2,…, bn]的点积定义为:a·b=a1*b1 + a2*b2 + … + an*bn

矩阵变换的一般规则

行主序和列主序

在JavaScript中,没有专门用来表示矩阵的类型,需要使用类型化数组new Float32Array来存储,但是数组是一维的,矩阵是二维的,将二维的矩阵存储到一维数组中有两个方式:行主序列主序
在这里插入图片描述
webgl按照列主序的规则将矩阵存储于数组,对于上面这个矩阵,按照列主序存到数组中一个是这样的:

 new Float32Array([ a,  e,  i,  m, b,  f,  j,  n,  c,  g,  k,  o, d,  h,  l,  p,]) 

一般在程序中这样书写,看起来更加舒服 👇

new Float32Array([
    a,  e,  i,  m,
    b,  f,  j,  n,
    c,  g,  k,  o,
    d,  h,  l,  p,
   ]) 

行向量和列向量

  • 如果是行向量,向量要放在左侧相乘。
  • 如果是列向量,向量要放在右侧相乘。

着色器中vec[i]类型的数据都是在乘号的右侧,由此推断他们都是列向量

复杂变换时的顺序

在多个矩阵变换时,不同的相乘顺序会导致不同的结果,按照理解成本最低的准则,通常是第一步缩放,之后旋转,最后平移

P’ = <平移矩阵> * <旋转矩阵> * <缩放矩阵> * P

变换矩阵进行图形变换

uniform传递矩阵

uniform的使用与前面文章使用的区别不大,创建uniform变量并绑定至着色器 => 向顶点着色器传输变量 => 顶点着色器接收并使用unifrom变量。这里有一个新出现的方法 gl.uniformMatrix4fv(),该方法用来将从javascript中获取的数据传值指定的mat4类型变量

gl.uniformMatrix4fv(loaction, transpose, array) 将 4*4的矩阵array分配给由location指定的uniform变量

  • location:uniform变量存储位置
  • transpose:false
  • array: Float32Array类型化数组,必须是列主序

transpose表示是否开启转置矩阵,webgl中没有提供该方法,因此必须设置为false

平移

平移的数学原理很容易理解,在x y z的基础上分别加上一个平移量即可:
在这里插入图片描述

  • x’ = x + Tx
  • y’ = y + Ty
  • z’ = z + Tz

但是这里有一个问题:根据前面矩阵相乘的理论,n维矩阵和n维向量相乘,不能实现向量和一个常量进行加减的操作:

在这里插入图片描述

为了解决这个问题,结合之前齐次坐标系的知识,可以使用一个4维的矩阵,同时将点坐标也扩充为vec4,设原始点坐标为(x,y,z,1),平移后点坐标为(x’,y’,z’,1):
在这里插入图片描述
换为表达式的形式则为:
在这里插入图片描述

比较x’,可得a=1,b=0,c=0,d=Tx;比较y’,可得f=1,e=0,g=0,h=Ty;比较z’,可得k=1,i=0,j=0,l=Tz;由此可得平移矩阵的表达式:
在这里插入图片描述
完整示例如下,initShader在之前声明过。基于变换矩阵实现平移动画效果。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>webgl</title>
  <script src="./lib.js"></script>
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <style>
    body {
      margin: 0;
      padding: 0;
    }
    canvas {
      margin: 50px 30px;
      width: 500px;
      height: 500px;
      background-color: antiquewhite;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>
  <script>

    /** @type {HTMLCanvasElement} */
    //------------------------------------------------------创建画布
    // 获取canvas元素对象
    let canvas = document.getElementById('canvas');

    // 获取webgl绘图上下文
    const gl = canvas.getContext('webgl');
    if (!gl) {
      throw new Error('WebGL not supported');
    }

    canvas.width = 500;
    canvas.height = 500;
    gl.viewport(0, 0, canvas.width, canvas.height)

    const vertex = `
			attribute vec4 aPosition;
      uniform mat4 mat;
			void main() {
				gl_Position = mat * aPosition;
			}
		`
    const fragment = `
			precision highp float;
			void main(){
				gl_FragColor =vec4(1.0,1.0,0.0,1.0);
			}
		`

    // 创建program
    const program = initShader(gl, vertex, fragment)
    // 获取attribute变量的数据存储位置
    const aPosition = gl.getAttribLocation(program, 'aPosition');
    const mat = gl.getUniformLocation(program, 'mat');
    // 创建缓冲区对象
    const buffer = gl.createBuffer();
    // 绑定缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    // 传入的数据
    const vertices = new Float32Array([
      -0.5, -0.5, // 顶点着色器补全为(0.0,0.5,0.0,1.0)
      0.5, -0.5,
      0.0, 0.5
    ])
    // 开辟空间并写入数据
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
    // 缓冲区对象分配给attribute变量
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0)
    // 开启attribue缓冲区变量
    gl.enableVertexAttribArray(aPosition)

    function getTranslateMatrix(x=0, y=0, z=0){
      return  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,
      x,   y,   z,   1,
    ])
    } 

    let translate = 0

    function animate() {
      console.log('requestAnimationFrame')
      translate += 0.01;
      if (translate > 1.5) {
        translate = 0
      }
      const matT = getTranslateMatrix(translate)
      gl.uniformMatrix4fv(mat, false, matT)

      // 开始绘制
      gl.drawArrays(gl.TRIANGLES, 0, 3)
      const timer = requestAnimationFrame(() => animate())
    }
    
    // 销毁定时器
    function removeAnimate(){
      cancelAnimationFrame(timer)
    }
    
    animate()

  </script>
</body>

</html>

在这里插入图片描述

缩放

缩放变换原理也很容易理解:
在这里插入图片描述

  • x’ = Sx * x
  • y’ = Sy * y
  • z’ = Sz * z

变换矩阵如下,像这种进行转置之后仍然不变的矩阵,称为对称矩阵,缩放矩阵就是一个对阵矩阵。
在这里插入图片描述

旋转

在前一篇文章中对旋转矩阵有过推导,这里直接晒出结论
在这里插入图片描述
假设原本齐次坐标系上一点P的坐标是(x,y,z),经逆时针旋转角度β后移动至P’(x’, y’, z’),r表示原点到旋转前的P的距离,α是X轴旋转到P的角度。则P的坐标可以表示为:

  • x’ = x * cos β - y * sin β
  • y’ = x * sin β + y * cos β
  • z’ =z

换成矩阵运算则为:
在这里插入图片描述

组合变换实例

结合上述理论,我们可以在着色器中传入平移、旋转和缩放矩阵,计算新的坐标,实现动画效果。但是在javascript中提前计算好最终的变换矩阵,然后只穿一个uniform变量是更好的方式,threejs提供了这样的矩阵工具,但是webgl没有提供矩阵计算的工具函数,有兴趣的话可以自己编写。
示例和代码如下:
在这里插入图片描述

<body>
  <canvas id="canvas"></canvas>
  <script>

    /** @type {HTMLCanvasElement} */
    //------------------------------------------------------创建画布
    // 获取canvas元素对象
    let canvas = document.getElementById('canvas');

    // 获取webgl绘图上下文
    const gl = canvas.getContext('webgl');
    if (!gl) {
      throw new Error('WebGL not supported');
    }

    canvas.width = 500;
    canvas.height = 500;
    gl.viewport(0, 0, canvas.width, canvas.height)

    const vertex = `
			attribute vec4 aPosition;
      uniform mat4 matTranslate;
      uniform mat4 matScale;
      uniform mat4 matRotate;
			void main() {
				gl_Position = matTranslate *  matRotate * matScale * aPosition;
			}
		`
    const fragment = `
			precision highp float;
			void main(){
				gl_FragColor =vec4(1.0,1.0,0.0,1.0);
			}
		`

    // 创建program
    const program = initShader(gl, vertex, fragment)
    // 获取attribute变量的数据存储位置
    const aPosition = gl.getAttribLocation(program, 'aPosition');
    const matTranslate = gl.getUniformLocation(program, 'matTranslate');
    const matScale = gl.getUniformLocation(program, 'matScale');
    const matRotate = gl.getUniformLocation(program, 'matRotate');
    // 创建缓冲区对象
    const buffer = gl.createBuffer();
    // 绑定缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    // 传入的数据
    const vertices = new Float32Array([
      -0.2, -0.2, // 顶点着色器补全为(0.0,0.5,0.0,1.0)
      0.2, -0.2,
      0.0, 0.2
    ])
    // 开辟空间并写入数据
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
    // 缓冲区对象分配给attribute变量
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0)
    // 开启attribue缓冲区变量
    gl.enableVertexAttribArray(aPosition)

    function getTranslateMatrix(x=0, y=0, z=0){
      return  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,
      x,   y,   z,   1,
    ])
    } 

    function getScaleMatrix(S = 1){
      return  new Float32Array([
      S, 0.0, 0.0, 0.0,
      0.0, S, 0.0, 0.0,
      0.0, 0.0, S, 0.0,
      0.0, 0.0, 0.0, 1
    ])
    } 

    function getRotateMatrix(deg){
      return  new Float32Array([
      Math.cos(deg), Math.sin(deg), 0.0, 0.0,
      -Math.sin(deg), Math.cos(deg), 0.0, 0.0,
      0.0, 0.0, 1.0, 0.0,
      0.0, 0.0, 0.0,  1.0,
    ])
    } 

    let translate = 0
    let scale = 1
    let deg = 0

    function animate() {
      console.log('requestAnimationFrame')
      translate += 0.01;
      scale += 0.003;
      deg += 0.01;
      if (translate > 1.5) {
        translate = 0
      }
      if (scale > 2) {
        scale = 1
      }

      const matT = getTranslateMatrix(translate)
      const matS = getScaleMatrix(scale)
      const matR = getRotateMatrix(deg)

      gl.uniformMatrix4fv(matTranslate, false, matT)
      gl.uniformMatrix4fv(matScale, false, matS)
      gl.uniformMatrix4fv(matRotate, false, matR)

      // 开始绘制
      gl.drawArrays(gl.TRIANGLES, 0, 3)
      const timer = requestAnimationFrame(() => animate())
    }
    
    // 销毁定时器
    function removeAnimate(){
      cancelAnimationFrame(timer)
    }
    
    animate()

  </script>
</body>

总结

  • 矩阵运算
    加减、矩阵数乘、矩阵乘矩阵、矩阵转置、逆矩阵、正交矩阵
  • 矩阵变换的一般规则
    行主序和列主序、行向量和列向量、复杂变换时的顺序
  • 变换矩阵进行图形变换
    uniform传递矩阵、平移、缩放、旋转
  • 组合变换实例

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

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

相关文章

11.1 使用关联容器

文章目录关联容器的类型使用map使用set关联容器中元素是按关键字保存和访问的&#xff0c;支持高效关键字查找和访问。顺序容器中元素是按他们在容器中的位置保存访问的。关联容器有两个主要类型&#xff1a;set和map。 set&#xff1a;每个元素包含一个关键字&#xff0c;想知…

OPC实践:通过 python-docx 读取 docx 文档

概述 本文记录下列命令执行的过程&#xff0c;通过对过程中的关键步骤进行记录&#xff0c;掌握 python-docx 库中 opc 与 parts 模块的源码、以及加深对 OPC 的理解。 import docx# fp 为 docx 文件路径&#xff0c; docx 包含一个 hello 字符串、一张 jepg 图片及一个表格…

<Python的列表和元组>——《Python》

目录 1.列表 1.1 列表的概念 1.2 创建列表 1.3 访问下标 1.4 切片操作 1.5 遍历列表元素 1.6 新增元素 1.7 查找元素 1.8 删除元素 1.9 连接列表 2. 元组 1.列表 1.1 列表的概念 编程中, 经常需要使用变量, 来保存/表示数据. 如果代码中需要表示的数据个数比较少,…

初识 Bootstrap(前端开发框架)

初识 Bootstrap&#xff08;前端开发框架&#xff09;参考Bootstrap特点获取目录结构jQuery 与 Popper准备工作包含 jQuery 与 Poppermetabox-sizing基本模板无注释版本注释版本参考 项目描述Bootstrap 官方教程https://getbootstrap.net/docs/getting-started/introduction/百…

字节青训前端笔记 | HTTP 使用指南

本节课介绍 Http 协议的基本定义和特点&#xff0c;在此基础上&#xff0c;对于 Http 协议的发展历程及报文结构展开进一步分析。 从输入字符串到打开网页 输入地址浏览器处理输入信息浏览器发请求到达服务器服务器返回信息浏览器读取响应信息浏览器渲染页面加载完成 什么是…

KVM虚拟化简介 | 初识

目录 1、kvm架构 2、架构解析 3、kvm和qemu的作用 1、kvm架构 2、架构解析 从rhel6开始使用&#xff0c;红帽公司直接把KVM的模块做成了内核的一部分。xen用在rhel6之前的企业版中默认内核不支持&#xff0c;需要重新安装带xen功能的内核KVM 针对运行在x86 硬件上的、驻留在内…

配置 Git 连接 GitHub

文章目录0.安装 Git1.注册 GitHub 账号2.配置 Git 的用户名和邮箱3.为本机生成 SSH 密钥对4.将公钥拷贝到 GitHub 上5.测试0.安装 Git Git 官网链接&#xff1a;https://git-scm.com/ Git 官网下载链接&#xff1a;https://git-scm.com/downloads 1.注册 GitHub 账号 GitHu…

蓝桥杯STM32G431RBT6学习——定时器PWM输出

蓝桥杯STM32G431RBT6学习——定时器PWM输出 前言 PWM波输出作为定时器的一个常用功能&#xff0c;也属于高频的考点。从数据手册的定时器解析可以了解到&#xff08;上篇描述&#xff09;&#xff1a;除了基本定时器&#xff08;TIM6、7&#xff09;外&#xff0c;其他所有定…

全国产网管型工业交换机的几种管理方式

全国产网管型工业交换机按其字面上的意思&#xff0c;一是全国产化&#xff08;工业交换机&#xff09;&#xff0c;就是交换机内部95%以上元器件的国内生产制造&#xff0c;重要的硬件芯片&#xff0c;比如交换机芯片、管理芯片、接口芯片等必须是国内厂商在国内研发、生产、制…

学习记录664@项目管理之项目进度管理

什么是项目进度管理 项目进度管理包括为管理项目按时完成所需的7个过程&#xff0c;具体为: 规划进度管理过程一一制定政策、程序和文档以管理项目进度。定义活动过程一一识别和记录为完成项目可交付成果而需采取的具体行动。排列活动顺序过程一一识别和记录项目活动之间的关…

【Kubernetes 企业项目实战】04、基于 K8s 构建 EFK+logstash+kafka 日志平台(下)

目录 一、安装收集日志组件 Fluentd 二、kibana 可视化展示查询 k8s 容器日志 三、测试 efk 收集生产环境业务 pod 日志 四、基于 EFKlogstashkafka 构建高吞吐量的日志平台 4.1 部署 fluentd 4.2 接入 kafka 4.3 配置 logstash 4.4 启动 logstash 本篇文章所用到的资料…

对象的比较

Java中基本类型间的元素比较&#xff0c;可以直接通过">"、"<"、""等符号判断大小&#xff0c;也可使用compareTo比较大小或者equals判断是否相等&#xff0c;作为引用类型的String类不可以使用">"、"<"比较大小…

2023最新 - 谷歌学术文献Bibtex批量获取脚本

首先&#xff0c;自行解决网络访问问题&#xff0c;保证能访问到谷歌学术&#xff0c;否则下面可免看 第一步&#xff1a;安装 selenium python 安装 selenium pip install selenium 第二步&#xff1a;安装 Chrome 浏览器 http://chorm.com.cn/ 第三步&#xff1a;根据 …

Linux应用基础——控制服务与守护进程

一、systemd基本介绍 1.作用 systemd守护进程管理Linux的启动&#xff0c;一般包括服务启动和服务管理&#xff0c;它可以在系统引导时以及运行中的系统上激活系统资源、服务器守护进程和其他进程 2.守护进程 守护进程是执行各种任务的后台等待或运行的进程&#xff0c;一般…

day18|235. 二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点

235. 二叉搜索树的最近公共祖先 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表示为一个结点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08…

【容器技术——Docker基本使用】

文章目录docker1 概述1.1 是什么1.2 相关资源2 使用2.1 镜像2.1.1 拉取镜像2.2.2 列出镜像2.2.3 删除镜像2.2 容器2.2.1 运行容器2.2.2 查看容器2.2.3 启动和关闭容器2.2.4 删除容器2.3 制作镜像2.4 Docker 仓库2.4.1 注册登录2.4.2 推送镜像2.5 dockerfile2.5.1 构建镜像2.5.2…

关于CIS移植的一些基本概念

1. 摄像头sensor 的原理 定时脉冲生成器会生成clock&#xff0c;用于访问image sensor 阵列中的行&#xff0c;预充电&#xff0c;并且按顺序采样像素阵列中的所有行。在一个行的预充电和采样的时间段里&#xff0c;像素的电荷量会随着曝光时间而逐渐减少。这就是快门结构中的曝…

Python采集周边烤肉店数据,康康哪一家最好吃?

人生苦短&#xff0c;我用Python 这不是天气开始突然大范围降温了吗&#xff1f; 降温就要吃烤肉啊 滋辣滋辣的声音特别好听&#xff5e; 放假吃烤肉真的特别快乐~ 天冷了&#xff0c;逛街…… 天冷了&#xff0c;吃烤肉…… 天冷了&#xff0c;喝奶茶…… 有温度的冬天&a…

p83出现的问题(空指针异常)原因及解决方案

我的GitHub&#xff1a;Powerveil GitHub我的Gitee&#xff1a;Powercs12 (powercs12) - Gitee.com皮卡丘每天学Java相关知识&#xff1a;Mybatis Plus、Spring Security本质原因&#xff1a;我们自己将Article字段的update字段做了自动填充导致自动填充时无法获取当前用户(程…

pytorch Conv2d令人迷惑的几个参数

本篇仅用来记录nn.Conv2d()中容易令人不解的几个参数 1. nn.Conv2d() 的输出维度的计算 相信大家都看过官网给出的计算方式: 直接套公式即可, 但需要注意的是, 最终计算的结果要向下取整. 2. dilation 官方解释为: dilation (int or tuple, optional) – Spacing between k…