WebGL图形编程实战【3】:矩阵操控 × 从二维到三维的跨越

上一篇文章:WebGL图形编程实战【2】:动态着色 × 纹理贴图技术揭秘
仓库地址:github…、gitee…

矩阵操控

矩阵变换

回到前面关于平移缩放、旋转的例子当中,我们是通过改变传递进去的xy的值来改变的。

在进行基础变换的时候,涉及到多个变量且变化频率高,在实际的webgl应用开发过程中,其复杂程度更令人发指,故引入了数学工具—矩阵。(具有规律性的二维数组)计算机实际上是一个固执的老顽童,它最喜欢有规律性质的东西,所以这样一拍即合,计算机技术与数学理论达成情人关系(webgl内置了矩阵系统)。本堂课的内容就是将变换过程转换成矩阵进行表示。

矩阵动画推演

下面先展示了在数学方面上对xyz轴的数据进行相对应的变化,而后是在矩阵层面上变化的值

平移
x1 = x + Tx;
y1 = y + Ty;
z1 = z + Tz;

在这里插入图片描述

旋转
x1 = x * cos(angle) - y * sin(angle);
y1 = y * cos(angle) + x * sin(angle);

在这里插入图片描述

缩放
x1 = x * Sx;
y1 = y * Sy;
z1 = z * Sz;

在这里插入图片描述

矩阵运算案例

加(减)法

只有同型矩阵之间才可以进行加(减)法运算,将两个矩阵相同位置的元相加即可,m行n列的两个矩阵相加(减)后得到一个新的m行n列矩阵,例如

1,2,3,4  +  3,4,5,6  =  4,6,8,10
2,3,4,5  +  2,3,4,5  =  4,6,8,10
数乘

数乘即将矩阵乘以一个常量,矩阵中的每个元都与这个常量相乘,例如

1,2,3  *  3  =  3,6,9
乘法

两个矩阵的乘法仅当第一个矩阵的列数和另一个矩阵的行数相等时才能定义

1,2,3  *  3,4  =  1*3+2*2+3*3  1*4+2*3+3*4  = 16 22
2,3,4  *  2,3  =  2*3+3*2+4*3  2*4+3*3+4*4  = 24 333,4  =  

glMatrix 常用API

glMatrix API 官网…

补充:在使用glMatrix-0.9.6.min.js和npm上的glMatrix.js,API是有一定区别的,下面是用最新的glMatrix

创建矩阵

返回类型是Float32Array,矩阵的元素个数是16,也就是一个4x4的矩阵。

const matrix = mat4.create();

投影矩阵

生成具有给定边界的透视投影矩阵。far传递null/undefined/no值将生成无限投影矩阵。

mat4.perspective(out, fovy, aspect, near, far);
mat4.perspective(matrix, 45, 4 / 3, 1, 100);
名称类型描述
outmat4mat4截头体矩阵将被写入
fovynumber垂直视场(弧度)
aspectnumber宽高比。通常视口宽度/高度
nearnumber截头体的近界
farnumber截头体的远边界,可以为null或Infinity

矩阵相乘

将两个mat 4相乘,参数一为目标矩阵,参数二三为要相乘的矩阵。

const matrix = mat4.create();
const matrix1 = mat4.create();
let target = [];
mat4.multiply(target, matrix, matrix1);

单位矩阵

将一个矩阵设置为单位矩阵。单位矩阵是一个4x4的矩阵,其元素值都为0,除了主对角线元素值都为1。

mat4.identity(matrix);

矩阵变化(平移、旋转、缩放)

平移缩放旋转都传递了两个矩阵参数,其中第一个参数是目标矩阵,第二个参数是变化矩阵(不做变换就和第一个传一样的值)。第三个参数是变化参数(平移的xyz值、缩放的xyz值、旋转的弧度和xyz值)。

mat4.translate(matrix, matrix, [10, 10, 10]);
mat4.scale(matrix, matrix, [1, 2, 1]);
mat4.rotate(matrix, matrix, 45, [0, 0, 1]);

WebGL+矩阵变化

整体逻辑如下mermaid图

数据组装
uniformMatrix4fv
变化值
赋值给shader
initWebGL
initShader
initBuffer
render
draw

修改着色器,添加一个中间矩阵,然后把中间矩阵传递给着色器。版本为0.9.6

const vertexString = `attribute vec4 a_position;uniform mat4 u_formMatrix;void main(){gl_Position = u_formMatrix * a_position;gl_PointSize = 40.0;}`;

