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()函数进行绘制时,就绘制出了旋转后的物体。

示例效果

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

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

相关文章

2023华为杯研究生数学建模竞赛选题建议+初步分析

如下为C君的2023华为杯研究生数学建模竞赛&#xff08;研赛&#xff09;选题建议初步分析 2023华为杯研究生数学建模竞赛&#xff08;研赛&#xff09;选题建议 提示&#xff1a;DS C君认为的难度&#xff1a;CE<D<F&#xff0c;开放度&#xff1a;CDE<F。 华为专项…

1600*A. LCM Challenge(数论 || 找规律)

解析&#xff1a; n<3&#xff0c;特判 n为奇数&#xff0c;则n、n-1、n-2必定互质&#xff0c;所以结果即为三者之和。 n为偶数&#xff0c; 不会严格证明原因&#xff0c;但是找找规律&#xff0c;是这样的...... #include<bits/stdc.h> using namespace std; #de…

数据科学的文本技术 Text Technology(IR信息检索、搜索引擎)

一、文章摘要 1. 内容 * Introduction to IR and text processing, system components * Zipf, Heaps, and other text laws * Pre-processing: tokenization, normalisation, stemming, stopping. * Indexing: inverted index, boolean and proximity search * Evaluation m…

【RocketMQ专题】快速实战及集群架构原理详解

目录 课程内容一、MQ简介基本介绍*作用&#xff08;解决什么问题&#xff09; 二、RocketMQ产品特点2.1 RocketMQ介绍2.2 RocketMQ特点2.3 RocketMQ的运行架构2.4 消息模型 三、RocketMQ快速实战3.1 快速搭建RocketMQ服务3.2 快速实现消息收发3.3 搭建Maven客户端项目3.4 搭建R…

vue+axios+el-progress(elementUI组件)实现下载进度条实时监听(小白简洁版)

一、实现效果 二、实现方式 方案&#xff1a;使用axios方法onDownloadProgress方法监听下载进度 使用此方式的前提&#xff01;&#xff01;&#xff01;请让后端在响应头中加上content-length&#xff0c;存放下载文件的总大小&#xff0c;如下图&#xff1a; 三、代码 1、进…

U盘格式化后数据能恢复吗?详细答案看这里!

“U盘格式化之后数据还可以恢复吗&#xff1f;这真的困扰了我好久!之前由于u盘中病毒&#xff0c;不得已将它格式化了&#xff0c;但是我还有好多视频、图片都保存在里面&#xff0c;这该怎么办呢&#xff1f;” 小小的u盘&#xff0c;可是给我们带来了很多的便利的。在互联网时…

【直播预约中】 腾讯大数据 x StarRocks|构建新一代实时湖仓

随着信息时代的兴起&#xff0c;数据已成为推动业务决策和创新的核心要素&#xff1b;结构化、半结构化等多种类型的数据呈现爆炸式增长&#xff0c;如何高效处理和分析海量数据已经成为关键挑战&#xff0c;结合传统数仓与数据湖优势的湖仓一体&#xff08;Lakehouse&#xff…

uni-app打包iOS ipa文件后不上架App store为用户提供下载解决过程记录

写在前面&#xff0c;itms-services协议是什么 itms-services协议是苹果提供的一种让iOS应用在用户设备上无线安装或升级的协议。 具体来说: itms-services表示iOS应用无线安装服务的URL方案,格式为:itms-services://?actiondownload-manifest&urlMANIFEST_URL其中MANIF…

鼠标移入展示字体操作

鼠标移入展示字体 点击删除实行删除操作&#xff0c;点击图片文字跳转产品详情的逻辑实现 <div class"allProduct-content"><template v-for"(item, index) in obj.product" :key"index"><!-- <img :src"item.image&qu…

element收缩已经展开的菜单

问题描述 VUE菜单有一个BUG&#xff0c;当我们点击其它按钮或者首页的时候&#xff0c;已经展示的一级菜单是不会自动收缩的。这个问题也导致很多开发者把一级菜单都换成了二级菜单。 错误展示 错误的效果请看下图。 解决方法 1、寻找菜单文件 因为我使用的是ruoyi的前端框…

