经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » OpenGL » 查看文章
WebGL或OpenGL关于模型视图投影变换的技巧
来源:cnblogs  作者:charlee44  时间:2019/2/18 9:19:19  对本文有异议

目录

1. 具体实例

看了不少的关于WebGL/OpenGL的资料,笔者发现这些资料在讲解图形变换的时候都讲了很多的原理,然后举出一个特别简单的实例(坐标是1.0,0.5的那种)来讲解。确实一看就懂,但用到实际的场景之中就一脸懵逼了(比如地形的三维坐标都是很大的数字)。所以笔者这里结合一个具体的实例,总结下WebGL/OpenGL中,关于模型变换、视图变换、投影变换的设置技巧。

绘制任何复杂的场景之前,都可以先绘制出其包围盒,能应用于包围盒的图形变换,基本上就能用于该场景了,因此,笔者这里绘制一幅地形的包围盒。它的最大最小范围为:

  1. //包围盒范围
  2. var minX = 399589.072;
  3. var maxX = 400469.072;
  4. var minY = 3995118.062;
  5. var maxY = 3997558.062;
  6. var minZ = 732;
  7. var maxZ = 1268;

2. 解决方案

WebGL是OpenGL的子集,因此我这里直接用WebGL的例子,但是各种接口函数跟OpenGL是非常类似的,尤其是图形变换的函数。

1) Cube.html

  1. <!DOCTYPE html>
  2. <html lang="zh">
  3.   <head>
  4.     <meta charset="utf-8" />
  5.     <title>Hello cube</title>
  6.   </head>
  7.  
  8.   <body onload="main()">
  9.     <canvas id="webgl" width="600" height="600">
  10.     Please use a browser that supports "canvas"
  11.     </canvas>
  12.  
  13.     <script src="lib/webgl-utils.js"></script>
  14.     <script src="lib/webgl-debug.js"></script>
  15.     <script src="lib/cuon-utils.js"></script>
  16.     <script src="lib/cuon-matrix.js"></script>
  17.     <script src="Cube.js"></script>
  18.   </body>
  19. </html>