在js当中通过glMatrix.js进行矩阵变换,然后用webGL的uniformMatrix4fv方法传递给着色器。

uniformMatrix4fv 为 uniform 变量指定矩阵值

  • 参数一:是指定待修改 uniform 变量的存储位置
  • 参数二:指定是否转置矩阵
  • 参数三:序列值
function animate() {const middleMat4 = mat4.create();mat4.identity(middleMat4);mat4.translate(middleMat4, [0, 0.5, 0]);mat4.rotate(middleMat4, 0.5 * Math.PI, [0, 0, 1]);mat4.scale(middleMat4, [0.5, 0.5, 0.5]);let uniformMatrix = webGL.getUniformLocation(program, 'u_formMatrix');webGL.uniformMatrix4fv(uniformMatrix, false, middleMat4);
}

案例:WebGL时钟效果

和上面webGL+矩阵变化的代码一样,在顶点着色器当中传入一个u_formMatrix用来计算,随后在initBuffer当中重新设置顶点坐标用来绘制三角带,如下

let triangleArray = [0, -0.1, 0, 1.0,0, 0.4, 0, 1.0,0.01, 0.4, 0, 1.0,0.01, -0.1, 0, 1.0
];
webGL.drawArrays(webGL.TRIANGLE_FAN, 0, 4);

之后就是矩阵变换的代码,用rotate选择的方法去改变矩阵,以秒钟为例,那就是一秒钟走2*Math.PI弧度除以60,这样一分钟60秒刚好一圈,那么代码就是这样实现的。

得到当前秒
计算弧度
初始化矩阵
单元化
旋转矩阵
传递矩阵
const second = new Date().getSeconds();
const rotate = 2 * Math.PI / 60 * second;
const middleMat4 = mat4.create();
mat4.identity(middleMat4);
mat4.rotate(middleMat4, -rotate, [0, 0, 1]);
let uniformMatrix = webGL.getUniformLocation(program, 'u_formMatrix');
webGL.uniformMatrix4fv(uniformMatrix, false, middleMat4);

随后分钟小时的代码就一样了,分钟和秒钟的计算是一样的,时针就是将60换成12即可。最后就是添加一个setInterval每隔一秒调用一次。注意:在这里绘制的时候,只需要在秒针绘制的时机先clear一遍,分针和时针的时候直接调用drawArray绘制即可,不用再次clear

在这里插入图片描述

完整代码地址:https://github.com/lizuoqun/visualThree/blob/main/webGL/animate/clockTriangle.html

三维世界

视点 & 视线

观察者的位置就是视点,从视点出发,观察者能看到的就是视线。

调整视口观察三维对象

三角形
分层级
设置颜色
lookAt
改变视口观察

前面的案例研究了xy轴的二维平面对象,而三维就是给z设置了对应的值,而改变观察的方向就能看到不同的结果。这里以三角形绘制为例,参考前面的代码实现,先绘制三个三角形,并且修改其z轴的值,在三个不同的平面上

let triangleArray = [0.0, 0.5, -0.4, 1.0,-0.5, -0.5, -0.4, 1.0,0.5, -0.5, -0.4, 1.0,0.5, 0.4, -0.2, 1.0,-0.5, 0.4, -0.2, 1.0,0.0, -0.6, -0.2, 1.0,0.0, 0.4, 0.0, 1,-0.4, -0.4, 0.0, 1,0.4, -0.4, 0.0, 1
];

给每一个层级的三角形设置一下不同的颜色,上面可以区分有三个层级,分别位于z的-0.4、-0.2、0.0三个位置上,修改这个数组对象,就代表着一个点对象有八个数值,分别是x、y、z、1、r、g、b、a,将颜色值和点绑定在一起

let triangleArray = [0.0, 0.5, -0.4, 1.0, 0.4, 1.0, 0.4, 1,-0.5, -0.5, -0.4, 1.0, 0.4, 1.0, 0.4, 1,0.5, -0.5, -0.4, 1.0, 0.4, 1.0, 0.4, 1,0.5, 0.4, -0.2, 1.0, 1.0, 0.4, 0.4, 1,-0.5, 0.4, -0.2, 1.0, 1.0, 0.4, 0.4, 1,0.0, -0.6, -0.2, 1.0, 1.0, 0.4, 0.4, 1,0.0, 0.4, 0.0, 1, 0.4, 0.4, 1.0, 1,-0.4, -0.4, 0.0, 1, 0.4, 0.4, 1.0, 1,0.4, -0.4, 0.0, 1, 0.4, 0.4, 1.0, 1
];

