WebGL编程指南 - 高级变换与动画基础

  • 学习使用一个矩阵变换库,该库封装了矩阵运算的数学细节。
  • 快速上手使用该矩阵库,对图形进行复合变换。
  • 在该矩阵库的帮助下,实现简单的动画效果。

矩阵变换库:cuon-matrix.js

OpenGL中的函数:

书中 cuon-matrix.js 函数库中是这样实现的:

函数库主要创建了一个Matrix4类(构造函数),在该类原型函数下绑定了众多方法,许多函数之前都留有注释,我们主要看一下代码最前面的注释:

/** 
 * This is a class treating 4x4 matrix.
 * This class contains the function that is equivalent to OpenGL matrix stack.
 * The matrix after conversion is calculated by multiplying a conversion matrix from the right.
 * The matrix is replaced by the calculated result.
 */

根据注释可知,该函数库主要处理4×4的矩阵,对标OpenGL中矩阵处理函数。函数库提供了一个名为Matrix4的对象(构造函数),我们可以通过new方法创建它的实例,对象内部挂载了许多关于矩阵计算的方法。

这样使用函数库的方法,写起来更加简单

  • 矩阵的创建方式:
// RotatedTriangle_Matrix.js
...
  // 创建旋转矩阵
  let radian = (Math.PI * ANGLE) / 180.0 // 转换为弧度制
  let cosB = Math.cos(radian)
  let sinB = Math.sin(radian)
  // 注意WebGL中矩阵是列主序的
  let 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,
  ])
  ...
// RotatedTriangle_Matrix4.js
...
  // 创建旋转矩阵
  // 为旋转矩阵创建Matrix4对象
  let xformMatrix = new Matrix4()
  // 将xformMatrix设置为旋转矩阵
  xformMatrix.setRotate(ANGLE, 0, 0, 1)
...
  • 传输矩阵数据
// RotatedTriangle_Matrix.js
...
  // 将旋转图形所需数据传输给顶点着色器
  let u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix')
  if (!u_xformMatrix) {
    console.log('Failed to get the storage location of u_xformMatrix')
  }
  gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix)
...
// RotatedTriangle_Matrix4.js
...
  let u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix')
  if (!u_xformMatrix) {
    console.log('Failed to get the storage location of u_xformMatrix')
  }
  gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix.elements)
...

Matrix4在不提供数据直接初始化的情况下,其挂载的element元素就是一个单位阵,之后大部分操作都是对element元素进行的。

从上表可见,Matrix4对象有两种方法:

  • 一种方法的名称中含有前缀set,这一类方法会根据参数计算出变换矩阵,然后将变换矩阵写入自身;
  • 另一种方法的名称中不含set前缀,这一类方法在计算出变换矩阵后,会将自身与计算出的变换矩阵相乘,最终结果写入Matrix4对象。

复合变换RotatedTranslatedTriangle.js

相关内容:采用Matirx4对象,运用矩阵乘法模拟符合变换。
相关函数:Matrix4.setRotate(), Matrix4.translate()

注意:矩阵乘法的先后顺序与模型变换的先后顺序有关,不能随意变化(矩阵乘法不满足乘法交换律)。

注意是 左乘

一个模型可能经过了多次变换,将这些变换全部复合成一个等效的变换,就得到了模型变换(model transformation),或称建模变换(modeling transformation),相应的,模型变换的矩阵称为模型矩阵(model matrix)。

重写Rotatedtriangle_Matrix4 代码:

  • 命名习惯,在顶点着色器中命名uniform变量(mat4)为u_ModelMatrix,取模型矩阵之意。
// 顶点着色器
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'uniform mat4 u_ModelMatrix;\n' +
  'void main(){\n' +
  '  gl_Position = u_ModelMatrix * a_Position;\n' +
  '}\n'
  • 构建矩阵时,采用Matrix4对象,先设置对象为旋转矩阵,再和平移矩阵相乘。
  // 创建Matrix4对象
  let modelMatrix = new Matrix4()
  // 旋转角度
  let ANGLE = 90.0
  // 平移距离
  let Tx = 0.5
  // 设置模型矩阵为旋转矩阵
  modelMatrix.setRotate(ANGLE, 0, 0, 1)
  // 将模型矩阵乘以平移矩阵
  modelMatrix.translate(Tx, 0, 0)

  // 将模型矩阵传输给顶点着色器
  let u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix')
  if (!u_ModelMatrix) {
    console.log('Failed to get the storage location of u_ModelMatrix')
  }
  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements)