2) Cube.js

  1. // Vertex shader program
  2. var VSHADER_SOURCE =
  3.     'attribute vec4 a_Position;\n' +
  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' +
  9.     '  v_Color = a_Color;\n' +
  10.     '}\n';
  11.  
  12. // Fragment shader program
  13. var FSHADER_SOURCE =
  14.     '#ifdef GL_ES\n' +
  15.     'precision mediump float;\n' +
  16.     '#endif\n' +
  17.     'varying vec4 v_Color;\n' +
  18.     'void main() {\n' +
  19.     '  gl_FragColor = v_Color;\n' +
  20.     '}\n';
  21.  
  22. //包围盒范围
  23. var minX = 399589.072;
  24. var maxX = 400469.072;
  25. var minY = 3995118.062;
  26. var maxY = 3997558.062;
  27. var minZ = 732;
  28. var maxZ = 1268;
  29.  
  30. //包围盒中心
  31. var cx = (minX + maxX) / 2.0;
  32. var cy = (minY + maxY) / 2.0;
  33. var cz = (minZ + maxZ) / 2.0;
  34.  
  35. //当前lookAt()函数初始视点的高度
  36. var eyeHight = 2000.0;
  37.  
  38. //根据视点高度算出setPerspective()函数的合理角度
  39. var fovy = (maxY - minY) / 2.0 / eyeHight;
  40. fovy = 180.0 / Math.PI * Math.atan(fovy) * 2;
  41.  
  42. //setPerspective()远截面
  43. var far = 3000;
  44.  
  45. //
  46. function main() {
  47.     // Retrieve <canvas> element
  48.     var canvas = document.getElementById('webgl');
  49.  
  50.     // Get the rendering context for 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.     // Initialize shaders
  58.     if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
  59.         console.log('Failed to intialize shaders.');
  60.         return;
  61.     }
  62.  
  63.     // Set the vertex coordinates and color
  64.     var n = initVertexBuffers(gl);
  65.     if (< 0) {
  66.         console.log('Failed to set the vertex information');
  67.         return;
  68.     }
  69.  
  70.     // Get the storage location of u_MvpMatrix
  71.     var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
  72.     if (!u_MvpMatrix) {
  73.         console.log('Failed to get the storage location of u_MvpMatrix');
  74.         return;
  75.     }
  76.  
  77.     // Register the event handler
  78.     var currentAngle = [0.0, 0.0]; // Current rotation angle ([x-axis, y-axis] degrees)
  79.     initEventHandlers(canvas, currentAngle);
  80.  
  81.     // Set clear color and enable hidden surface removal
  82.     gl.clearColor(0.0, 0.0, 0.0, 1.0);
  83.     gl.enable(gl.DEPTH_TEST);
  84.  
  85.     // Start drawing
  86.     var tick = function () {
  87.  
  88.         //setPerspective()宽高比
  89.         var aspect = canvas.width / canvas.height;
  90.  
  91.         //
  92.         draw(gl, n, aspect, u_MvpMatrix, currentAngle);
  93.         requestAnimationFrame(tick, canvas);
  94.     };
  95.     tick();
  96. }
  97.  
  98. function initEventHandlers(canvas, currentAngle) {
  99.     var dragging = false;         // Dragging or not
  100.     var lastX = -1, lastY = -1;   // Last position of the mouse
  101.  
  102.     // Mouse is pressed
  103.     canvas.onmousedown = function (ev) {
  104.         var x = ev.clientX;
  105.         var y = ev.clientY;
  106.         // Start dragging if a moue is in <canvas>
  107.         var rect = ev.target.getBoundingClientRect();
  108.         if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
  109.             lastX = x;
  110.             lastY = y;
  111.             dragging = true;
  112.         }
  113.     };
  114.  
  115.     //鼠标离开时
  116.     canvas.onmouseleave = function (ev) {
  117.         dragging = false;
  118.     };
  119.  
  120.     // Mouse is released
  121.     canvas.onmouseup = function (ev) {
  122.         dragging = false;
  123.     };
  124.  
  125.     // Mouse is moved
  126.     canvas.onmousemove = function (ev) {
  127.         var x = ev.clientX;
  128.         var y = ev.clientY;
  129.         if (dragging) {
  130.             var factor = 100 / canvas.height; // The rotation ratio
  131.             var dx = factor * (- lastX);
  132.             var dy = factor * (- lastY);
  133.             // Limit x-axis rotation angle to -90 to 90 degrees
  134.             //currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 90.0), -90.0);
  135.             currentAngle[0] = currentAngle[0] + dy;
  136.             currentAngle[1] = currentAngle[1] + dx;
  137.         }
  138.         lastX = x, lastY = y;
  139.     };
  140.  
  141.     //鼠标缩放
  142.     canvas.onmousewheel = function (event) {
  143.         var lastHeight = eyeHight;
  144.         if (event.wheelDelta > 0) {
  145.             eyeHight = Math.max(1, eyeHight - 80);
  146.         } else {
  147.             eyeHight = eyeHight + 80;
  148.         }
  149.  
  150.         far = far + eyeHight - lastHeight;
  151.     };
  152. }
  153.  
  154. function draw(gl, n, aspect, u_MvpMatrix, currentAngle) {
  155.     //模型矩阵
  156.     var modelMatrix = new Matrix4();
  157.     modelMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0); // Rotation around x-axis 
  158.     modelMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); // Rotation around y-axis    
  159.     modelMatrix.translate(-cx, -cy, -cz);
  160.  
  161.     //视图矩阵
  162.     var viewMatrix = new Matrix4();
  163.     viewMatrix.lookAt(0, 0, eyeHight, 0, 0, 0, 0, 1, 0);
  164.  
  165.     //投影矩阵
  166.     var projMatrix = new Matrix4();
  167.     projMatrix.setPerspective(fovy, aspect, 10, far);
  168.  
  169.     //模型视图投影矩阵
  170.     var mvpMatrix = new Matrix4();
  171.     mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
  172.  
  173.     // Pass the model view projection matrix to u_MvpMatrix
  174.     gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
  175.  
  176.     // Clear color and depth buffer
  177.     gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  178.  
  179.     // Draw the cube
  180.     gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
  181. }
  182.  
  183. function initVertexBuffers(gl) {
  184.     // Create a cube
  185.     //    v6----- v5
  186.     //   /|      /|
  187.     //  v1------v0|
  188.     //  | |     | |
  189.     //  | |v7---|-|v4
  190.     //  |/      |/
  191.     //  v2------v3
  192.  
  193.     var verticesColors = new Float32Array([
  194.         // Vertex coordinates and color
  195.         maxX, maxY, maxZ, 1.0, 1.0, 1.0,  // v0 White
  196.         minX, maxY, maxZ, 1.0, 0.0, 1.0,  // v1 Magenta
  197.         minX, minY, maxZ, 1.0, 0.0, 0.0,  // v2 Red
  198.         maxX, minY, maxZ, 1.0, 1.0, 0.0,  // v3 Yellow
  199.         maxX, minY, minZ, 0.0, 1.0, 0.0,  // v4 Green
  200.         maxX, maxY, minZ, 0.0, 1.0, 1.0,  // v5 Cyan
  201.         minX, maxY, minZ, 0.0, 0.0, 1.0,  // v6 Blue
  202.         minX, minY, minZ, 1.0, 0.0, 1.0   // v7 Black
  203.     ]);
  204.  
  205.     // Indices of the vertices
  206.     var indices = new Uint8Array([
  207.         0, 1, 2, 0, 2, 3,    // front
  208.         0, 3, 4, 0, 4, 5,    // right
  209.         0, 5, 6, 0, 6, 1,    // up
  210.         1, 6, 7, 1, 7, 2,    // left
  211.         7, 4, 3, 7, 3, 2,    // down
  212.         4, 7, 6, 4, 6, 5     // back
  213.     ]);
  214.  
  215.     // Create a buffer object
  216.     var vertexColorBuffer = gl.createBuffer();
  217.     var indexBuffer = gl.createBuffer();
  218.     if (!vertexColorBuffer || !indexBuffer) {
  219.         return -1;
  220.     }
  221.  
  222.     // Write the vertex coordinates and color to the buffer object
  223.     gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
  224.     gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
  225.  
  226.     var FSIZE = verticesColors.BYTES_PER_ELEMENT;
  227.     // Assign the buffer object to a_Position and enable the assignment
  228.     var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  229.     if (a_Position < 0) {
  230.         console.log('Failed to get the storage location of a_Position');
  231.         return -1;
  232.     }
  233.     gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
  234.     gl.enableVertexAttribArray(a_Position);
  235.     // Assign the buffer object to a_Color and enable the assignment
  236.     var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  237.     if (a_Color < 0) {
  238.         console.log('Failed to get the storage location of a_Color');
  239.         return -1;
  240.     }
  241.     gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
  242.     gl.enableVertexAttribArray(a_Color);
  243.  
  244.     // Write the indices to the buffer object
  245.     gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  246.     gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
  247.  
  248.     return indices.length;
  249. }