修改着色器代码,这里使用varying变量,先将颜色值传递给顶点着色器,再透传给片元着色器中,根据varying变量的值,设置颜色。

// 顶点着色器
const vertexString = `attribute vec4 a_position;attribute vec4 a_color;varying vec4 color;void main(){gl_Position =  a_position;color = a_color;}`;// 片元着色器
const fragmentString = `precision mediump float;varying vec4 color;void main(){gl_FragColor = color;}`;

进行赋值,在js当中通过vertexAttribPointer将颜色值传递给着色器当中的a_color变量,同时因为数组的内容改了,之前是4个数据一个点现在是8个数据一个点,所以设置点的代码也要调整

// 设置点坐标
webGL.vertexAttribPointer(aPosition, 4, webGL.FLOAT, false, 8 * 4, 0);
// 调整不同层级三角形的颜色
let aColor = webGL.getAttribLocation(program, 'a_color');
webGL.enableVertexAttribArray(aColor);
webGL.vertexAttribPointer(aColor, 4, webGL.FLOAT, false, 8 * 4, 4 * 4);

改变视角:在顶点着色器当中添加u_formMatrix,使用lookAt方法设置视点,并传递给着色器中

let modelView = mat4.create();
mat4.identity(modelView);
modelView = mat4.lookAt(modelView, [0, -0.5, 0.2], [0, 0, 0], [0, 1, 0]);
let uniformMatrix = webGL.getUniformLocation(program, 'u_formMatrix');
webGL.uniformMatrix4fv(uniformMatrix, false, modelView);

lookAt(out, eye, center, up): 使用给定的眼睛位置、焦点和上方向轴生成注视矩阵

参数说明

名称类型描述
outmat4截头体矩阵将被写入
eyeReadonlyVec3视口位置
centerReadonlyVec3观看者正在观看的点
upReadonlyVec3指定上方向

叠加矩阵变化

可以再创建一个新的矩阵进行旋转90度,之后将视口矩阵和旋转矩阵乘积重新赋值也就完成了叠加矩阵变化。

let ModelMatrix = mat4.create();
mat4.identity(ModelMatrix);
mat4.rotate(ModelMatrix, ModelMatrix, Math.PI / 2, [0, 0, 1]);let ViewMatrix = mat4.create();
mat4.identity(ViewMatrix);
ViewMatrix = mat4.lookAt(ViewMatrix, [0, 0, 0.3], [0, 0, 0], [0, 1, 0]);
let mvMatrix = mat4.create();
mat4.multiply(mvMatrix, ViewMatrix, ModelMatrix);
// 最后将这个进行赋值
let uniformMatrix = webGL.getUniformLocation(program, 'u_formMatrix');
webGL.uniformMatrix4fv(uniformMatrix, false, mvMatrix);

原本都是正向上的三角形就变成了横的了

在这里插入图片描述

可视范围(正射投影)

在上一个案例当中,当视点在极右或极左的位置时,三角形会缺少一部分。原因是没有指定可视范围,即实际观察得到的区域边界

两类常用的可视空间:

  • 长方体可视空间,也称盒状空间,由正射投影产生
  • 四棱锥/金字塔可视空间,由透视投影产生

可视空间由前后两个矩形表面确定,分别称近裁剪面(near)和远裁剪面(far)

改变视口可视域:ortho(out, left, right, bottom, top, near, far):生成具有给定边界的正交投影矩阵

名称类型描述
outmat4输出矩阵
leftnumber截头体的左边界
rightnumber右边界
bottomnumber底边界
topnumber上边界
nearnumber
farnumber

改变视口可视域,通过ortho方法设置可视域范围,这样他的坐标系取值就变成了canvas的坐标系,绘制图形的坐标值也要进行相对应的调整

let ProjMatrix = mat4.create();
mat4.identity(ProjMatrix);
mat4.ortho(ProjMatrix, -100, 100, -100, 100, near, far);    //修改可视域范围let uniformMatrix = webGL.getUniformLocation(program, 'u_formMatrix');
webGL.uniformMatrix4fv(uniformMatrix, false, ProjMatrix);

可视空间(透视投影)