完整代码:

// RotatedTranslatedTriangle.js
// 顶点着色器
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'uniform mat4 u_ModelMatrix;\n' +
  'void main(){\n' +
  '  gl_Position = u_ModelMatrix * a_Position;\n' +
  '}\n'
// 片元着色器
var FSHADER_SOURCE =
  'void main(){\n' + ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + '}\n'

// 主函数
function main() {
  // 获取canvas元素
  let canvas = document.getElementById('webgl')
  // 获取WebGL上下文
  let 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 initialize shaders')
    return
  }
  // 设置顶点位置
  let n = initVertexBuffers(gl)
  if (n < 0) {
    console.log('Failed to set the positions of the vertices')
    return
  }

  // 创建Matrix4对象
  let modelMatrix = new Matrix4()
  // 旋转角度
  let ANGLE = 90.0
  // 平移距离
  let Tx = 0.5
  // 设置模型矩阵为旋转矩阵
  modelMatrix.setRotate(ANGLE, 0, 0, 1)
  // 将模型矩阵乘以平移矩阵
  modelMatrix.translate(Tx, 0, 0)

  // 将模型矩阵传输给顶点着色器
  let u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix')
  if (!u_ModelMatrix) {
    console.log('Failed to get the storage location of u_ModelMatrix')
  }
  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements)

  // 设置背景色
  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  // 清空绘图区
  gl.clear(gl.COLOR_BUFFER_BIT)
  // 绘制三角形
  gl.drawArrays(gl.TRIANGLES, 0, n)
}

function initVertexBuffers(gl) {
  // 设置类型化数组和顶点数
  let vertices = new Float32Array([0.0, 0.3, -0.3, -0.3, 0.3, -0.3])
  let n = 3
  // 创建缓冲区对象
  let vertexBuffer = gl.createBuffer()
  if (!vertexBuffer) {
    console.log('Failed to create the buffer object')
    return -1
  }
  // 绑定缓冲区
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
  // 缓冲区写入数据
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STREAM_DRAW)

  let 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
  }
  // 将缓冲区分配给attribute变量
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
  // 开启attribute变量(连接)
  gl.enableVertexAttribArray(a_Position)

  return n
}

动画-RotatingTriangle.js

相关内容:通过JavaScript灵活设计WebGL系统,通过反复变换和重绘图形生成动画效果;setInterval()系列函数和requestAnimationgFrame()系列函数的异同。
相关函数:setInterval(), requestAnimationFrame(), cancelAnimationFrame()

机制一:在t0、t1、t2、t3等时刻反复调用同一个函数来绘制三角形。
机制二:每次绘制之前,清除上次绘制的内容,并使三角形旋转相应的角度。

基于此,该实例程序与前面的示例有以下三点区别:

  • 实现反复调用绘制函数的机制(机制一)
  • 定义绘制函数,在绘制函数中包括清空绘图区、向着色器传值、绘制三步
  • 由于程序需要反复绘制,所以在一开始就指定了背景色。
// RotatedTranslatedTriangle.js
// 顶点着色器
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'uniform mat4 u_ModelMatrix;\n' +
  'void main(){\n' +
  '  gl_Position = u_ModelMatrix * 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_STEP = 45.0

// 主函数
function main() {
  // 获取canvas元素
  let canvas = document.getElementById('webgl')
  // 获取WebGL上下文
  let 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 initialize shaders')
    return
  }
  // 设置顶点位置
  let n = initVertexBuffers(gl)
  if (n < 0) {
    console.log('Failed to set the positions of the vertices')
    return
  }
  // 设置背景色
  gl.clearColor(0.0, 0.0, 0.0, 1.0)

  // 获取u_ModelMatrix变量存储位置
  let u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix')
  if (!u_ModelMatrix) {
    console.log('Failed to get the storage location of u_ModelMatrix')
  }
  // 三角形当前旋转角度
  let currentAngle = 0.0
  // 模型矩阵,Matrix4对象
  let modelMatrix = new Matrix4()

  // 开始绘制三角形
  let tick = function () {
    currentAngle = animate(currentAngle) // 更新旋转角
    draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix)
    requestAnimationFrame(tick) // 请求浏览器调用tick
  }
  tick()
}

