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

目录

  • 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 >
    <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 program
var 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 program
var 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 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;
};

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

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这样的渲染引擎,是可以用鼠标绕第三个轴旋转的(当然操作有点费力)。这里希望大家能批评指正下。

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

时间: 2024-10-11 03:40:51

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

WebGL简易教程(六):第一个三维示例(使用模型视图投影变换)

目录 1. 概述 2. 示例:绘制多个三角形 2.1. Triangle_MVPMatrix.html 2.2. Triangle_MVPMatrix.js 2.2.1. 数据加入Z值 2.2.2. 加入深度测试 2.2.3. MVP矩阵设置 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL简易教程(五):图形变换(模型.视图.投影变换)>中,详细讲解了OpenGL\WebGL关于绘制场景的模型变换.视图变换以及投影变换的过程.不过那篇教程是纯理论知识,这里就具体结合一个实际的例子

Opengl使用模型视图变换移动光源

光源绕一个物体旋转,按下鼠标左键时,光源位置旋转. #include <GL/glut.h> static int spin = 0;static GLdouble x_1 = 0.0;static GLdouble y_1 = 0.0;static GLdouble z_1 = 0.0;void init(void){ glClearColor(0.0,0.0,0.0,0.0); glShadeModel(GL_SMOOTH); glEnable(GL_LIGHTING); glEnable

【GISER&amp;&amp;Painter】Chapter02:WebGL中的模型视图变换

上一节我们提到了如何在一张画布上画一个简单几何图形,通过创建画布,获取WebGLRendering上下文,创建一个简单的着色器,然后将一些顶点数据绑定到gl的Buffer中,最后通过绑定buffer数据,提供buffer中顶点数据的情况,执行渲染绘制方法,将数据结果从buffer中刷新到帧缓存中.整个流程十分清晰明了,可是通过对比原来OpenGL中的整个流程,我们会发现其中还缺少了一些很重要的处理步骤,虽然我们创建了属于自己的着色器,可并没有对顶点数据进行类似于顶点处理管线中的模型视图变换.透视

清华版CG 实验5 OpenGL模型视图变换

1.实验目的: 理解掌握OpenGL程序的模型视图变换. 2.实验内容: (1)阅读实验原理,运行示范实验代码,理解掌握OpenGL程序的模型视图变换: (2)根据示范代码,尝试完成实验作业: 3.实验原理: 在OpenGL程序中,视图变换必须出现在模型变换之前,但可以在绘图之前的任何时候执行投影变换和视口变换. 1.在指定的视图变换之前,应该使用glLoadIdentity()函数把当前矩阵设置为单位矩阵. 2.在载入单位矩阵之后,使用gluLookAt()函数指定视图变换.如果程序没有调用g

OpenGL学习笔记——视图

顶点变换的步骤: 视图与模型变换一起组成了模型视图矩阵,这个矩阵作用于物体坐标,产生视觉坐标.紧接着,如果指定了其他的裁剪平面,用于从场景中删除某些物体或者提供物体的裁剪视图,这些裁剪平面会在这个时候生效.之后,OpenGL使用投影矩阵产生了裁剪坐标.这个变换定义了一个视景体,位于这个空间外的物体将会被裁剪掉.随后发生的是透视除法,它把坐标除以w,产生规范化的设备坐标.最后,经过变换的坐标经过视口变换成为窗口坐标.可以通过控制视口的大小对最终的图像进行放大.缩小和拉伸(cocos2dx的屏幕适配

opengl漫游模型示例

opengl漫游模型示例: #include <stdio.h> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtx/transform.hpp> #include <GLFW/glfw3.h> #include "TrackballController.h" #define CIGAMA 0.1 TrackballController* TrackballControlle

第55课 模型视图设计模式

1. 模型视图模式 (1)模型视图设计模式的核心思想 ①模型(数据)与视图(显示)相分离 ②模型对外提供标准接口存取数据(不关心数据如何显示) ③视图自定义数据的显示方式(不关心数据如何组织存储) (2)模型视图模式的直观理解 (3)模型视图模式的工作机制 ①当数据发生改变时,模型发出信号通知视图 ②当用户与视图进行交互时,视图发出信号提供交互信息 2. Qt中的模型-视图类层次结构 (1)Qt中的模型类的层次结构 (2)Qt中的视图类的层次结构 3. 关键技术问题 (1)模型如何为数据提供统一

Qt 学习之路:模型-视图高级技术

PathView PathView是 QtQuick 中最强大的视图,同时也是最复杂的.PathView允许创建一种更灵活的视图.在这种视图中,数据项并不是方方正正,而是可以沿着任意路径布局.沿着同一布局路径,数据项的属性可以被更详细的设置,例如缩放.透明度等. 使用PathView首先需要定义一个代理和一个路径.除此之外,PathView还可以设置很多其它属性,其中最普遍的是pathItemCount,用于设置可视数据项的数目:preferredHighlightBegin.preferred

学习模型-视图-控制器MVC模式

MVC简介: MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式.MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式: Model(模型)表示应用程序核心(比如数据库记录列表). View(视图)显示数据(数据库记录). Controller(控制器)处理输入(写入数据库记录). MVC 模式同时提供了对 HTML.