【AI视野·今日NLP 自然语言处理论文速览 第三十五期】Mon, 18 Sep 2023

AI视野今日CS.NLP 自然语言处理论文速览 Mon, 18 Sep 2023 Totally 51 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers "Merge Conflicts!" Exploring the Impacts of External Distractors to Parametric Knowledge Gra…

Zygote Secondary:加速应用启动的未来之路

Zygote Secondary&#xff1a;加速应用启动的未来之路 1. 引言 在现代的移动应用开发中&#xff0c;启动速度和响应性能是用户体验的重要方面。然而&#xff0c;传统的 Android 进程管理方式在启动应用时会出现性能瓶颈&#xff0c;导致启动时间过长和资源占用过多。为了解决…

更新GitLab上的项目

更新GitLab上的项目 如有需要&#xff0c;请参考这篇&#xff1a;上传项目到gitlab上 1.打开终端&#xff0c;进入到本地项目的根目录。 2.如果你还没有将远程GitLab仓库添加到本地项目&#xff0c;你可以使用以下命令&#xff1a; 比如&#xff1a; git remote add origin …

聊聊API安全的重要性及治理思路

在应用程序开发过程中&#xff0c;API是一个会被经常提及的东西&#xff0c;它的全称是Application Programming Interface&#xff08;应用程序接口&#xff09;&#xff0c;一般指的是Web API&#xff0c;即&#xff1a;采用HTTP通信协议的API或者是Web应用程序对外提供的API…

vue3项目学习一:创建vue3项目

创建vue3项目 一、使用vue-cli创建vue3项目1.安装vue-cli2.创建vue3项目 二、初始化项目结构三、导入element-ui 一、使用vue-cli创建vue3项目 1.安装vue-cli 先查看是否安装vue-cli 在cmd窗口输入vue -V查看版本&#xff0c;如果出现 则说明存在vue-cli,如果出现 则需要安…

ubuntu+.net6+docker 应用部署教程

先期工作 1、本地首先安装 Docker Desktop 2、本地装linux in windows 3、生成镜像 后期工作 1、云服务器部署 生成镜像方法 1、生成Dockerfile配置文件 开发工具visual studio 2022 如果项目已经存在&#xff0c;可以选中项目&#xff0c;右键点击->选择添加Docker…

【css】如何实现自定义滚动悬浮置顶、固定表头

说到固定表头或者滚动置顶&#xff0c;我们需要认识css的两个api的2个属性&#xff1a; position: sticky; position: sticky; 是 CSS 中的一种定位方式。当应用于元素时&#xff0c;该元素在滚动时会固定在父容器的指定位置&#xff0c;直到滚动到达特定的位置或条件满足后&…

【pytest】 pytest拓展功能 pycharm PermissionError: [Errno 13] Permission denied:

目录 1. pytest-html 1.1 PermissionError: [Errno 13] Permission denied: D:\\software\\python3\\anconda3\\Lib\\site-packages\\pytest_html\\__pycache__\\tmp_ttoasov 1.2错误原因 2. 失败用例重试 3. 用例并行执行 pytest-parallel 1. pytest-html 管理员打开 A…

oracle客户端的安装(SQL Developer)

参考资料 软件首页&#xff1a;https://www.oracle.com/database/sqldeveloper/ 官方文档&#xff1a;https://docs.oracle.com/en/database/oracle/sql-developer/ 下载地址&#xff1a;https://www.oracle.com/database/sqldeveloper/technologies/download/ 安装指南&#…

Unity 2021.x及以下全版本Crack

前言 最近Unity那档子事不出来了吗&#xff0c;搞得所有人都挺烦的&#xff0c;顺便在公司内网需要我完成一个游戏的项目&#xff0c;就研究了一下如何将Unity给Crack掉。 注意所有操作应有连接外网的权限 以我选择的版本为例&#xff0c;我使用的是Unity 2021.3.5f1与Unity…