function initVertexBuffers(gl) {
  // 设置类型化数组和顶点数
  let vertices = new Float32Array([0.0, 0.3, -0.3, -0.3, 0.3, -0.3])
  let n = 3
  // 创建缓冲区对象
  let vertexBuffer = gl.createBuffer()
  if (!vertexBuffer) {
    console.log('Failed to create the buffer object')
    return -1
  }
  // 绑定缓冲区
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
  // 缓冲区写入数据
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STREAM_DRAW)

  let 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
  }
  // 将缓冲区分配给attribute变量
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
  // 开启attribute变量(连接)
  gl.enableVertexAttribArray(a_Position)

  return n
}
function draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix) {
  // 设置旋转矩阵
  modelMatrix.setRotate(currentAngle, 0, 0, 1)
  // 旋转前加个平移
  // modelMatrix.translate(0.5, 0, 0)
  // 将旋转矩阵传输给顶点着色器
  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements)
  // 清空绘图区
  gl.clear(gl.COLOR_BUFFER_BIT)
  // 绘制三角形
  gl.drawArrays(gl.TRIANGLES, 0, n)
}

// 记录上一次调用函数的时刻
var g_last = Date.now()
// 更新旋转角
function animate(angle) {
  // 计算距离上次调用经过多长时间
  let now = Date.now()
  let elapsed = now - g_last // 毫秒
  g_last = now
  // 根据上次调用的时间,更新当前旋转角度
  let newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0
  return (newAngle %= 360)
}

重复调用函数的方法:

  // 开始绘制三角形
  let tick = function () {
    currentAngle = animate(currentAngle) // 更新旋转角
    draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix) // 绘制三角形
    requestAnimationFrame(tick) // 请求浏览器调用tick
  }
  tick()

该方法是一个诞生较早的方法,其出现时浏览器还没有支持多标签页,所以在现代浏览器中,不论标签页是否被激活,该方法都会反复调用func,如果标签页较多,就会增加浏览器的负荷。

这一方法只有当标签页处于激活状态时才会生效,基于此也无法指定重复调用的时间间隔。

更新旋转角度:

...
// 旋转速度(度/秒)
var ANGLE_STEP = 45.0
...
// 记录上一次调用函数的时刻
var g_last = Date.now()
// 更新旋转角
function animate(angle) {
  // 计算距离上次调用经过多长时间
  let now = Date.now()
  let elapsed = now - g_last // 毫秒
  g_last = now
  // 根据上次调用的时间,更新当前旋转角度
  let newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0
  return (newAngle %= 360)
}

animate()是更新旋转角的主要部分,该函数配合当前旋转角currentAngle变量、全局变量ANGLE_STEP变量(旋转速度)和g_last(上一次调用函数的时刻)使用,基本思路是:根据本次调用与上次调用之间的时间间隔来决定这一帧的旋转角度比上一帧大出多少

绘制函数的设计:

/**
 * 绘制三角形
 * @param {WebGLRenderingContext} gl 上下文对象
 * @param {number} n 顶点数量 int
 * @param {number} currentAngle 当前旋转角度 float
 * @param {Martix4} modelMatrix 根据当前的旋转角度计算出的旋转矩阵,存储在Martix4对象中
 * @param {number} u_ModelMatrix 顶点着色器中同名的uniform变量的存储位置 uint
 */
function draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix) {
  // 设置旋转矩阵
  modelMatrix.setRotate(currentAngle, 0, 0, 1)
  // 将旋转矩阵传输给顶点着色器
  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements)
  // 清空绘图区
  gl.clear(gl.COLOR_BUFFER_BIT)
  // 绘制三角形
  gl.drawArrays(gl.TRIANGLES, 0, n)
}

 参考:【《WebGL编程指南》读书笔记-高级变换与动画基础】_webgl高级-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

