zoukankan      html  css  js  c++  java
  • WebGL简易教程(六):第一个三维示例(使用模型视图投影变换)

    1. 概述

    在上一篇教程《WebGL简易教程(五):图形变换(模型、视图、投影变换)》中,详细讲解了OpenGLWebGL关于绘制场景的模型变换、视图变换以及投影变换的过程。不过那篇教程是纯理论知识,这里就具体结合一个实际的例子,进一步理解WebGL中是如何通过图形变换让一个真正的三维场景显示出来。

    2. 示例:绘制多个三角形

    继续改进之前的代码,这次就更进一步,在一个场景中绘制了三个三角形。

    2.1. Triangle_MVPMatrix.html

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <title>Hello Triangle</title>
      </head>
    
      <body onload="main()">
        <canvas id="webgl" width="400" height="400">
        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="Triangle_MVPMatrix.js"></script>
      </body>
    </html>
    

    与之间的代码相比,这段代码主要是引入了一个cuon-matrix.js,这个是一个图形矩阵的处理库,能够方便与GLSL进行交互。

    2.2. Triangle_MVPMatrix.js

    // 顶点着色器程序
    var VSHADER_SOURCE =
      'attribute vec4 a_Position;
    ' + // attribute variable
      'attribute vec4 a_Color;
    ' +
      'uniform mat4 u_MvpMatrix;
    ' +
      'varying vec4 v_Color;
    ' +
      'void main() {
    ' +
      '  gl_Position = u_MvpMatrix * a_Position;
    ' + // Set the vertex coordinates of the point
      '  v_Color = a_Color;
    ' +
      '}
    ';
    
    // 片元着色器程序
    var FSHADER_SOURCE =
      'precision mediump float;
    ' +
      'varying vec4 v_Color;
    ' +
      'void main() {
    ' +
      '  gl_FragColor = v_Color;
    ' +
      '}
    ';
    
    function main() {
      // 获取 <canvas> 元素
      var canvas = document.getElementById('webgl');
    
      // 获取WebGL渲染上下文
      var gl = getWebGLContext(canvas);
      if (!gl) {
        console.log('Failed to get the rendering context for WebGL');
        return;
      }
    
      // 初始化着色器
      if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Failed to intialize shaders.');
        return;
      }
    
      // 设置顶点位置
      var n = initVertexBuffers(gl);
      if (n < 0) {
        console.log('Failed to set the positions of the vertices');
        return;
      }
    
      //设置MVP矩阵
      setMVPMatrix(gl,canvas);
    
      // 指定清空<canvas>的颜色
      gl.clearColor(0.0, 0.0, 0.0, 1.0);
    
      // 开启深度测试
      gl.enable(gl.DEPTH_TEST);
    
      // 清空颜色和深度缓冲区
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
      // 绘制三角形
      gl.drawArrays(gl.TRIANGLES, 0, n);
    }
    
    //设置MVP矩阵
    function setMVPMatrix(gl,canvas) {
      // 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;
      }
    
      //模型矩阵
      var modelMatrix = new Matrix4();
      modelMatrix.setTranslate(0.75, 0, 0);
    
      //视图矩阵
      var viewMatrix = new Matrix4();  // View matrix
      viewMatrix.setLookAt(0, 0, 5, 0, 0, -100, 0, 1, 0);
    
      //投影矩阵
      var projMatrix = new Matrix4();  // Projection matrix
      projMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100);
    
      //MVP矩阵
      var mvpMatrix = new Matrix4();
      mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
    
      //将MVP矩阵传输到着色器的uniform变量u_MvpMatrix
      gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
    }
    
    //
    function initVertexBuffers(gl) {
      // 顶点坐标和颜色
      var verticesColors = new Float32Array([
        0.0, 1.0, -4.0, 0.4, 1.0, 0.4,  //绿色在后
        -0.5, -1.0, -4.0, 0.4, 1.0, 0.4,
        0.5, -1.0, -4.0, 1.0, 0.4, 0.4,
    
        0.0, 1.0, -2.0, 1.0, 1.0, 0.4, //黄色在中
        -0.5, -1.0, -2.0, 1.0, 1.0, 0.4,
        0.5, -1.0, -2.0, 1.0, 0.4, 0.4,
    
        0.0, 1.0, 0.0, 0.4, 0.4, 1.0,  //蓝色在前
        -0.5, -1.0, 0.0, 0.4, 0.4, 1.0,
        0.5, -1.0, 0.0, 1.0, 0.4, 0.4,
      ]);
    
      //
      var n = 9; // 点的个数
      var FSIZE = verticesColors.BYTES_PER_ELEMENT;   //数组中每个元素的字节数
    
      // 创建缓冲区对象
      var vertexBuffer = gl.createBuffer();
      if (!vertexBuffer) {
        console.log('Failed to create the buffer object');
        return -1;
      }
    
      // 将缓冲区对象绑定到目标
      gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
      // 向缓冲区对象写入数据
      gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
    
      //获取着色器中attribute变量a_Position的地址 
      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;
      }
      // 将缓冲区对象分配给a_Position变量
      gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
    
      // 连接a_Position变量与分配给它的缓冲区对象
      gl.enableVertexAttribArray(a_Position);
    
      //获取着色器中attribute变量a_Color的地址 
      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;
      }
      // 将缓冲区对象分配给a_Color变量
      gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
      // 连接a_Color变量与分配给它的缓冲区对象
      gl.enableVertexAttribArray(a_Color);
    
      // 解除绑定
      gl.bindBuffer(gl.ARRAY_BUFFER, null);
    
      return n;
    }
    

    相比之前的代码,主要做了3点改进:

    1. 数据加入Z值;
    2. 加入了深度测试;
    3. MVP矩阵设置;

    2.2.1. 数据加入Z值

    之前绘制的三角形,只有X坐标和Y坐标,Z值坐标自动补足为默认为0的。在这里会绘制了3个三角形,每个三角形的深度不同。如下代码所示,定义了3个三角形9个点,每个点包含xyz信息和rgb信息:

      // 顶点坐标和颜色
      var verticesColors = new Float32Array([
        0.0, 1.0, -4.0, 0.4, 1.0, 0.4,  //绿色在后
        -0.5, -1.0, -4.0, 0.4, 1.0, 0.4,
        0.5, -1.0, -4.0, 1.0, 0.4, 0.4,
    
        0.0, 1.0, -2.0, 1.0, 1.0, 0.4, //黄色在中
        -0.5, -1.0, -2.0, 1.0, 1.0, 0.4,
        0.5, -1.0, -2.0, 1.0, 0.4, 0.4,
    
        0.0, 1.0, 0.0, 0.4, 0.4, 1.0,  //蓝色在前
        -0.5, -1.0, 0.0, 0.4, 0.4, 1.0,
        0.5, -1.0, 0.0, 1.0, 0.4, 0.4,
      ]);
    

    这意味着与着色器传输变量的函数gl.vertexAttribPointer()的参数也得相应的变化。注意要深入理解这个函数每个参数代表的含义:

      // ...
    
      // 将缓冲区对象分配给a_Position变量
      gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
    
      // ...
      // 将缓冲区对象分配给a_Color变量
      gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
    

    2.2.2. 加入深度测试

    在默认情况下,WebGL是根据顶点在缓冲区的顺序来进行绘制的,后绘制的图形会覆盖已经绘制好的图形。但是这样往往与实际物体遮挡情况不同,造成一些很怪异的现象,比如远的物体反而遮挡了近的物体。所以WebGL提供了一种深度检测(DEPTH_TEST)的功能,启用该功能就会检测物体(实际是每个像素)的深度,来决定是否绘制。其启用函数为:
    2
    除此之外,还应该注意在绘制每一帧之前都应该清除深度缓冲区(depth buffer)。WebGL有多种缓冲区。我们之前用到的与顶点着色器交互的缓冲区对象就是顶点缓冲区,每次重新绘制刷新的就是颜色缓冲区。深度缓冲区记录的就是每个几何图形的深度信息,每绘制一帧,都应清除深度缓冲区:
    3
    在本例中的相关代码为:

      // ...
    
      // 开启深度测试
      gl.enable(gl.DEPTH_TEST);
    
      // 清空颜色和深度缓冲区
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
      // ...
    

    2.2.3. MVP矩阵设置

    在上一篇教程中提到过,WebGL的任何图形变换过程影响的都是物体的顶点,模型变换、视图变换、投影变换都是在顶点着色器中实现的。由于每个顶点都是要进行模型视图投影变换的,所以可以合并成一个MVP矩阵,将其传入到顶点着色器中的:

      //...
      'uniform mat4 u_MvpMatrix;
    ' +  
      'void main() {
    ' +
      '  gl_Position = u_MvpMatrix * a_Position;
    ' + // Set the vertex coordinates of the point 
      //...
      '}
    ';
    

    在函数setMVPMatrix()中,创建了MVP矩阵,并将其传入到着色器:

    //设置MVP矩阵
    function setMVPMatrix(gl,canvas) {
      // 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;
      }
    
      //模型矩阵
      var modelMatrix = new Matrix4();
      modelMatrix.setTranslate(0.75, 0, 0);
    
      //视图矩阵
      var viewMatrix = new Matrix4();  // View matrix
      viewMatrix.setLookAt(0, 0, 5, 0, 0, -100, 0, 1, 0);
    
      //投影矩阵
      var projMatrix = new Matrix4();  // Projection matrix
      projMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100);
    
      //MVP矩阵
      var mvpMatrix = new Matrix4();
      mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
    
      //将MVP矩阵传输到着色器的uniform变量u_MvpMatrix
      gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
    }
    

    在上述代码中,依次分别设置了:

    • 模型矩阵:X方向上平移了0.75个单位。
    • 视图矩阵:视点为(0,0,5),观察点为(0,0,-100),上方向为(0,1,0)的观察视角。
    • 投影矩阵:垂直张角为30,画图视图的宽高比,近截面距离为1,远截面为100的视锥体。

    三者级联,得到MVP矩阵,将其传入到顶点着色器中。

    3. 结果

    用浏览器打开Triangle_MVPMatrix.html,就会发现浏览器页面显示了一个由远及近,近大远小的三个三角形。如图所示:
    1

    4. 参考

    本来部分代码和插图来自《WebGL编程指南》。

    代码和数据地址

    上一篇
    目录
    下一篇

  • 相关阅读:
    [C++] inline内联函数使用方法
    [C++] new和delete运算符使用方法
    [C++] namespace命名空间和using用法
    [C++] 引用类型&
    [C++] wchar_t关键字使用方法
    [C++] typeid关键字使用方法
    json_encode转化索引数组之后依然还是数组的问题
    微信网页授权 的流程
    验证码比较hash_equals 方法
    laravel 模型观察器
  • 原文地址:https://www.cnblogs.com/charlee44/p/11625869.html
Copyright © 2011-2022 走看看