• WebGL 用鼠标控制物体旋转


    目录

    鼠标控制物体旋转 

    如何实现物体旋转

    示例程序(RotateObject.js)

    代码详解

    示例效果


    鼠标控制物体旋转 

    有时候,WebGL程序需要让用户通过鼠标操作三维物体。这一节来分析示例程序RotateObject,该程序允许用户通过拖动(即按住左键移动)鼠标旋转三维物体。为了简单,示例程序中的三维物体是一个立方体,但拖曳鼠标旋转物体的方法却适用于所有物体。下图显示了程序的运行效果,立方体上贴有纹理图像。

    如何实现物体旋转

    我们已经知道如何旋转二维图形或三维物体了:就是使用模型视图投影矩阵来变换顶点的坐标。现在需要使用鼠标来控制物体旋转,就需要根据鼠标的移动情况创建旋转矩阵,更新模型视图投影矩阵,并对物体的顶点坐标进行变换。 

    我们可以这样来实现:在鼠标左键按下时记录鼠标的初始坐标,然后在鼠标移动的时候用当前坐标减去初始坐标,获得鼠标的位移,然后根据这个位移来计算旋转矩阵。显然,我们需要监听鼠标的移动事件,并在事件响应函数中计算鼠标的位移、旋转矩阵,从而旋转立方体。下面看一下示例程序。

    示例程序(RotateObject.js)

    如下显示了示例程序的代码,如你所见,着色器部分没什么特别的。顶点着色器使用模型视图投影矩阵变换顶点坐标(第7行),并向片元着色器传入纹理坐标以映射纹理(第8行)。

    1. var VSHADER_SOURCE =
    2. 'attribute vec4 a_Position;\n' +
    3. 'attribute vec2 a_TexCoord;\n' +
    4. 'uniform mat4 u_MvpMatrix;\n' +
    5. 'varying vec2 v_TexCoord;\n' +
    6. 'void main() {\n' +
    7. ' gl_Position = u_MvpMatrix * a_Position;\n' +
    8. ' v_TexCoord = a_TexCoord;\n' +
    9. '}\n';
    10. var FSHADER_SOURCE =
    11. '#ifdef GL_ES\n' +
    12. 'precision mediump float;\n' +
    13. '#endif\n' +
    14. 'uniform sampler2D u_Sampler;\n' +
    15. 'varying vec2 v_TexCoord;\n' +
    16. 'void main() {\n' +
    17. ' gl_FragColor = texture2D(u_Sampler, v_TexCoord);\n' +
    18. '}\n';
    19. function main() {
    20. var canvas = document.getElementById('webgl');
    21. var gl = getWebGLContext(canvas);
    22. if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) return
    23. var n = initVertexBuffers(gl);
    24. gl.clearColor(0.0, 0.0, 0.0, 1.0);
    25. gl.enable(gl.DEPTH_TEST);
    26. var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
    27. var viewProjMatrix = new Matrix4();
    28. viewProjMatrix.setPerspective(30.0, canvas.width / canvas.height, 1.0, 100.0);
    29. viewProjMatrix.lookAt(3.0, 3.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
    30. // 注册事件处理函数
    31. var currentAngle = [0.0, 0.0]; // 绕z轴旋转角度,绕y轴旋转角度
    32. initEventHandlers(canvas, currentAngle);
    33. if (!initTextures(gl)) return // 设置纹理
    34. var tick = function() { // Start drawing
    35. draw(gl, n, viewProjMatrix, u_MvpMatrix, currentAngle);
    36. requestAnimationFrame(tick, canvas);
    37. };
    38. tick();
    39. }
    40. function initVertexBuffers(gl) {
    41. // v6----- v5
    42. // /| /|
    43. // v1------v0|
    44. // | | | |
    45. // | |v7---|-|v4
    46. // |/ |/
    47. // v2------v3
    48. var vertices = new Float32Array([ // Vertex coordinates
    49. 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
    50. 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
    51. 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
    52. -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
    53. -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
    54. 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
    55. ]);
    56. var texCoords = new Float32Array([ // Texture coordinates
    57. 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v1-v2-v3 front
    58. 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, // v0-v3-v4-v5 right
    59. 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, // v0-v5-v6-v1 up
    60. 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v1-v6-v7-v2 left
    61. 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // v7-v4-v3-v2 down
    62. 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 // v4-v7-v6-v5 back
    63. ]);
    64. // Indices of the vertices
    65. var indices = new Uint8Array([
    66. 0, 1, 2, 0, 2, 3, // front
    67. 4, 5, 6, 4, 6, 7, // right
    68. 8, 9,10, 8,10,11, // up
    69. 12,13,14, 12,14,15, // left
    70. 16,17,18, 16,18,19, // down
    71. 20,21,22, 20,22,23 // back
    72. ]);
    73. var indexBuffer = gl.createBuffer();
    74. if (!initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position')) return -1; // Vertex coordinates
    75. if (!initArrayBuffer(gl, texCoords, 2, gl.FLOAT, 'a_TexCoord')) return -1;// Texture coordinates
    76. gl.bindBuffer(gl.ARRAY_BUFFER, null);
    77. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    78. gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
    79. return indices.length;
    80. }
    81. function initEventHandlers(canvas, currentAngle) {
    82. var dragging = false; // 是否在拖动
    83. var lastX = -1, lastY = -1; // 鼠标的开始位置
    84. canvas.onmousedown = function(ev) { // Mouse is pressed
    85. var x = ev.clientX, y = ev.clientY;
    86. // 如果鼠标在canvas内就开始拖动
    87. var rect = ev.target.getBoundingClientRect();
    88. if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
    89. lastX = x; lastY = y;
    90. dragging = true;
    91. }
    92. };
    93. canvas.onmouseup = function(ev) { dragging = false; }; // Mouse is released
    94. canvas.onmousemove = function(ev) { // Mouse is moved
    95. var x = ev.clientX, y = ev.clientY;
    96. if (dragging) {
    97. var factor = 100/canvas.height; // The rotation ratio
    98. var dx = factor * (x - lastX);
    99. var dy = factor * (y - lastY);
    100. // 将x轴旋转角度限制为-90到90度
    101. currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 90.0), -90.0);
    102. currentAngle[1] = currentAngle[1] + dx; // 拿y轴举例,鼠标水平移动,物体会以Y轴旋转,所以水平的移动距离直接影响y轴要转动角度
    103. }
    104. lastX = x, lastY = y;
    105. };
    106. }
    107. var g_MvpMatrix = new Matrix4(); // 模型视图投影矩阵
    108. function draw(gl, n, viewProjMatrix, u_MvpMatrix, currentAngle) {
    109. // 计算模型视图投影矩阵并将其传递给u_MvpMatrix
    110. g_MvpMatrix.set(viewProjMatrix);
    111. g_MvpMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0); // 绕x轴旋转
    112. g_MvpMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); // 绕y轴旋转
    113. gl.uniformMatrix4fv(u_MvpMatrix, false, g_MvpMatrix.elements);
    114. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 清除颜色|深度缓冲
    115. gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0); // 画画
    116. }
    117. function initArrayBuffer(gl, data, num, type, attribute) {
    118. var buffer = gl.createBuffer();
    119. gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    120. gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
    121. var a_attribute = gl.getAttribLocation(gl.program, attribute);
    122. gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
    123. gl.enableVertexAttribArray(a_attribute);
    124. return true;
    125. }
    126. function initTextures(gl) {
    127. var texture = gl.createTexture(); // 创建温丽丽对象
    128. var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler'); // 获取uSampler的存储位置(_S)
    129. var image = new Image();
    130. image.onload = function(){ loadTexture(gl, texture, u_Sampler, image); };
    131. image.src = '../resources/sky.jpg';
    132. return true;
    133. }
    134. function loadTexture(gl, texture, u_Sampler, image) {
    135. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 翻转图像Y坐标
    136. // 激活纹理单元0
    137. gl.activeTexture(gl.TEXTURE0);
    138. // 将纹理对象绑定到2维目标(先绑定到纹理单元)
    139. gl.bindTexture(gl.TEXTURE_2D, texture);
    140. // 设置纹理参数
    141. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    142. // 将图像设置为纹理,设置图像参数
    143. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
    144. // 将纹理单元0传递到uSampler
    145. gl.uniform1i(u_Sampler, 0);
    146. }

    代码详解

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

    示例效果

  • 相关阅读:
    队列的基本操作以及C语言实现
    批量压缩图片软件-免费图片压缩后高清无损
    基于SSM实现微博系统
    计算机指令集详解(RISC 和 CISC)
    mysql 计算两个坐标距离
    Redis主从结构数据同步分析
    学习尚硅谷HTML+CSS总结
    keepalived+nginx高可用 脑裂监控
    JavaSE - 深度探讨继承与多态,私有成员是否被继承问题
    C++的类型转换
  • 原文地址:https://blog.csdn.net/dabaooooq/article/details/133148458