go jwt 用户登录和返回用户信息 token ----important!!!

1.每一行代码都有详细注释&#xff0c;解释了其功能和作用。这些注释可以帮助你理解代码如何工作&#xff0c;特别是在处理用户登录、生成 JWT、验证 JWT 和返回用户信息的过程中。 package main // 指定这个文件是一个可执行程序import ("fmt" …

SSRF-利用dict协议-攻击redis

1.靶场准备&#xff1a; CTFHub-技能树-Web-SSRF-Redis协议 蚁剑AntSword 2.简述&#xff1a; 2.1 SSRF 服务器端请求伪造&#xff0c;存在一个url参数&#xff0c;一般用于图片上传、网页重定向等&#xff0c;我们可以控制url参数&#xff0c;去访问内网服务器的敏感内容…

运用AI实践|如何从AI工具提升工作效率实践

文章目录 引言关于1024这个数值Python 语言获取算法代码Java语言获取算法代码其他语言获取算法代码1024 的用途和功能总结 &#x1f4eb; 作者简介&#xff1a;「六月暴雪飞梨花」&#xff0c;专注于研究Java&#xff0c;就职于科技型公司后端工程师 &#x1f3c6; 近期荣誉&am…

FPGA学习(6)-基础语法参数化设计阻塞与非阻塞

目录 1.两种参数化不改变源文件&#xff0c;只改仿真文件的值 2.参数化设计实现模块的重用 2.1不用参数化方法 2.1.1源文件 2.1.2仿真文件 2.1.3仿真波形及实验 2.2 用参数方法 2.2.1调用之前写的led灯闪烁模块&#xff0c;在本源函数中&#xff0c;例化4次调用之前的模…

Nginx15-Lua扩展模块

零、文章目录 Nginx15-Lua扩展模块 1、ngx_lua模块概念 淘宝开发的ngx_lua模块通过将lua解释器集成进Nginx&#xff0c;可以采用lua脚本实现业务逻辑&#xff0c;由于lua的紧凑、快速以及内建协程&#xff0c;所以在保证高并发服务能力的同时极大地降低了业务逻辑实现成本。…

ECharts饼图-饼图纹理,附视频讲解与代码下载

引言&#xff1a; 在数据可视化的世界里&#xff0c;ECharts凭借其丰富的图表类型和强大的配置能力&#xff0c;成为了众多开发者的首选。今天&#xff0c;我将带大家一起实现一个饼图图表&#xff0c;通过该图表我们可以直观地展示和分析数据。此外&#xff0c;我还将提供详…

信号(二)【信号的产生】

目录 1. 键盘组合键2. kill 命令3. 系统调用4. 异常5. 软件条件6. Term 和 Core 的区别 本篇文章介绍五种信号产生的方式&#xff0c;键盘组合键、kill 命令、系统调用、代码异常&#xff08;进程异常&#xff09;、软件条件来产生信号。 1. 键盘组合键 信号&#xff08;一&a…

商汤科技十周年公布新战略,将无缝集成算力、模型及应用

10月18日&#xff0c;恰逢商汤科技十周年庆典&#xff0c;“2024商汤十周年国际论坛&#xff1a;迈向AI 2.0共融新时代”在香港科学园成功举办。 据「TMT星球」了解&#xff0c;来自全球的行业领袖、政府代表、AI专家共聚于此&#xff0c;共同探讨AI行业的未来。 活动上&…

Linux隐藏权限介绍

隐藏权限概览 在Linux系统中&#xff0c;有时即便是以root用户身份&#xff0c;你也可能遇到无法修改特定文件的情况。这种限制往往源自chattr命令的应用&#xff0c;该命令用于为文件或目录设置“隐藏权限”&#xff0c;即底层属性&#xff0c;以增强系统安全性。值得注意的是…

Standard IO

为了提高可移植性&#xff0c;将通用IO接口经过再封装就形成了标准IO&#xff0c;标准IO不仅适用于Unix环境&#xff0c;也兼容非Unix环境&#xff0c;这也是为什么说我们应该尽可能的使用标准IO&#xff0c;通用IO通过文件描述符fd来与文件交互&#xff0c;为了以示区分&#…

极氪MIX:一台只有你想不到,没有它做不到的“家用神车”