在正射投影的可视空间中,不管三角形与视点的距离是远是近,它有多大,那么画出来就有多大。为了打破这条限制,使用透视投影可视空间,它将使场景具有深度性

设置透视投影:perspective(out, fovy, aspect, near, far):生成具有给定边界的透视投影矩阵

名称类型描述
outmat4输出矩阵
fovynumber垂直方向的视野角度(上截面与下截面的角度)
aspectnumber纵横比(宽高比)
nearnumber
farnumber

创建一个透视投影矩阵,并赋值给uniformMatrix,去修改传入的角度的时候可以观察到变化

let ProjMatrix = mat4.create();
mat4.identity(ProjMatrix);
//角度小,看到的物体大,角度大,看到的物体小。
mat4.perspective(ProjMatrix, 160 * Math.PI / 180, 1, 1, 100); //修改可视域范围

在这里插入图片描述

正射投影和透视投影的区别
  • 在透视投影下,产生的三维场景看上去更是有深度感,更加自然,因为我们平时观察真实世界用的也是透视投影。在大多数情况下,比如三维射击类游戏中,我们都应当采用透视投影。
  • 正射投影的好处是用户可以方便地比较场景中物体( 比如两个原子的模型)
    的大小,这是因为物体看上去的大小与其所在的位置没有关系。在建筑平面图等技术绘图的相关场合,应当使用这种投影。

在这里插入图片描述

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

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

相关文章

目标识别与双目测距(1)环境搭建:Ubuntu+yolov5+pcl库

环境情况 ubuntu 18.04 → 20.04(最终) 安装Ubuntu1804虚拟机系统 Anaconda:可参考我的另一篇文章 Python 3.6.13 → 3.8(最终)Anaconda3-2021.05 目标识别:YOLOv5相关 1、安装git sudo apt install gi…

GAMES101-现代计算机图形学入门(Animation/simulation)

目录 一些科普Keyframe AnimatorPhysical Simulation质点弹簧系统 Mass Spring Rope粒子系统运动学 Forward Kinematics逆运动学Inverse KinematicsRiggingMotion Capture 第二次课 cont.Single Particle Simulation流体模拟 Fluid Simulation GitHub主页:https://g…

[C++] 智能指针 进阶

标题:[C] 智能指针 进阶 水墨不写bug 在很久之前我们探讨了智能指针的浅显认识,接下来会更加深入,从源码角度认识智能指针,从而了解智能指针的设计原理,并应用到以后的工作项目中。 本文将会按照C智能指针的发展历史&…

Linux系统中应用端控制串口的基本方法

在编写应用程序的时候,我们需要将上面提到的串口参数如波特率、数据位、奇偶校验位、停止位 等设置好,通讯双方才能在预定好的参数下相互通讯。哪里设置参数,通过什么设置参数,还有 如何设置这些参数,接下来我们分别来实现。 1:linux系统中termios结构体用法 POS…

高效加盖骑缝章:PDF文件处理的实用解决方案

今天为大家推荐一款实用的PDF骑缝章加盖工具,它能一键生成精准骑缝章,无需复杂操作,极大提升办公效率。 01 软件介绍 这款软件就是PDF加盖骑缝章: 绿色免费且无需安装,解压即用,不占用系统资源 支持Windo…

LLM架构解析:NLP基础(第一部分)—— 模型、核心技术与发展历程全解析

本专栏深入探究从循环神经网络(RNN)到Transformer等自然语言处理(NLP)模型的架构,以及基于这些模型构建的应用程序。 本系列文章内容: NLP自然语言处理基础(本文)词嵌入&#xff0…

黑马点评Feed流推送帖子zset实现

什么是Feed流? 顾名思义就是投喂流 传统的信息查找方式用户需要手动去搜寻 Feed流就是不再是用户自己找 而是服务端主动投喂他喜欢/想看到的信息 考虑以下场景: 张三关注了李四 王五关注了李四 当李四发了动态时 它的粉丝们在我的关注列表里就能看到自己关注的人发的动态且最…

详解CountDownLatch底层源码

大家好,我是此林。 今天来分享一下CountDownLatch的底层源码。 CountDownLatch 是 Java 并发包 (java.util.concurrent) 中的线程之间同步工具类,主要用于协调多个线程的执行顺序。其核心思想是通过计数器实现线程间的"等待-唤醒"机制&#…

Ubuntu24.04 离线安装 MySQL8.0.41

