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.rhkb.cn/news/141879.html

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

相关文章

DT 卡通材质学习 一

渐变着色器 相交线 笔刷和卡通结合使用 修改器

计算机类软件方向适合参加的比赛

前言 博主是一名计算机专业的大三学生&#xff0c;在校时候参加了很多比赛和训练营&#xff0c;现在给大家博主参加过的几个的比赛&#xff0c;希望能给大一大二的学生提供一点建议。 正文 最近也有比赛的&#xff0c;我会从时间线上来给大家推荐一些比赛&#xff0c;并且给…

大屏大概是怎么个开发法(前端)

写在前面&#xff0c;博主是个在北京打拼的码农&#xff0c;从事前端工作5年了&#xff0c;做过十多个大大小小不同类型的项目&#xff0c;最近心血来潮在这儿写点东西&#xff0c;欢迎大家多多指教。 对于文章中出现的任何错误请大家批评指出&#xff0c;一定及时修改。有任何…

AutoDL云服务器的使用

目录 简介使用流程租借连接服务器输入登录指令以及密码设置服务器环境和同步文件 使用技巧查看远程主机资源上传下载数据如果上传的文件是压缩包 我的心得将大文件用百度云盘上传&#xff0c;小文件通过同步区域上传跨实例拷贝数据如果本实例没GPU选择其他实例 简介 使用此云服…

位移贴图的实现原理

在以前的文章中介绍过GLTF编辑器 &#xff0c; 编辑器可以对模型的各种材质纹理进行编辑修改&#xff0c;但是有一些新手用户可能对这些材质纹理不太了解&#xff0c;所以我收集了一些资料对这些材质纹理做一下详细的介绍&#xff0c;今天这篇文章主要是介绍位移贴图。 1、什么…

CMOS图像传感器——Stack Pixel(2)

在去年的时候, 就写过Sony大法的Stack Pixel CMOS图像传感器——Stack Pixel_stacked-pixel cis_沧海一升的博客-CSDN博客对索尼的2-Layer Transistor Pixel技术进行了介绍_stacked-pixel cishttps://blog.csdn.net/qq_21842097/article/details/127007460 IEDM 2021 上…

【好文推荐】openGauss 5.0.0 数据库安全——全密态探究

前言 写此文章的目的&#xff0c;主要是验证&#xff1a; openGauss 5.0.0 数据库能够实现哪种加密方式的全密态全密态数据库的特点 一、全密态介绍 全密态数据库意在解决数据全生命周期的隐私保护问题&#xff0c;使得系统无论在何种业务场景和环境下&#xff0c;数据在传…

Vue中的深度监听(Deep Watch):详细解析与实际示例

Vue中的深度监听&#xff08;Deep Watch&#xff09;&#xff1a;详细解析与实际示例 Vue.js 是一款流行的前端 JavaScript 框架&#xff0c;其响应式系统是其核心特性之一。通过响应式系统&#xff0c;Vue允许开发者轻松地监听数据的变化并对其做出响应。在某些情况下&#x…

Redis的softMinEvictableIdleTimeMillis和minEvictableIdleTimeMillis参数

背景&#xff1a; Redis的softMinEvictableIdleTimeMillis&#xff0c;minEvictableIdleTimeMillis是一个令人疑惑两个参数&#xff0c;特别是当它和minIdle组合起来时就更难理解了&#xff0c;本文就来梳理下他们的之间的关系 softMinEvictableIdleTimeMillis&#xff0c;mi…

2023智慧云打印小程序源码多店铺开源版 +前端

智慧自助云打印系统/智慧云打印小程序源码 前端 这是一款全新的基于Thinkphp的最新自助打印系统&#xff0c;最新UI界面设计的云打印小程序源码

【Java 基础篇】Java网络编程实战:P2P文件共享详解

Java网络编程是现代软件开发中不可或缺的一部分&#xff0c;因为它允许不同计算机之间的数据传输和通信。在本篇博客中&#xff0c;我们将深入探讨Java中的P2P文件共享&#xff0c;包括什么是P2P文件共享、如何实现它以及一些相关的重要概念。 什么是P2P文件共享&#xff1f; …

投资理财知识分享:100个金融知识专业术语

大家好&#xff0c;我是财富智星&#xff0c;今天跟大家分享一下投资理财知识方面100个金融知识专业术语&#xff0c;希望能帮助大家了解更多金融知识。 1. 股票&#xff1a;代表对一家公司所有权的证券。 2. 债券&#xff1a;公司或政府发行的借款证券。 3. 投资组合&#xff…

2023羊城杯决赛赛后记录

文章目录 前言ez_SSTIbreakfix easyuploadbreakfix BabyMemobreakfix fuzee_rcebreakfix Oh! My PDFbreakfix easy00aeshacker黑客的秘密LmqHmAsk 前言 参加了羊城杯决赛&#xff0c;一直都没自己重新搭环境对题目做一个复现和记录&#xff0c;当时还有有几道题是没有弄出来的…

中睿天下受邀参加2023北京数字交通大会暨博览会并发表主题演讲

2023年9月11号由中国交通报社、中国交通运输协会联合主办的2023北京数字交通大会暨博览会&#xff08;简称大会暨博览会&#xff09;在北京中国国际展览中心&#xff08;新馆&#xff09;举行。这次大会主题是“数字新时代、交通新未来”&#xff0c;将聚焦数字交通创新发展&am…

如何使用show profile 查看sql的执行周期

修改配置文件/etc/my.cnf 新增一行&#xff1a;query_cache_type1 重启mysql 先开启 show variables like %profiling%; set profiling1;select * from xxx ;show profiles; #显示最近的几次查询show profile cpu,block io for query 编号 #查看程序的执行步骤

Redis原理(一):Redis数据结构(上)

文章目录 1、 Redis数据结构-动态字符串2、 Redis数据结构-intset3、 Redis数据结构-Dict4、 Redis数据结构-ZipList5、 Redis数据结构-ZipList的连锁更新问题6、 Redis数据结构-QuickList1、 Redis数据结构-动态字符串 我们都知道Redis中保存的Key是字符串,value往往是字符串…

【Java 基础篇】Java 图书管理系统详解

介绍 图书管理系统是一种用于管理图书信息、借阅记录、用户信息等的软件系统。它可以帮助图书馆、书店或个人管理和组织图书资源&#xff0c;提供了方便的借阅和查询功能。在这篇博客中&#xff0c;我们将详细介绍如何使用Java编程语言创建一个简单的图书管理系统。 功能需求…

HTML+VUE+element-ui通过点击不同按钮展现不同页面

完整源码可以私聊我&#xff0c;需要一点点费用~ 页面展示 方案一 方案二 代码解释 方案一&#xff1a;使用v-show 这种方案只适合有两种页面 现在form表单中设置好要展现的页面名&#xff08;此处的url没有作用不用管&#xff09; 通过一个两个for循环分别将form表单填入…

基于 Socket 网络编程

基于 Socket 网络编程 前言一、基于Socket的网络通信传输&#xff08;传输层&#xff09;二、UDP 的数据报套接字编程1、UDP 套接字编程 API2、使用 UDP Socket 实现简单通信 三、TCP 流套接字编程1、TCP 流套接字编程 API2、使用 TCP Socket 实现简单通信3、使用 Tcp 协议进行…

GIT 提交过滤非必要文件

1、git ls-files&#xff1a;该命令会列出Git仓库中管理的所有文件 2、git rm -r -f --cached . -f 是强制删除 最后的点 是删除所有 git rm -r --cached . 3、 进入Preferences->plugins 中下载一个(.ignore)插件  在项目本地new一个(.gitignore)的文件新增需要过滤文…