了解极氪品牌的朋友应该都知道 极氪一直都在尝试打破目前汽车或者生活的一些现状 更愿意创造一些破界、超前的产品 比如说将家庭城市通勤、假日露营、自驾旅行、户外垂钓、朋友相聚等多场景融入一个空间的极氪MIX 这款车突破了SUV或MPV车型形态的固有限制 前悬仅 865mm&am…

【ArcGIS Pro实操第八期】绘制WRF三层嵌套区域

【ArcGIS Pro实操第八期】绘制WRF三层嵌套区域 数据准备ArcGIS Pro绘制WRF三层嵌套区域Map-绘制三层嵌套区域更改ArcMap地图的默认显示方向指定数据框范围 Map绘制研究区Layout-布局出图 参考 本博客基于ArcGIS Pro绘制WRF三层嵌套区域&#xff0c;具体实现图形参考下图&#x…

Centos安装Nginx 非Docker

客户的机器属于 Centos7 系列&#xff0c;由于其较为陈旧&#xff0c;2024开始众多镜像和软件源都已失效。此篇文章将详细记录在 Centos7 操作系统上从零开始安装 Nginx 的整个流程。 本文Nginx是安装在/usr/local/nginx下 详细步骤如下&#xff1a; 准备Nginx安装包&#x…

安防监控摄像头图传模组,1公里WiFi无线传输方案,监控新科技

在数字化浪潮汹涌的今天&#xff0c;安防监控领域也迎来了技术革新的春风。今天&#xff0c;我们就来聊聊这一领域的产品——摄像头图传模组&#xff0c;以及它如何借助飞睿智能1公里WiFi无线传输技术&#xff0c;为安防监控带来未有的便利与高效。 一、安防监控的新篇章 随着…

程序员适合玩的游戏:《人力资源机器》提升编程思维【Human Resource Machine】

程序员适合玩的游戏&#xff1a;《人力资源机器》提升编程思维【Human Resource Machine】 在当今这个技术日新月异的时代&#xff0c;编程已经成为一门不可或缺的技能。对于程序员来说&#xff0c;不仅需要扎实的专业知识&#xff0c;还需要不断锻炼逻辑思维和解决问题的能力…

用.NET开发跨平台应用程序采用 Avalonia 与MAUI如何选择

Avalonia是一个强大的框架&#xff0c;使开发人员能够使用.NET创建跨平台应用程序。它使用自己的渲染引擎绘制UI控件&#xff0c;确保在Windows、macOS、Linux、Android、iOS和WebAssembly等不同平台上具有一致的外观和行为。这意味着开发人员可以共享他们的UI代码&#xff0c;…

RNN、LSTM 与 Bi-LSTM

一. RNN 循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;是深度学习领域一类具有内部自连接的神经网络能够学习复杂的矢量到矢量的映射。 最大特点&#xff1a;前面的序列数据可以用作后面的结果预测中。 一个简单的循环神经网络结构&#xff0c;其结构包…

如何写一个视频编码器演示篇

先前写过《视频编码原理简介》&#xff0c;有朋友问光代码和文字不太真切&#xff0c;能否补充几张图片&#xff0c;今天我们演示一下&#xff1a; 这是第一帧画面&#xff1a;P1&#xff08;我们的参考帧&#xff09; 这是第二帧画面&#xff1a;P2&#xff08;需要编码的帧&…

Golang | Leetcode Golang题解之第480题滑动窗口中位数

题目&#xff1a; 题解&#xff1a; type hp struct {sort.IntSlicesize int } func (h *hp) Push(v interface{}) { h.IntSlice append(h.IntSlice, v.(int)) } func (h *hp) Pop() interface{} { a : h.IntSlice; v : a[len(a)-1]; h.IntSlice a[:len(a)-1]; return v }…

SCCB协议与IIC协议不同

SCCB开始信号与结束信号都与IIC协议的大概一致&#xff0c;这里就不细讲了 开始、结束信号参考&#xff1a;【I2C】IIC读写时序_iic读时序-CSDN博客 SSCB写时序&#xff1a; 即&#xff1a;start phase_1 phase_2 phase_3 stop SCCB读时序&#xff1a; 即&#xff…