一、环境准备 1.1 官方下载MySQL8.0.41 完整包 1.2 上传包 & 解压 上传包名称是:mysql-server_8.0.41-1ubuntu24.04_amd64.deb-bundle.tar # 切换到上传目录 cd /home/MySQL8 # 解压: tar -xvf mysql-server_8.0.41-1ubuntu24.04_amd64.deb-bundl…

【算法应用】基于粒子群算法PSO求解无人机集群路径规划问题

目录 1.无人机路径规划模型2.粒子群算法PSO原理3.结果展示4.参考文献5.代码获取 1.无人机路径规划模型 路径最优性 为了实现UAV的高效运行,计划的路径需要在某一特定标准上达到最优。UAV飞行路径Xi表示为UAV需要飞过的一系列n个航路点,每个航路点对应搜…

电脑ip地址每次开机会换吗?全面解析

在探讨“电脑IP地址每次开机会换吗”这一问题时,我们首先需要明确的是,IP地址的更换情况并非一成不变,而是受到多种因素的影响,其中最核心的是IP地址的类型——动态IP还是静态IP。这两种类型的IP地址在分配方式、稳定性以及使用场…

sqli-labs靶场 less 8

文章目录 sqli-labs靶场less 8 布尔盲注 sqli-labs靶场 每道题都从以下模板讲解,并且每个步骤都有图片,清晰明了,便于复盘。 sql注入的基本步骤 注入点注入类型 字符型:判断闭合方式 (‘、"、’、“”&#xf…

docker-Dify外接Fastgpt知识库

参考地址:https://mp.weixin.qq.com/s/crQrneHZ0sT-c04YanofSw 总体步骤 部署fda(fastgpt-dify-adapter)docker 部署dify,fastgpt在fastgpt创建open apikey,复制知识库id;在dify外接fastgpt知识库; docker安装 下载…

Django学习笔记

Django学习笔记 安装django pip install django创建APP 用django来写后端的时候,要把各个功能分散到各个创建好的APP去实现 在终端输入 python manage.py startapp app01(APP名称)APP内部文件 admin.py django默认提供了admin后台管理 apps.py app启动类 mo…

向量数据库是什么,它有什么作用?

环境: 向量数据库 问题描述: 向量数据库是什么,它有什么作用 解决方案: 向量数据库是一种专门设计用于高效处理高维向量数据的系统,主要用于存储、索引、查询和检索高维向量数据,特别适合处理非结构化数…

【SPP】蓝牙串口协议应用层深度解析:从连接建立到实战开发

目录 一、SPP应用层协议框架与角色模型 1.1 分层协议栈模型 1.2 设备角色模型(DevA 与 DevB 交互) 二、连接建立流程:从 SDP 到 RFCOMM 2.1 服务发现(SDP)流程(SDP 记录关键参数) 2.2 连接…

【Portainer】Docker可视化组件安装

Portainer Portainer 是用于管理容器化环境的一体化平台工程解决方案,提供广泛的定制功能,以满足个人开发人员和企业团队的需求。 官方地址: https://www.portainer.io/ 安装 在 WSL / Docker Desktop 上使用 Docker 安装 Portainer CE 通过命令或UI页…

【第33节】windows原理:初探PE文件

目录 一、PE文件概述 二、DOS头部 三、DOS头部与NT头部之间 四、NT头部 五、文件头区段 六、了解个别概念 七、扩展头 八、区段头表 一、PE文件概述 PE文件是有特定格式的文件,像后缀名是EXE的可执行文件、后缀名是DLL的动态链接库文件、sys格式的驱动文件&…

谷粒微服务高级篇学习笔记整理---异步线程池

多线程回顾 多线程实现的4种方式 1. 继承 Thread 类 通过继承 Thread 类并重写 run() 方法实现多线程。 public class MyThread extends Thread {Overridepublic void run() {System.out.println("线程运行: " Thread.currentThread().getName());} }// 使用 pub…

网络运维学习笔记(DeepSeek优化版) 024 HCIP-Datacom OSPF域内路由计算

文章目录 OSPF域内路由计算:单区域的路由计算一、OSPF单区域路由计算原理二、1类LSA详解2.1 1类LSA的作用与结构2.2 1类LSA的四种链路类型 三、OSPF路由表生成验证3.1 查看LSDB3.2 查看OSPF路由表3.3 查看全局路由表 四、2类LSA详解4.1 2类LSA的作用与生成条件4.2 2…