3) 运行结果

这份代码改进《WebGL编程指南》一书里面绘制一个简单立方体的例子,引用的几个JS-lib也是该书提供。本例全部源代码地址链接为:https://share.weiyun.com/52XmsFv ,密码:h1lbay。
用chrome打开Cube.html,会出现一个长方体的包围盒,还可以用鼠标左键旋转,鼠标滚轮缩放:

3. 详细讲解

本例的思路是通过JS的requestAnimationFrame()函数不停的调用绘制函数draw(),同时将一些变量关联到鼠标操作事件和draw(),达到页面图形变换的效果。这里笔者就不讲原理,重点讲一讲设置三个图形变换的具体过程,网上已经有非常多的原理介绍了。

1) 模型变换

在draw()函数中设置模型矩阵:

  1. //模型矩阵
  2. var modelMatrix = new Matrix4();
  3. modelMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0); // Rotation around x-axis 
  4. modelMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); // Rotation around y-axis    
  5. modelMatrix.translate(-cx, -cy, -cz);

由于这个包围盒(长方体)的坐标值都非常大,所以第一步需要对其做平移变换translate(-cx, -cy, -cz),cx,cy,cz就是包围盒的中心:

  1. //包围盒中心
  2. var cx = (minX + maxX) / 2.0;
  3. var cy = (minY + maxY) / 2.0;
  4. var cz = (minZ + maxZ) / 2.0;

接下来是旋转变换,数组currentAngle记录了绕X轴和Y轴旋转的角度,初始值为0。配合onmousedown,onmouseup,onmousemove三个鼠标事件,将页面鼠标X、Y方向的移动,转换成绕X轴,Y轴的角度值,累计到currentAngle中,从而实现了三维模型随鼠标旋转。

  1. // Mouse is moved
  2. canvas.onmousemove = function (ev) {
  3.     var x = ev.clientX;
  4.     var y = ev.clientY;
  5.     if (dragging) {
  6.         var factor = 100 / canvas.height; // The rotation ratio
  7.         var dx = factor * (- lastX);
  8.         var dy = factor * (- lastY);
  9.         // Limit x-axis rotation angle to -90 to 90 degrees
  10.         //currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 90.0), -90.0);
  11.         currentAngle[0] = currentAngle[0] + dy;
  12.         currentAngle[1] = currentAngle[1] + dx;
  13.     }
  14.     lastX = x, lastY = y;
  15. };

