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.mzph.cn/diannao/75905.shtml

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

相关文章

并发编程--共享内存SHM

共享内存SHM 文章目录 共享内存SHM1. 基本概念2. 函数接口2.1 创建或打开SHM对象2.2 映射 / 解除映射SHM对象2.3 其余操作2.4示例代码 1. 基本概念 共享内存,顾名思义,就是通过不同进程共享一段相同的内存来达到通信的目的,由于SHM对象不再交…

Redis中的数据类型与适用场景

目录 前言1. 字符串 (String)1.1 特点1.2 适用场景 2. 哈希 (Hash)2.1 特点2.2 适用场景 3. 列表 (List)3.1 特点3.2 适用场景 4. 集合 (Set)4.1 特点4.2 适用场景 5. 有序集合 (Sorted Set)5.1 特点5.2 适用场景 6. Redis 数据类型的选型建议结语 前言 Redis 作为一款高性能的…

科技赋能建筑业变革:中建海龙创新引领高质量发展新路径

在建筑工业化浪潮中,中建海龙科技有限公司(以下简称“中建海龙”)凭借深厚的技术积累与持续创新,成为推动行业转型升级的标杆企业。作为中国建筑国际集团旗下核心科技力量,中建海龙深耕模块化集成建筑(MiC&…

Vue下 Sortable 实现 table 列表字段可拖拽排序,显示隐藏组件开发

vue 开发table 列表时&#xff0c;需要动态调整列字段的顺序和显示隐藏 实现效果如图所示&#xff1a; vue 组件代码 <template><div style"width: 90%; margin: 0 auto;"><el-table :data"tableData" border"" ref"table…

故障扭曲棱镜反射照片效果ps特效滤镜样机 Distorted Mirror Poster Effect

只需单击几下即可执行令人着迷的高质量图像和摄影&#xff01;此照片效果包含智能对象图层&#xff0c;提供完全自定义、易用性和多功能性的工作流程。只需双击其缩略图打开所需的图层&#xff0c;删除占位符镜头&#xff0c;添加图形&#xff0c;保存它&#xff0c;然后观看 P…

基于dify平台批量分析excel格式信息

如何以表格形式批量输入一些信息&#xff0c;然后让大模型以对话应用形式逐条进行推理分析&#xff1f; 这里提供一个分步解决方案&#xff0c;结合 Dify平台功能 和 API调用优化 的思路&#xff0c;既保证效率又降低复杂度&#xff1a; 1. 优先检查 Dify 的「数据集」功能 Di…

CARLA常见技术问题集锦(一)地图与场景构建篇

编者荐语&#xff1a; 在自动驾驶技术加速落地的今天&#xff0c;CARLA 仿真引擎凭借其开源生态与高保真仿真能力&#xff0c;已成为全球开发者构建智能驾驶算法的核心工具之一。随着虚幻引擎 5.5 的全面升级&#xff0c;CARLA 0.10.0 版本实现了视觉革命&#xff1a;Lumen 全…

vue+webpack5(高级配置)

项目地址 基础配置可查看文档 1、devtool 配置 (找到报错位置)2、优化打包速度3、oneOf 每个文件只被一个loader处理4、 include/exclude 处理某些文件或者排除某些文件5、 cache 缓存 &#xff08;提升后面几次的打包速度&#xff09;6、 多进程打包7、减少代码体积 Tree Shak…

JavaWeb——事务管理、AOP

目录 一、事管理 1.开启事务管理日志 2.开启事务管理 3.传播行为 二、AOP 1.通知类型 2.通知顺序 3.切入点表达式 4.连接点 一、事务管理 1.开启事务管理日志 2.开启事务管理 3.传播行为 当一个事务方法被另一个事务方法调用时&#xff0c;这个事物方法应该如何进行事…

okhttp3网络请求

一、使用okhttp3和gson build.gradle ... dependencied {...implementation com.squareup.okhttp3:okhttp:3.9.0implementation com.google.code.gson:gson:2.10.1 }二、响应模型 可根据实际情况进行调整&#xff0c;目前我所需的就是这三个变量 HttpResponseData.java im…

【蓝桥杯每日一题】3.28

&#x1f3dd;️专栏&#xff1a; 【蓝桥杯备篇】 &#x1f305;主页&#xff1a; f狐o狸x "今天熬的夜&#xff0c;会变成明天奖状的闪光点&#xff01;" 目录 一、唯一的雪花 题目链接 题目描述 解题思路 解题代码 二、逛画展 题目链接 题目描述 解题思路 解题代…

【MinIO】Bucket的生命周期管理

&#x1f47b;创作者&#xff1a;丶重明 &#x1f47b;创作时间&#xff1a;2025年3月7日 &#x1f47b;擅长领域&#xff1a;运维 目录 1.ILM使用介绍2.生命周期配置实例 1.ILM使用介绍 对象生命周期管理&#xff08;ILM&#xff09;是现代对象存储系统的核心功能之一&#x…

Android 中隐藏标题栏和状态栏的方法

在Android开发中&#xff0c;隐藏标题栏和状态栏是实现全屏显示的常见需求。 一、隐藏标题栏 1、通过代码隐藏 对于继承自 AppCompatActivity 的 Activty&#xff0c;可在 onCreate() 方法中调用supportRequestWindowFeature 或 getSupportActionBar 方法来隐藏标题栏。 ove…

进程间通信——信号量

进程间通信——信号量 目录 一、基本概念 1.1 概念 1.2 基本操作 1.3 相关函数 1.3.1 semget创建/获取 1.3.2 semop操作信号量 1.3.3 semctl初始化/删除 二、代码操作 2.1 不用PV的 2.2 用PV 的 2.2.1 a.c 2.2.2 b.c 2.2.3 sem.h 2.2.4 sem.c 一、基本概念 1.1…

Linux内核2-TFTP与NFS环境搭建

Uboot&#xff1a;引导程序 初始化硬件设备&#xff0c;初始化c语言环境&#xff0c;为内核加载做准备 zImage:内核文件 rootfs:文件系统&#xff0c;为用户提供一个与硬件设备数据交互的系统 1.TFTP和NFS功能 TFTP:简单文件传输协议网络配置 pc可以下载 2.minicom bootargs…

TDengine 中的命名与边界

简介 本章主要介绍命名的合法字符集和限制规则&#xff0c;这对于正确使用 TDengine&#xff0c;减小报错很重要&#xff0c;这些规则在 SQL 语句中都生效&#xff0c;在使用过程中要注意&#xff0c;避免不必要的错误。 名称命名规则 合法字符&#xff1a;英文字符、数字和…

C++ 中将函数作为参数传递

C 中将函数作为参数传递 1. 通过指针传递函数 函数可以通过传递函数的地址来作为参数传递&#xff1b;简而言之&#xff0c;就是通过指针实现这一点。 示例代码 #include <iostream> using namespace std;// 定义加法和减法函数 #include <iostream> #include …

Vala 编程语言教程-继承

继承‌ 在 Vala 中&#xff0c;一个类可以继承自 ‌一个或零个‌ 其他类。尽管实际开发中通常继承一个类&#xff08;不同于 Java 等语言的隐式继承机制&#xff09;&#xff0c;但 Vala 并不强制要求必须继承。 当定义继承自其他类的子类时&#xff0c;子类的实例与父…

Crypto Architecture Kit简介

HarmonyOS 5.0.3(15) 版本的配套文档&#xff0c;该版本API能力级别为API 15 Release 文章目录 约束与限制能力范围基本概念与相关Kit的关系 Crypto Architecture Kit屏蔽了第三方密码学算法库实现差异的算法框架&#xff0c;提供加解密、签名验签、消息验证码、哈希、安全随机…

交流电机类型及其控制技术

交流电机可分为同步电机和异步电机两大种类&#xff0c;如果电机转子的转速与定子旋转磁场的转速相等&#xff0c;转子与定子旋转磁场在空间同步地旋转&#xff0c;这种电机就称为同步电机。如果电机转子的转速不等于定子旋转磁场的转速&#xff0c;转子与定子旋转磁场在空间旋…