经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » WebGL » 查看文章
WebGL简易教程(八):三维场景交互
来源:cnblogs  作者:charlee44  时间:2019/10/8 9:15:19  对本文有异议

1. 概述

在上一篇教程《WebGL简易教程(七):绘制一个矩形体》中,通过一个绘制矩形包围盒的实例,进一步理解了模型视图投影变换。其实,三维场景的UI交互工作正是基于模型视图投影变换的基础之上的。这里就通过之前的知识实现一个三维场景的浏览实例:通过鼠标实现场景的旋转和缩放。

2. 实例

改进上一篇教程的JS代码,得到新的代码如下:

  1. // 顶点着色器程序
  2. var VSHADER_SOURCE =
  3. 'attribute vec4 a_Position;\n' + // attribute variable
  4. 'attribute vec4 a_Color;\n' +
  5. 'uniform mat4 u_MvpMatrix;\n' +
  6. 'varying vec4 v_Color;\n' +
  7. 'void main() {\n' +
  8. ' gl_Position = u_MvpMatrix * a_Position;\n' + // Set the vertex coordinates of the point
  9. ' v_Color = a_Color;\n' +
  10. '}\n';
  11. // 片元着色器程序
  12. var FSHADER_SOURCE =
  13. 'precision mediump float;\n' +
  14. 'varying vec4 v_Color;\n' +
  15. 'void main() {\n' +
  16. ' gl_FragColor = v_Color;\n' +
  17. '}\n';
  18. //定义一个矩形体:混合构造函数原型模式
  19. function Cuboid(minX, maxX, minY, maxY, minZ, maxZ) {
  20. this.minX = minX;
  21. this.maxX = maxX;
  22. this.minY = minY;
  23. this.maxY = maxY;
  24. this.minZ = minZ;
  25. this.maxZ = maxZ;
  26. }
  27. Cuboid.prototype = {
  28. constructor: Cuboid,
  29. CenterX: function () {
  30. return (this.minX + this.maxX) / 2.0;
  31. },
  32. CenterY: function () {
  33. return (this.minY + this.maxY) / 2.0;
  34. },
  35. CenterZ: function () {
  36. return (this.minZ + this.maxZ) / 2.0;
  37. },
  38. LengthX: function () {
  39. return (this.maxX - this.minX);
  40. },
  41. LengthY: function () {
  42. return (this.maxY - this.minY);
  43. }
  44. }
  45. var currentAngle = [0.0, 0.0]; // 绕X轴Y轴的旋转角度 ([x-axis, y-axis])
  46. var curScale = 1.0; //当前的缩放比例
  47. function main() {
  48. // 获取 <canvas> 元素
  49. var canvas = document.getElementById('webgl');
  50. // 获取WebGL渲染上下文
  51. var gl = getWebGLContext(canvas);
  52. if (!gl) {
  53. console.log('Failed to get the rendering context for WebGL');
  54. return;
  55. }
  56. // 初始化着色器
  57. if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
  58. console.log('Failed to intialize shaders.');
  59. return;
  60. }
  61. // 设置顶点位置
  62. var cuboid = new Cuboid(399589.072, 400469.072, 3995118.062, 3997558.062, 732, 1268);
  63. var n = initVertexBuffers(gl, cuboid);
  64. if (n < 0) {
  65. console.log('Failed to set the positions of the vertices');
  66. return;
  67. }
  68. //注册鼠标事件
  69. initEventHandlers(canvas);
  70. // 指定清空<canvas>的颜色
  71. gl.clearColor(0.0, 0.0, 0.0, 1.0);
  72. // 开启深度测试
  73. gl.enable(gl.DEPTH_TEST);
  74. //绘制函数
  75. var tick = function () {
  76. //设置MVP矩阵
  77. setMVPMatrix(gl, canvas, cuboid);
  78. //清空颜色和深度缓冲区
  79. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  80. //绘制矩形体
  81. gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
  82. //请求浏览器调用tick
  83. requestAnimationFrame(tick);
  84. };
  85. //开始绘制
  86. tick();
  87. // 绘制矩形体
  88. gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
  89. }
  90. //注册鼠标事件
  91. function initEventHandlers(canvas) {
  92. var dragging = false; // Dragging or not
  93. var lastX = -1, lastY = -1; // Last position of the mouse
  94. //鼠标按下
  95. canvas.onmousedown = function (ev) {
  96. var x = ev.clientX;
  97. var y = ev.clientY;
  98. // Start dragging if a moue is in <canvas>
  99. var rect = ev.target.getBoundingClientRect();
  100. if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
  101. lastX = x;
  102. lastY = y;
  103. dragging = true;
  104. }
  105. };
  106. //鼠标离开时
  107. canvas.onmouseleave = function (ev) {
  108. dragging = false;
  109. };
  110. //鼠标释放
  111. canvas.onmouseup = function (ev) {
  112. dragging = false;
  113. };
  114. //鼠标移动
  115. canvas.onmousemove = function (ev) {
  116. var x = ev.clientX;
  117. var y = ev.clientY;
  118. if (dragging) {
  119. var factor = 100 / canvas.height; // The rotation ratio
  120. var dx = factor * (x - lastX);
  121. var dy = factor * (y - lastY);
  122. currentAngle[0] = currentAngle[0] + dy;
  123. currentAngle[1] = currentAngle[1] + dx;
  124. }
  125. lastX = x, lastY = y;
  126. };
  127. //鼠标缩放
  128. canvas.onmousewheel = function (event) {
  129. if (event.wheelDelta > 0) {
  130. curScale = curScale * 1.1;
  131. } else {
  132. curScale = curScale * 0.9;
  133. }
  134. };
  135. }
  136. //设置MVP矩阵
  137. function setMVPMatrix(gl, canvas, cuboid) {
  138. // Get the storage location of u_MvpMatrix
  139. var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
  140. if (!u_MvpMatrix) {
  141. console.log('Failed to get the storage location of u_MvpMatrix');
  142. return;
  143. }
  144. //模型矩阵
  145. var modelMatrix = new Matrix4();
  146. modelMatrix.scale(curScale, curScale, curScale);
  147. modelMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0); // Rotation around x-axis
  148. modelMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); // Rotation around y-axis
  149. modelMatrix.translate(-cuboid.CenterX(), -cuboid.CenterY(), -cuboid.CenterZ());
  150. //投影矩阵
  151. var fovy = 60;
  152. var near = 1;
  153. var projMatrix = new Matrix4();
  154. projMatrix.setPerspective(fovy, canvas.width / canvas.height, 1, 10000);
  155. //计算lookAt()函数初始视点的高度
  156. var angle = fovy / 2 * Math.PI / 180.0;
  157. var eyeHight = (cuboid.LengthY() * 1.2) / 2.0 / angle;
  158. //视图矩阵
  159. var viewMatrix = new Matrix4(); // View matrix
  160. viewMatrix.lookAt(0, 0, eyeHight, 0, 0, 0, 0, 1, 0);
  161. //MVP矩阵
  162. var mvpMatrix = new Matrix4();
  163. mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
  164. //将MVP矩阵传输到着色器的uniform变量u_MvpMatrix
  165. gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
  166. }
  167. //
  168. function initVertexBuffers(gl, cuboid) {
  169. // Create a cube
  170. // v6----- v5
  171. // /| /|
  172. // v1------v0|
  173. // | | | |
  174. // | |v7---|-|v4
  175. // |/ |/
  176. // v2------v3
  177. // 顶点坐标和颜色
  178. var verticesColors = new Float32Array([
  179. cuboid.maxX, cuboid.maxY, cuboid.maxZ, 1.0, 1.0, 1.0, // v0 White
  180. cuboid.minX, cuboid.maxY, cuboid.maxZ, 1.0, 0.0, 1.0, // v1 Magenta
  181. cuboid.minX, cuboid.minY, cuboid.maxZ, 1.0, 0.0, 0.0, // v2 Red
  182. cuboid.maxX, cuboid.minY, cuboid.maxZ, 1.0, 1.0, 0.0, // v3 Yellow
  183. cuboid.maxX, cuboid.minY, cuboid.minZ, 0.0, 1.0, 0.0, // v4 Green
  184. cuboid.maxX, cuboid.maxY, cuboid.minZ, 0.0, 1.0, 1.0, // v5 Cyan
  185. cuboid.minX, cuboid.maxY, cuboid.minZ, 0.0, 0.0, 1.0, // v6 Blue
  186. cuboid.minX, cuboid.minY, cuboid.minZ, 1.0, 0.0, 1.0 // v7 Black
  187. ]);
  188. //顶点索引
  189. var indices = new Uint8Array([
  190. 0, 1, 2, 0, 2, 3, // 前
  191. 0, 3, 4, 0, 4, 5, // 右
  192. 0, 5, 6, 0, 6, 1, // 上
  193. 1, 6, 7, 1, 7, 2, // 左
  194. 7, 4, 3, 7, 3, 2, // 下
  195. 4, 7, 6, 4, 6, 5 // 后
  196. ]);
  197. //
  198. var FSIZE = verticesColors.BYTES_PER_ELEMENT; //数组中每个元素的字节数
  199. // 创建缓冲区对象
  200. var vertexColorBuffer = gl.createBuffer();
  201. var indexBuffer = gl.createBuffer();
  202. if (!vertexColorBuffer || !indexBuffer) {
  203. console.log('Failed to create the buffer object');
  204. return -1;
  205. }
  206. // 将缓冲区对象绑定到目标
  207. gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
  208. // 向缓冲区对象写入数据
  209. gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
  210. //获取着色器中attribute变量a_Position的地址
  211. var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  212. if (a_Position < 0) {
  213. console.log('Failed to get the storage location of a_Position');
  214. return -1;
  215. }
  216. // 将缓冲区对象分配给a_Position变量
  217. gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
  218. // 连接a_Position变量与分配给它的缓冲区对象
  219. gl.enableVertexAttribArray(a_Position);
  220. //获取着色器中attribute变量a_Color的地址
  221. var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  222. if (a_Color < 0) {
  223. console.log('Failed to get the storage location of a_Color');
  224. return -1;
  225. }
  226. // 将缓冲区对象分配给a_Color变量
  227. gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
  228. // 连接a_Color变量与分配给它的缓冲区对象
  229. gl.enableVertexAttribArray(a_Color);
  230. // 将顶点索引写入到缓冲区对象
  231. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  232. gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
  233. return indices.length;
  234. }

与之前的代码相比,这里主要改进了两个方面的内容:重绘刷新和鼠标事件调整参数。

2.1. 重绘刷新

与之前只绘制一次场景不同,为了满足浏览交互工作,页面就必须实时刷新,来满足不同的鼠标、键盘事件对场景的影响。可以使用JS的requestAnimationFrame()函数进行定时重绘刷新操作。其函数定义如下:

在代码中的实现如下:

  1. //绘制函数
  2. var tick = function () {
  3. //设置MVP矩阵
  4. setMVPMatrix(gl, canvas, cuboid);
  5. //清空颜色和深度缓冲区
  6. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  7. //绘制矩形体
  8. gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
  9. //请求浏览器调用tick
  10. requestAnimationFrame(tick);
  11. };
  12. //开始绘制
  13. tick();

在这段代码中,定义了一个绘制函数tick(),而在该函数的结束处,调用了requestAnimationFrame()函数来向浏览器请求调用其回调函数,也就是tick()。以此循环往复,页面会不停的请求调用绘制tick(),从而带到了重绘刷新的效果。

前面提到过,重绘刷新每一帧之前,都要清空颜色缓冲区和深度缓冲区,不让上一帧的效果影响到下一帧。同理,MVP矩阵也是每绘制一帧之前就需要重新设置的。

2.2. 鼠标事件调整参数

在设置MVP矩阵函数setMVPMatrix()中,可以发现视图矩阵和投影矩阵都是初次计算好就固定的,只有模型矩阵随着变量currentAngle和curScale变化而变化,相关代码如下:

  1. var currentAngle = [0.0, 0.0]; // 绕X轴Y轴的旋转角度 ([x-axis, y-axis])
  2. var curScale = 1.0; //当前的缩放比例
  3. //设置MVP矩阵
  4. function setMVPMatrix(gl, canvas, cuboid) {
  5. //...
  6. //模型矩阵
  7. var modelMatrix = new Matrix4();
  8. modelMatrix.scale(curScale, curScale, curScale);
  9. modelMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0); // Rotation around x-axis
  10. modelMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); // Rotation around y-axis
  11. modelMatrix.translate(-cuboid.CenterX(), -cuboid.CenterY(), -cuboid.CenterZ());
  12. //...
  13. }

currentAngle和curScale是预先定义的全局变量,它们在函数initEventHandlers中被设置。在initEventHandlers函数中,注册了画布元素canvas的鼠标事件。当鼠标在画布视图中拖动的时候,currentAngle根据鼠标在X、Y方向上位移变化而变化:

  1. //鼠标按下
  2. canvas.onmousedown = function (ev) {
  3. var x = ev.clientX;
  4. var y = ev.clientY;
  5. // Start dragging if a moue is in <canvas>
  6. var rect = ev.target.getBoundingClientRect();
  7. if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
  8. lastX = x;
  9. lastY = y;
  10. dragging = true;
  11. }
  12. };
  13. //...
  14. //鼠标移动
  15. canvas.onmousemove = function (ev) {
  16. var x = ev.clientX;
  17. var y = ev.clientY;
  18. if (dragging) {
  19. var factor = 100 / canvas.height; // The rotation ratio
  20. var dx = factor * (x - lastX);
  21. var dy = factor * (y - lastY);
  22. currentAngle[0] = currentAngle[0] + dy;
  23. currentAngle[1] = currentAngle[1] + dx;
  24. }
  25. lastX = x, lastY = y;
  26. };

当鼠标在画布上滑动滚轮的时候,curScale根据滚动的幅度变化而变化:

  1. //鼠标缩放
  2. canvas.onmousewheel = function (event) {
  3. if (event.wheelDelta > 0) {
  4. curScale = curScale * 1.1;
  5. } else {
  6. curScale = curScale * 0.9;
  7. }
  8. };

currentAngle和curScale的变化使得模型矩阵发生改变,而每绘制一帧就会重新设置MVP矩阵,这就使得三维场景随着鼠标操作而变化,从而完成交互操作。

3. 结果

在浏览器中打开对应的HTML文件,运行结果如下:

4. 参考

本来部分代码和插图来自《WebGL编程指南》,源代码链接:地址 。会在此共享目录中持续更新后续的内容。

原文链接:http://www.cnblogs.com/charlee44/p/11628857.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号