WebGL 用鼠标控制物体旋转
目录
鼠标控制物体旋转
如何实现物体旋转
示例程序(RotateObject.js)
代码详解
示例效果
鼠标控制物体旋转
有时候,WebGL程序需要让用户通过鼠标操作三维物体。这一节来分析示例程序RotateObject,该程序允许用户通过拖动(即按住左键移动)鼠标旋转三维物体。为了简单,示例程序中的三维物体是一个立方体,但拖曳鼠标旋转物体的方法却适用于所有物体。下图显示了程序的运行效果,立方体上贴有纹理图像。
如何实现物体旋转
我们已经知道如何旋转二维图形或三维物体了:就是使用模型视图投影矩阵来变换顶点的坐标。现在需要使用鼠标来控制物体旋转,就需要根据鼠标的移动情况创建旋转矩阵,更新模型视图投影矩阵,并对物体的顶点坐标进行变换。
我们可以这样来实现:在鼠标左键按下时记录鼠标的初始坐标,然后在鼠标移动的时候用当前坐标减去初始坐标,获得鼠标的位移,然后根据这个位移来计算旋转矩阵。显然,我们需要监听鼠标的移动事件,并在事件响应函数中计算鼠标的位移、旋转矩阵,从而旋转立方体。下面看一下示例程序。
示例程序(RotateObject.js)
如下显示了示例程序的代码,如你所见,着色器部分没什么特别的。顶点着色器使用模型视图投影矩阵变换顶点坐标(第7行),并向片元着色器传入纹理坐标以映射纹理(第8行)。
var VSHADER_SOURCE ='attribute vec4 a_Position;\n' +'attribute vec2 a_TexCoord;\n' +'uniform mat4 u_MvpMatrix;\n' +'varying vec2 v_TexCoord;\n' +'void main() {\n' +' gl_Position = u_MvpMatrix * a_Position;\n' +' v_TexCoord = a_TexCoord;\n' +'}\n';
var FSHADER_SOURCE ='#ifdef GL_ES\n' +'precision mediump float;\n' +'#endif\n' +'uniform sampler2D u_Sampler;\n' +'varying vec2 v_TexCoord;\n' +'void main() {\n' +' gl_FragColor = texture2D(u_Sampler, v_TexCoord);\n' +'}\n';function main() {var canvas = document.getElementById('webgl');var gl = getWebGLContext(canvas);if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) returnvar n = initVertexBuffers(gl);gl.clearColor(0.0, 0.0, 0.0, 1.0);gl.enable(gl.DEPTH_TEST);var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');var viewProjMatrix = new Matrix4();viewProjMatrix.setPerspective(30.0, canvas.width / canvas.height, 1.0, 100.0);viewProjMatrix.lookAt(3.0, 3.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);// 注册事件处理函数var currentAngle = [0.0, 0.0]; // 绕z轴旋转角度,绕y轴旋转角度initEventHandlers(canvas, currentAngle);if (!initTextures(gl)) return // 设置纹理var tick = function() { // Start drawingdraw(gl, n, viewProjMatrix, u_MvpMatrix, currentAngle);requestAnimationFrame(tick, canvas);};tick();
}function initVertexBuffers(gl) {// v6----- v5// /| /|// v1------v0|// | | | |// | |v7---|-|v4// |/ |/// v2------v3var vertices = new Float32Array([ // Vertex coordinates1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0,-1.0, 1.0, 1.0,-1.0, 1.0, // v0-v1-v2-v3 front1.0, 1.0, 1.0, 1.0,-1.0, 1.0, 1.0,-1.0,-1.0, 1.0, 1.0,-1.0, // v0-v3-v4-v5 right1.0, 1.0, 1.0, 1.0, 1.0,-1.0, -1.0, 1.0,-1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up-1.0, 1.0, 1.0, -1.0, 1.0,-1.0, -1.0,-1.0,-1.0, -1.0,-1.0, 1.0, // v1-v6-v7-v2 left-1.0,-1.0,-1.0, 1.0,-1.0,-1.0, 1.0,-1.0, 1.0, -1.0,-1.0, 1.0, // v7-v4-v3-v2 down1.0,-1.0,-1.0, -1.0,-1.0,-1.0, -1.0, 1.0,-1.0, 1.0, 1.0,-1.0 // v4-v7-v6-v5 back]);var texCoords = new Float32Array([ // Texture coordinates1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v1-v2-v3 front0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, // v0-v3-v4-v5 right1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, // v0-v5-v6-v1 up1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v1-v6-v7-v2 left0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // v7-v4-v3-v2 down0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 // v4-v7-v6-v5 back]);// Indices of the verticesvar indices = new Uint8Array([0, 1, 2, 0, 2, 3, // front4, 5, 6, 4, 6, 7, // right8, 9,10, 8,10,11, // up12,13,14, 12,14,15, // left16,17,18, 16,18,19, // down20,21,22, 20,22,23 // back]);var indexBuffer = gl.createBuffer();if (!initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position')) return -1; // Vertex coordinatesif (!initArrayBuffer(gl, texCoords, 2, gl.FLOAT, 'a_TexCoord')) return -1;// Texture coordinatesgl.bindBuffer(gl.ARRAY_BUFFER, null);gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);return indices.length;
}function initEventHandlers(canvas, currentAngle) {var dragging = false; // 是否在拖动var lastX = -1, lastY = -1; // 鼠标的开始位置canvas.onmousedown = function(ev) { // Mouse is pressedvar x = ev.clientX, y = ev.clientY;// 如果鼠标在canvas内就开始拖动var rect = ev.target.getBoundingClientRect();if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {lastX = x; lastY = y;dragging = true;}};canvas.onmouseup = function(ev) { dragging = false; }; // Mouse is releasedcanvas.onmousemove = function(ev) { // Mouse is movedvar x = ev.clientX, y = ev.clientY;if (dragging) {var factor = 100/canvas.height; // The rotation ratiovar dx = factor * (x - lastX);var dy = factor * (y - lastY);// 将x轴旋转角度限制为-90到90度currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 90.0), -90.0);currentAngle[1] = currentAngle[1] + dx; // 拿y轴举例,鼠标水平移动,物体会以Y轴旋转,所以水平的移动距离直接影响y轴要转动角度}lastX = x, lastY = y;};
}var g_MvpMatrix = new Matrix4(); // 模型视图投影矩阵
function draw(gl, n, viewProjMatrix, u_MvpMatrix, currentAngle) {// 计算模型视图投影矩阵并将其传递给u_MvpMatrixg_MvpMatrix.set(viewProjMatrix);g_MvpMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0); // 绕x轴旋转g_MvpMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); // 绕y轴旋转gl.uniformMatrix4fv(u_MvpMatrix, false, g_MvpMatrix.elements);gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 清除颜色|深度缓冲gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0); // 画画
}function initArrayBuffer(gl, data, num, type, attribute) {var buffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, buffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);var a_attribute = gl.getAttribLocation(gl.program, attribute);gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);gl.enableVertexAttribArray(a_attribute);return true;
}function initTextures(gl) {var texture = gl.createTexture(); // 创建温丽丽对象var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler'); // 获取uSampler的存储位置(_S)var image = new Image();image.onload = function(){ loadTexture(gl, texture, u_Sampler, image); };image.src = '../resources/sky.jpg';return true;
}function loadTexture(gl, texture, u_Sampler, image) {gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 翻转图像Y坐标// 激活纹理单元0gl.activeTexture(gl.TEXTURE0);// 将纹理对象绑定到2维目标(先绑定到纹理单元)gl.bindTexture(gl.TEXTURE_2D, texture);// 设置纹理参数gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);// 将图像设置为纹理,设置图像参数gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);// 将纹理单元0传递到uSamplergl.uniform1i(u_Sampler, 0);
}
代码详解
首先,main()函数计算出了初始的模型视图投影矩阵(第29~31行)。程序将根据鼠标位移来实时更新该矩阵。
然后,鼠标移动事件响应函数实现了用鼠标旋转三维物体的逻辑。currentAngle变量表示当前的旋转角度,它是一个数组,因为物体的旋转需要被分解为绕x轴旋转和绕y轴旋转两步,因此需要两个角度值(第34行)。真正注册事件响应函数的过程发生在initEventHandlers()函数中(第35行)。真正绘制的过程发生在tick()函数中(第37行)。
initEventHandler()函数的任务是注册鼠标响应事件函数(第87行),包括:鼠标左键按下事件(第91行)、鼠标左键松开事件(第101行),以及鼠标移动事件(第103行)。
当鼠标左键被按下时,首先检查鼠标是否在<canvas>元素内部(第95行),如果是,就将鼠标左键按下时的位置坐标保存到lastX和lastY变量中(第96行),并将dragging变量赋值为true,表示拖曳操作(即按住鼠标左键移动)开始了。
鼠标左键被松开时,表示拖曳操作结束了,将dragging变量赋值为false(第101行)。
鼠标移动事件响应函数最为重要(第103行):首先检查dragging变量,判断当前是否处于拖动状态。如果不在拖曳状态,说明是鼠标的正常移动(左键松开状态下的移动),那就什么都不做。如果处于拖曳状态,就计算出当前鼠标(相对于上次鼠标移动事件触发时)的移动距离,即位移值,并将结果保存在dx和dy变量中(第107~108行)。注意,位移值在存入变量前按比例缩小了,这样dx和dy的值就与<canvas>自身的大小无关了。有了鼠标当前的位移dx和dy,就可以根据这两个值计算出当前三维物体(相对于上次鼠标移动事件触发时)在x轴和y轴上的旋转角度值(第110和111行)。而且,程序还将物体在y轴上的旋转角度限制在正负90度之间,这样做的原因仅仅是为了展示技巧,你也可以将其删掉。最后,把当前鼠标的位置坐标赋值给lastX和lastY。
一旦成功地将鼠标的移动转化为旋转矩阵,我们就可以用旋转矩阵更新物体的状态(第121~122行)。当程序再次调用tick()函数进行绘制时,就绘制出了旋转后的物体。
示例效果
相关文章:

