分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 软件开发

WebGL或OpenGL关于模型视图投影变换的设置技巧

发布时间:2023-09-06 02:33责任编辑:傅花花关键词:WebWebGL
???

目录

???
???????
    ???????
  • 1. 具体实例
  • ???????
  • 2. 解决方案
      ???????
    • 1) Cube.html
    • ???????
    • 2) Cube.js
    • ???????
    • 3) 运行结果
    • ???????
  • ???????
  • 3. 详细讲解
      ???????
    • 1) 模型变换
    • ???????
    • 2) 视图变换
    • ???????
    • 3) 投影变换
    • ???????
    • 4) 模型视图投影矩阵
    • ???????
  • ???????
  • 4. 存在问题
  • ???????
???

1. 具体实例

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

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

//包围盒范围var minX = 399589.072;var maxX = 400469.072;var minY = 3995118.062;var maxY = 3997558.062;var minZ = 732;var maxZ = 1268;

2. 解决方案

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

1) Cube.html

<!DOCTYPE html><html lang="zh"> ?<head> ???<meta charset="utf-8" /> ???<title>Hello cube</title> ?</head> ?<body onload="main()"> ???<canvas id="webgl" width="600" height="600"> ???Please use a browser that supports "canvas" ???</canvas> ???<script src="lib/webgl-utils.js"></script> ???<script src="lib/webgl-debug.js"></script> ???<script src="lib/cuon-utils.js"></script> ???<script src="lib/cuon-matrix.js"></script> ???<script src="Cube.js"></script> ?</body></html>

2) Cube.js

// Vertex shader programvar VSHADER_SOURCE = ???‘attribute vec4 a_Position;\n‘ + ???‘attribute vec4 a_Color;\n‘ + ???‘uniform mat4 u_MvpMatrix;\n‘ + ???‘varying vec4 v_Color;\n‘ + ???‘void main() {\n‘ + ???‘ ?gl_Position = u_MvpMatrix * a_Position;\n‘ + ???‘ ?v_Color = a_Color;\n‘ + ???‘}\n‘;// Fragment shader programvar FSHADER_SOURCE = ???‘#ifdef GL_ES\n‘ + ???‘precision mediump float;\n‘ + ???‘#endif\n‘ + ???‘varying vec4 v_Color;\n‘ + ???‘void main() {\n‘ + ???‘ ?gl_FragColor = v_Color;\n‘ + ???‘}\n‘;//包围盒范围var minX = 399589.072;var maxX = 400469.072;var minY = 3995118.062;var maxY = 3997558.062;var minZ = 732;var maxZ = 1268;//包围盒中心var cx = (minX + maxX) / 2.0;var cy = (minY + maxY) / 2.0;var cz = (minZ + maxZ) / 2.0;//当前lookAt()函数初始视点的高度var eyeHight = 2000.0;//根据视点高度算出setPerspective()函数的合理角度var fovy = (maxY - minY) / 2.0 / eyeHight;fovy = 180.0 / Math.PI * Math.atan(fovy) * 2;//setPerspective()远截面var far = 3000;//function main() { ???// Retrieve <canvas> element ???var canvas = document.getElementById(‘webgl‘); ???// Get the rendering context for WebGL ???var gl = getWebGLContext(canvas); ???if (!gl) { ???????console.log(‘Failed to get the rendering context for WebGL‘); ???????return; ???} ???// Initialize shaders ???if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) { ???????console.log(‘Failed to intialize shaders.‘); ???????return; ???} ???// Set the vertex coordinates and color ???var n = initVertexBuffers(gl); ???if (n < 0) { ???????console.log(‘Failed to set the vertex information‘); ???????return; ???} ???// Get the storage location of u_MvpMatrix ???var u_MvpMatrix = gl.getUniformLocation(gl.program, ‘u_MvpMatrix‘); ???if (!u_MvpMatrix) { ???????console.log(‘Failed to get the storage location of u_MvpMatrix‘); ???????return; ???} ???// Register the event handler ???var currentAngle = [0.0, 0.0]; // Current rotation angle ([x-axis, y-axis] degrees) ???initEventHandlers(canvas, currentAngle); ???// Set clear color and enable hidden surface removal ???gl.clearColor(0.0, 0.0, 0.0, 1.0); ???gl.enable(gl.DEPTH_TEST); ???// Start drawing ???var tick = function () { ???????//setPerspective()宽高比 ???????var aspect = canvas.width / canvas.height; ???????// ???????draw(gl, n, aspect, u_MvpMatrix, currentAngle); ???????requestAnimationFrame(tick, canvas); ???}; ???tick();}function initEventHandlers(canvas, currentAngle) { ???var dragging = false; ????????// Dragging or not ???var lastX = -1, lastY = -1; ??// Last position of the mouse ???// Mouse is pressed ???canvas.onmousedown = function (ev) { ???????var x = ev.clientX; ???????var y = ev.clientY; ???????// Start dragging if a moue is in <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.onmouseleave = function (ev) { ???????dragging = false; ???}; ???// Mouse is released ???canvas.onmouseup = function (ev) { ???????dragging = false; ???}; ???// Mouse is moved ???canvas.onmousemove = function (ev) { ???????var x = ev.clientX; ???????var y = ev.clientY; ???????if (dragging) { ???????????var factor = 100 / canvas.height; // The rotation ratio ???????????var dx = factor * (x - lastX); ???????????var dy = factor * (y - lastY); ???????????// Limit x-axis rotation angle to -90 to 90 degrees ???????????//currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 90.0), -90.0); ???????????currentAngle[0] = currentAngle[0] + dy; ???????????currentAngle[1] = currentAngle[1] + dx; ???????} ???????lastX = x, lastY = y; ???}; ???//鼠标缩放 ???canvas.onmousewheel = function (event) { ???????var lastHeight = eyeHight; ???????if (event.wheelDelta > 0) { ???????????eyeHight = Math.max(1, eyeHight - 80); ???????} else { ???????????eyeHight = eyeHight + 80; ???????} ???????far = far + eyeHight - lastHeight; ???};}function draw(gl, n, aspect, u_MvpMatrix, currentAngle) { ???//模型矩阵 ???var modelMatrix = new Matrix4(); ???modelMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0); // Rotation around x-axis ????modelMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); // Rotation around y-axis ???????modelMatrix.translate(-cx, -cy, -cz); ???//视图矩阵 ???var viewMatrix = new Matrix4(); ???viewMatrix.lookAt(0, 0, eyeHight, 0, 0, 0, 0, 1, 0); ???//投影矩阵 ???var projMatrix = new Matrix4(); ???projMatrix.setPerspective(fovy, aspect, 10, far); ???//模型视图投影矩阵 ???var mvpMatrix = new Matrix4(); ???mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix); ???// Pass the model view projection matrix to u_MvpMatrix ???gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements); ???// Clear color and depth buffer ???gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); ???// Draw the cube ???gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);}function initVertexBuffers(gl) { ???// Create a cube ???// ???v6----- v5 ???// ??/| ?????/| ???// ?v1------v0| ???// ?| | ????| | ???// ?| |v7---|-|v4 ???// ?|/ ?????|/ ???// ?v2------v3 ???var verticesColors = new Float32Array([ ???????// Vertex coordinates and color ???????maxX, maxY, maxZ, 1.0, 1.0, 1.0, ?// v0 White ???????minX, maxY, maxZ, 1.0, 0.0, 1.0, ?// v1 Magenta ???????minX, minY, maxZ, 1.0, 0.0, 0.0, ?// v2 Red ???????maxX, minY, maxZ, 1.0, 1.0, 0.0, ?// v3 Yellow ???????maxX, minY, minZ, 0.0, 1.0, 0.0, ?// v4 Green ???????maxX, maxY, minZ, 0.0, 1.0, 1.0, ?// v5 Cyan ???????minX, maxY, minZ, 0.0, 0.0, 1.0, ?// v6 Blue ???????minX, minY, minZ, 1.0, 0.0, 1.0 ??// v7 Black ???]); ???// Indices of the vertices ???var indices = new Uint8Array([ ???????0, 1, 2, 0, 2, 3, ???// front ???????0, 3, 4, 0, 4, 5, ???// right ???????0, 5, 6, 0, 6, 1, ???// up ???????1, 6, 7, 1, 7, 2, ???// left ???????7, 4, 3, 7, 3, 2, ???// down ???????4, 7, 6, 4, 6, 5 ????// back ???]); ???// Create a buffer object ???var vertexColorBuffer = gl.createBuffer(); ???var indexBuffer = gl.createBuffer(); ???if (!vertexColorBuffer || !indexBuffer) { ???????return -1; ???} ???// Write the vertex coordinates and color to the buffer object ???gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer); ???gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW); ???var FSIZE = verticesColors.BYTES_PER_ELEMENT; ???// Assign the buffer object to a_Position and enable the assignment ???var a_Position = gl.getAttribLocation(gl.program, ‘a_Position‘); ???if (a_Position < 0) { ???????console.log(‘Failed to get the storage location of a_Position‘); ???????return -1; ???} ???gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0); ???gl.enableVertexAttribArray(a_Position); ???// Assign the buffer object to a_Color and enable the assignment ???var a_Color = gl.getAttribLocation(gl.program, ‘a_Color‘); ???if (a_Color < 0) { ???????console.log(‘Failed to get the storage location of a_Color‘); ???????return -1; ???} ???gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3); ???gl.enableVertexAttribArray(a_Color); ???// Write the indices to the buffer object ???gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); ???gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW); ???return indices.length;}