注意模型矩阵的平移变换要放后面,需要把坐标轴换到包围盒中心,才能绕三维模型自转。

2) 视图变换

通过lookAt()函数设置视图矩阵:

  1. //当前lookAt()函数初始视点的高度
  2. var eyeHight = 2000.0;
  3.  
  4. // …
  5.  
  6. //视图矩阵
  7. var viewMatrix = new Matrix4();
  8. viewMatrix.lookAt(0, 0, eyeHight, 0, 0, 0, 0, 1, 0);

视图变换调整的是观察者的状态,lookAt()函数分别设置了视点、目标观察点以及上方向。虽然可以在任何位置去观察三维场景的点,从而得到渲染结果。但在实际的应用当中,这个函数设置的结果很难以想象,所以笔者设置成,观察者站在包围盒中心上方的位置,对准坐标系原点(注意这个时候经过模型变换,包围盒的中心点已经是坐标系原点了),常见的Y轴作为上方向。这样,视图内无论如何都是可见的。
这里将视点的高度设置成变量eyeHight,初始值为2000,是一个大于0的经验值。同时通过鼠标的滚轮事件onmousewheel()调整该值,从而实现三维模型的缩放的:

  1.  //鼠标缩放
  2.  canvas.onmousewheel = function (event) {
  3.      var lastHeight = eyeHight;
  4.      if (event.wheelDelta > 0) {
  5.          eyeHight = Math.max(1, eyeHight - 80);
  6.      } else {
  7.          eyeHight = eyeHight + 80;
  8.      } 
  9.  };

3) 投影变换

通过setPerspective()来设置投影变换:

  1. //根据视点高度算出setPerspective()函数的合理角度
  2. var fovy = (maxY - minY) / 2.0 / eyeHight;
  3. fovy = 180.0 / Math.PI * Math.atan(fovy) * 2;
  4.  
  5. //setPerspective()远截面
  6. var far = 3000;
  7.  
  8. //setPerspective()宽高比
  9. var aspect = canvas.width / canvas.height;
  10.  
  11. //...
  12.  
  13. //投影矩阵
  14. var projMatrix = new Matrix4();
  15. projMatrix.setPerspective(fovy, aspect, 10, far);

前面的视图变换已经论述了,这个模型是在中心点上方去观察中心点,相当于视线垂直到前界面near的表面,那么setPerspective()就可以确定其角度fovy了,示意图如下:

很明显的看出,当光线射到包围盒的中心,包围盒Y方向长度的一半,除以视点高,就是fovy一般的正切值。

宽高比aspect即是页面canvas元素的宽高比。

近界面near一般设置成较近的值,但是不能太近(比如小于1),否则会影响深度判断的精度造成页面闪烁。《OpenGL绘制纹理,缩放相机导致纹理闪烁的解决方法gluPerspective ()》论述了这个问题。

而远界面far也是需要跟着鼠标滚轮一起变换的,否则当eyeHight变大,三维物体会逐渐离开透视变换的视锥体:

  1. //鼠标缩放
  2. canvas.onmousewheel = function (event) {
  3.     var lastHeight = eyeHight;
  4.     if (event.wheelDelta > 0) {
  5.         eyeHight = Math.max(1, eyeHight - 80);
  6.     } else {
  7.         eyeHight = eyeHight + 80;
  8.     }
  9.  
  10.     far = far + eyeHight - lastHeight;
  11. };

4) 模型视图投影矩阵

将三个矩阵都应用起来,就得到最终的模型视图投影矩阵。注意计算式是:投影矩阵 * 视图矩阵 * 模型矩阵:

  1. //模型视图投影矩阵
  2. var mvpMatrix = new Matrix4();
  3. mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);

4. 存在问题

本例中的三维物体随着鼠标旋转,是把鼠标X、Y方向的移动距离转换成绕X轴,Y轴方向的角度来实现的。但是如何用鼠标实现绕Z轴(第三轴)旋转呢?例如像OSG这样的渲染引擎,是可以用鼠标绕第三个轴旋转的(当然操作有点费力)。这里希望大家能批评指正下。

原文链接:http://www.cnblogs.com/charlee44/p/10393227.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号