WebGL 用鼠标控制物体旋转
目录 鼠标控制物体旋转 如何实现物体旋转 示例程序(RotateObject.js) 代码详解 示例效果 鼠标控制物体旋转 有时候,WebGL程序需要让用户通过鼠标操作三维物体。这一节来分析示例程序RotateObject,该程序允许用户通过拖动&…...

Spring Boot魔法:简化Java应用的开发与部署
文章目录 什么是Spring Boot?1. 自动配置(Auto-Configuration)2. 独立运行(Standalone)3. 生产就绪(Production Ready)4. 大量的起步依赖(Starter Dependencies) Spring …...
参议院算法Java
Dota2 的世界里有两个阵营: Radiant(天辉)和 Dire(夜魇) Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定,他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的一项: 禁止一名参议员的权利:参…...

前端提交规范 ESLint + Prettier + husky + lint-staged
如何统一代码风格,规范提交呢? 推荐使用前端规范全家桶 ESLint Prettier husky lint-staged。 eslint (github.com/eslint/esli…)JavaScript 代码检测工具,检测并提示错误或警告信息prettier (github.com/prettier/pr…) 代码自动化格式…...

python实现命令tree的效果
把所有的文档都传到了git上,但是内容过多找起来不方便,突发奇想如果能在readme中,递归列出所有文件同时添加上对应的地址,这样只需要搜索到对应的文件点击就能跳转过去了… 列出文件总得有个显示格式,所以就按照tree的来了… 用python实现命令tree的效果 首先,这是tree的效果…...