3) 运行结果

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

3. 详细讲解

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

1) 模型变换

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

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

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

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

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

// Mouse is movedcanvas.onmousemove = function (ev) { ???var x = ev.clientX; ???var y = ev.clientY; ???if (dragging) { ???????var factor = 100 / canvas.height; // The rotation ratio ???????var dx = factor * (x - lastX); ???????var dy = factor * (y - lastY); ???????// Limit x-axis rotation angle to -90 to 90 degrees ???????//currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 90.0), -90.0); ???????currentAngle[0] = currentAngle[0] + dy; ???????currentAngle[1] = currentAngle[1] + dx; ???} ???lastX = x, lastY = y;};

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

2) 视图变换

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

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

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

 //鼠标缩放 canvas.onmousewheel = function (event) { ????var lastHeight = eyeHight; ????if (event.wheelDelta > 0) { ????????eyeHight = Math.max(1, eyeHight - 80); ????} else { ????????eyeHight = eyeHight + 80; ????} ?};

3) 投影变换

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

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

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

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

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

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

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

//鼠标缩放canvas.onmousewheel = function (event) { ???var lastHeight = eyeHight; ???if (event.wheelDelta > 0) { ???????eyeHight = Math.max(1, eyeHight - 80); ???} else { ???????eyeHight = eyeHight + 80; ???} ???far = far + eyeHight - lastHeight;};

4) 模型视图投影矩阵

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

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

4. 存在问题

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

WebGL或OpenGL关于模型视图投影变换的设置技巧

原文地址:https://www.cnblogs.com/charlee44/p/10393227.html

知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved