WebGL 用鼠标控制物体旋转

2023-09-21 21:20:28

目录

鼠标控制物体旋转 

如何实现物体旋转

示例程序(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)) return
  var 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 drawing
    draw(gl, n, viewProjMatrix, u_MvpMatrix, currentAngle);
    requestAnimationFrame(tick, canvas);
  };
  tick();
}

function initVertexBuffers(gl) {
  //    v6----- v5
  //   /|      /|
  //  v1------v0|
  //  | |     | |
  //  | |v7---|-|v4
  //  |/      |/
  //  v2------v3
  var vertices = new Float32Array([   // Vertex coordinates
     1.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 front
     1.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 right
     1.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 down
     1.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 coordinates
      1.0, 1.0,   0.0, 1.0,   0.0, 0.0,   1.0, 0.0,    // v0-v1-v2-v3 front
      0.0, 1.0,   0.0, 0.0,   1.0, 0.0,   1.0, 1.0,    // v0-v3-v4-v5 right
      1.0, 0.0,   1.0, 1.0,   0.0, 1.0,   0.0, 0.0,    // v0-v5-v6-v1 up
      1.0, 1.0,   0.0, 1.0,   0.0, 0.0,   1.0, 0.0,    // v1-v6-v7-v2 left
      0.0, 0.0,   1.0, 0.0,   1.0, 1.0,   0.0, 1.0,    // v7-v4-v3-v2 down
      0.0, 0.0,   1.0, 0.0,   1.0, 1.0,   0.0, 1.0     // v4-v7-v6-v5 back
  ]);
  // Indices of the vertices
  var indices = new Uint8Array([
     0, 1, 2,   0, 2, 3,    // front
     4, 5, 6,   4, 6, 7,    // right
     8, 9,10,   8,10,11,    // up
    12,13,14,  12,14,15,    // left
    16,17,18,  16,18,19,    // down
    20,21,22,  20,22,23     // back
  ]);

  var indexBuffer = gl.createBuffer();
  if (!initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position')) return -1; // Vertex coordinates
  if (!initArrayBuffer(gl, texCoords, 2, gl.FLOAT, 'a_TexCoord')) return -1;// Texture coordinates
  gl.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 pressed
    var 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 released

  canvas.onmousemove = function(ev) { // Mouse is moved
    var x = ev.clientX, y = ev.clientY;
    if (dragging) {
      var factor = 100/canvas.height; // The rotation ratio
      var 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_MvpMatrix
  g_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坐标
  // 激活纹理单元0
  gl.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传递到uSampler
  gl.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()函数进行绘制时,就绘制出了旋转后的物体。

示例效果

更多推荐

php生成随机验证码图片

1,CaptchaPicture.php用于生成画布,然后在画布上生成四位随机验证码<?phpsession_start();header("Content-type:image/png");//创建图像的格式$image_width=76;//设置图像的宽度$image_height=40;//设置图像的高度$len

线性代数与编程语言结合 基础

什么是线性代数线性代数是数学的一个分支,研究向量空间和线性变换的理论与方法。它涉及了向量、矩阵、线性方程组、线性映射等概念与运算规则。线性代数在科学和工程领域中被广泛应用,如物理学、计算机图形学、统计学、电子工程等。它提供了一种强大的工具和语言来描述和解决线性问题,比如矩阵求逆、解线性方程组、特征值和特征向量等。通过线

抖音矩阵系统源码:开发搭建

一、抖音矩阵系统源码开发概述抖音seo矩阵系统源码开发技术要求十分严格。首先,需要熟练掌握Python、Java等编程语言,具有扎实的算法基础。在此基础上,还需要具备深度学习、神经网络等相关技能,能够实现精准推荐和内容分析等功能。其次,抖音seo矩阵系统开发还需要专业的云计算技术支持,比如分布式计算、负载均衡等,以确保

智能配电房监控系统:实现配电智能化管理

智能配电房监控系统是一种基于现代信息技术,实现对配电房设备运行状态实时监控的智能化系统。它能够实时监测配电房设备的运行状态,及时发现设备故障,提高配电系统的可靠性,同时还可以实现远程监控和智能化控制,提高配电系统的效率。一、智能配电房监控系统的构成智能配电房监控系统主要由监控终端、通信网络和监控中心三部分构成。1.监控

ECharts

ECharts是一款基于JavaScript的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。ECharts提供了常规的折线图、柱状图、散点图、饼图、K线图,用于统计的盒形图,用于地理数据可视化的地图、热力图、线图,用于关系数据可视化的关系图、treemap、旭日图,多维数据可视化的平行坐标,还

Android 12 源码分析 —— 应用层 六(StatusBar的UI创建和初始化)

Android12源码分析——应用层六(StatusBar的UI创建和初始化)在前面的文章中,我们分别介绍了Layout整体布局,以及StatusBar类的初始化.前者介绍了整体上面的布局,后者介绍了三大窗口的创建的入口处,以及需要做的准备工作.现在我们分别来细化三大窗口的UI创建和初始化,首先从StatusBar窗口

【初阶数据结构】——堆排序和TopK问题

=========================================================================个人主页代码仓库C语言专栏初阶数据结构专栏Linux专栏===========================================================

在React中使用Immutable.js

Immutable的几种数据类型在React中使用Immutable.js实例扩展LodashLodash常用api分类Lodash中常用的API介绍:Lodash中api和javascript中api有什么不同react中如何使用LodashImmutable的几种数据类型List:有序索引集,类似JavaScrip

新版发布 | Cloudpods v3.10.5 和 v3.9.13 正式发布

Cloudpodsv3.10.5本期发布中,ocboot部署脚本有较多变化,首先支持以非root用户执行安装流程,其次响应社区的呼吁,增加了–stack参数,允许Allinone一键安装仅包含私有云(参数为edge)或云管(参数为cmp)的部署。本期亮点为KVM虚拟机对GPU的支持,不仅支持了虚拟机挂载图形模式的GPU

MacOS 控制固态磁盘写入量,设置定时任务监控

M1芯片的内存交换策略非常激进,导致内存较小的机型固态硬盘写入量十分恐怖,网上很多人都有类似的遭遇。如何看待8G256GM1MacBookAir使用一个月硬盘写入22TB+?而固态硬盘是有擦除、写入寿命的,一般就按100次算,256G大概就是250TB。当然,并不是说超过这个数,硬盘就坏了,只是一般超过这个数,再坏,厂

9.2 【MySQL】独立表空间结构

9.2.1区(extent)的概念对于16KB的页来说,连续的64个页就是一个区,也就是说一个区默认占用1MB空间大小。不论是系统表空间还是独立表空间,都可以看成是由若干个区组成的,每256个区被划分成一组。画个图表示就是这样:其中extent0~extent255这256个区算是第一个组,extent256~exte

热文推荐