Deformable DETR(2020 ICLR)
Deformable DETR(2020 ICLR) detr训练epochs缩小十倍,小目标性能更好 Deformable attention 结合变形卷积的稀疏空间采样和Transformer的关系建模能力 使用多层级特征层特征,不需要使用FPN的设计(直接使用backbone多层级输出&a…...

springboot01
目录 新建Maven工程,什么都不选 pom.xml加上 新建包top.cjz.controller 新建类HelloController 新建类HelloApplication 运行浏览器访问 新建Maven工程,什么都不选 pom.xml加上 <!--springboot工程需要继承的父工程--> <parent…...

虚拟机中window/ubuntu系统如何联网?
以下内容源于网络资源的学习与整理,如有侵权请告知删除。 参考博客 (1)VMware虚拟机中Windows11无法连接网络 (2)图解vmware虚拟机win8无线上网 (3)VMware中VMnet0、VMnet1、VMnet8是什么 &…...

计算物理专题----随机游走实战
计算物理专题----随机游走实战 Problem 1 Implement the 3D random walk 拟合线 自旋的 拟合函数(没有数学意义) 参数:0.627,3.336,0.603,-3.234 自由程满足在一定范围内的均匀分布以标准自由程为单位长度,…...

《思维与智慧》简介及投稿邮箱
《思维与智慧》自1982年创刊,经国家新闻出版署批准,由河北省教育厅主管,河北行知文化传媒有限责任公司主办的益智励 志类大众文化期刊。 《思维与智慧》办刊宗旨是:“开发思维,启迪智慧,滋润心灵”&#x…...
flask+python快速搭建
app.py """APP 入口模块""" from traceback import format_excfrom api_limiter import limiter from flask import Flask, jsonify import loggingfrom controller import api_sql_blueapp Flask(__name__) limiter.init_app(app) app.regist…...

基于微信小程序的美术馆预约平台设计与实现(源码+lw+部署文档+讲解等)
前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌💗 👇🏻…...

ruoyi-vue-pro yudao 项目商城 mall 模块启用及相关SQL脚本
目前ruoyi-vue-pro 项目虽然开源,但是商城 mall 模块被屏蔽了,查看文档却要收费 199元(知识星球),价格有点太高了吧。 分享下如何启用 mall 模块,顺便贴上sql相关脚本。 一、启用模块 修改根目录 pom.xm…...

default 和 delete 与默认构造函数 的使用
前言 使用default和delete关键字来干预编译器自动生成的函数。让我详细解释一下这些知识点: 正文 编译器生成的默认构造函数: 如果类A没有定义任何构造函数,那么编译器会自动生成一个无参的默认构造函数 A()。这个默认构造函数实际上是一个…...

【开发篇】一、热部署
文章目录 1、手工启动热部署2、自动启动热部署3、热部署范围配置4、关闭热部署功能 1、手工启动热部署 日常开发与调试,改几行代码想看效果就得手动点重启,很繁琐,接下来考虑启动热部署。首先引入springboot开发者工具: <dep…...
点云从入门到精通技术详解100篇-定子装配过程中基于深度学习的易变形材料的点云分割(下)
目录 4.3.2 校正网络 4.3.3 浅层特征提取网络 4.3.4 空间边界 Transformer 深层特征提取网络 4.3.5 损失函数...

谷歌浏览器关闭自动更新功能
背景:自动化测试需要下载webdriver驱动,然而浏览器自动更新会导致原来的驱动版本与现有浏览器版本不匹配,所以要禁用掉浏览器自动更新功能。 1.右键-我的电脑-打开管理; 2.选择任务计划程序-任务计划程序库-找到两个chrome自动更新…...
电商业务--技术负责人 250K*15
职位描述 研发团队管理 系统搭建 技术管理 系统架构 岗位职责 负责/参与到中大型负责系统的整体架构和设计; 根据业务特点和行业最佳实践,设计符合多个市场物流业务需求,且具备可扩展能力的系统架构和业务架构承担团队稳定性建设工作&#…...
MySQL只同步单个表或多个表,非全部同步!
replicate-do-table 是 MySQL 复制配置中的一个选项,它允许您指定要在从服务器上复制的表。如果您想要只复制主服务器上特定的表到从服务器,您可以使用这个选项。 以下是如何操作 replicate-do-table 的步骤: 停止从服务器: 在从服务器上执行…...
【论文基本功】【LaTeX】个人常用易忘LaTeX命令
【论文基本功】【LaTeX】个人常用易忘LaTeX命令 1. 基本符号2. 引用3. 字体及符号大小4. 其他参考 1. 基本符号 符号LaTeX命令备注 ∣ ⋅ ∣ | \cdot | ∣⋅∣| \cdot |绝对值 ∣ ∣ ⋅ ∣ ∣ || \cdot || ∣∣⋅∣∣\| \cdot \|范数 ⌈ ⋅ ⌉ \lceil \cdot \rceil ⌈⋅⌉\lce…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...

[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG
TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码:HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...

水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关
在水泥厂的生产流程中,工业自动化网关起着至关重要的作用,尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关,为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多,其中不少设备采用Devicenet协议。Devicen…...

C++--string的模拟实现
一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现,其目的是加强对string的底层了解,以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量,…...

数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)
目录 🔍 若用递归计算每一项,会发生什么? Horners Rule(霍纳法则) 第一步:我们从最原始的泰勒公式出发 第二步:从形式上重新观察展开式 🌟 第三步:引出霍纳法则&…...