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关于绘制场景的模型变换、视图变换以及投影变换的过程。不过那篇教程是纯理论知识,这里就具体结合一个实际的例子,进一步理解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;\n' + // attribute variable
  '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' + // Set the vertex coordinates of the point
  '  v_Color = a_Color;\n' +
  '}\n';

// 片元着色器程序
var FSHADER_SOURCE =
  'precision mediump float;\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_FragColor = v_Color;\n' +
  '}\n';

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)的功能,启用该功能就会检测物体(实际是每个像素)的深度,来决定是否绘制。其启用函数为:

除此之外,还应该注意在绘制每一帧之前都应该清除深度缓冲区(depth buffer)。WebGL有多种缓冲区。我们之前用到的与顶点着色器交互的缓冲区对象就是顶点缓冲区,每次重新绘制刷新的就是颜色缓冲区。深度缓冲区记录的就是每个几何图形的深度信息,每绘制一帧,都应清除深度缓冲区:

在本例中的相关代码为:

  // ...

  // 开启深度测试
  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;\n' +
  'void main() {\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\n' + // Set the vertex coordinates of the point
  //...
  '}\n';

在函数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,就会发现浏览器页面显示了一个由远及近,近大远小的三个三角形。如图所示:

4. 参考

本来部分代码和插图来自《WebGL编程指南》,源代码链接:https://share.weiyun.com/5VjlUKo ,密码:sw0x2x。会在此共享目录中持续更新后续的内容。

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

时间: 2024-11-05 11:54:47

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

WebGL简易教程(二):向着色器传输数据

目录 1. 概述 2. 示例:绘制一个点(改进版) 1) attribute变量 2) uniform变量 3) varying变量 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL简易教程(一):第一个简单示例>中,通过一个绘制点的例子,对WebGL中的可编程渲染管线有了个基本的认识.在之前绘制点的例子中,点的位置,点的大小,点的颜色,都是固定写在着色器中的,这样的程序是缺乏可扩展性的. 比如我想绘制一张地形(DEM),平时地形数据是保存在地形文件之中的.被程序加载之后,数据信息

WebGL简易教程(四):颜色

目录 1. 概述 2. 示例:绘制三角形 1) 数据的组织 2) varying变量 3. 结果 4. 理解 1) 图形装配和光栅化 2) 内插过程 5. 参考 1. 概述 在上一篇教程<WebGL简易教程(三):绘制一个三角形(缓冲区对象)>中,通过使用缓冲区对象(buffer object)来向顶点着色器传送数据.那么,如果这些数据(与顶点相关的数据,如法向量.颜色等)需要继续传送到片元着色器该怎么办呢? 例如这里给三角形的每个顶点赋予不同的颜色,绘制一个彩色的三角形.这个时候就需要用到之

WebGL简易教程(十):光照

目录 1. 概述 2. 原理 2.1. 光源类型 2.2. 反射类型 2.2.1. 环境反射(enviroment/ambient reflection) 2.2.2. 漫反射(diffuse reflection) 2.2.3. 综合 3. 实例 3.1. 具体代码 3.2. 改动详解 3.2.1. 设置日照 3.2.2. 着色器光照设置 4. 结果 5. 参考 1. 概述 在上一篇教程<WebGL简易教程(九):综合实例:地形的绘制>中,实现了对一个地形场景的渲染.在这篇教程中,就给这个地

WebGL简易教程(十一):纹理

目录 1. 概述 2. 实例 2.1. 准备纹理 2.2. 配置纹理 2.3. 使用纹理 3. 结果 4. 参考 1. 概述 在之前的之前的教程<WebGL简易教程(九):综合实例:地形的绘制>中,绘制了一个带颜色的地形场景.地形的颜色是根据高程赋予的RGB值,通过不同的颜色来表示地形的起伏,这是表达地形渲染的一种方式.除此之外,还可以将拍摄得到的数字影像,贴到地形上面,得到更逼真的地形效果.这就要用到我们这一章的新知识--纹理了. 这里用到的纹理图像,是一张从GoogleEarth上下载的卫

WebGL简易教程(十二):包围球与投影

目录 1. 概述 2. 实现详解 3. 具体代码 4. 参考 1. 概述 在之前的教程中,都是通过物体的包围盒来设置模型视图投影矩阵(MVP矩阵),来确定物体合适的位置的.但是在很多情况下,使用包围盒并不方便计算,可以利用包围盒再生成一个包围球,利用包围球来设置MVP矩阵. 在<WebGL简易教程(十):光照>中,给地形赋予了固定方向的平行光.这篇教程的例子就是想模拟在平行光的视角下地形的情况.对于点光源光,可以用透视投影来实现渲染的效果:而平行光就需要通过正射投影来模拟.并且,这种正射并不是

WebGL简易教程(五):图形变换(模型、视图、投影变换)

目录 1. 概述 2. 详论 1) 模型变换 (1) 平移变换 (2) 缩放变换 (3) 旋转变换 (4) 组合变换 2) 视图变换 (1) 原理 (2) 推导 3) 投影变换 (1) 透视投影 (2) 正射投影 3. 综合运用 4. 参考 1. 概述 通过之前的教程,对WebGL中可编程渲染管线的流程有了一定的认识.但是只有前面的知识还不足以绘制真正的三维场景,可以发现之前我们绘制的点.三角形的坐标都是[-1,1]之间,Z值的坐标都是采用的默认0值,而一般的三维场景都是很复杂的三维坐标.为了在

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入门教程第1篇——六色立方

WebGL入门教程第1篇——六色立方 WebGL,一项允许开发人员在浏览器里操纵GPU来显示图形的技术.让我们一起走进WebGL的世界. 读者对象 本系列适合具有基础JavaScript知识的开发人员. 准备工作 我们应该在本地搭建好web服务器,或者安装了具有预览功能的IDE.如果你安装了Visual Studio,Nivk童鞋为我们开发了WebGL代码提示功能,你可以通过以下步骤使Visual Studio支持WebGL代码提示:打开Visual Studio——点击工具——点击选项——展开

Ocelot简易教程(六)之重写配置文件存储方式并优化响应数据

原文:Ocelot简易教程(六)之重写配置文件存储方式并优化响应数据 本来这篇文章在昨天晚上就能发布的,悲剧的是写了两三千字的文章居然没保存,结果我懵逼了.今天重新来写这篇文章.今天我们就一起来探讨下如何重写Ocelot配置文件的存储方式以及获取方式. 作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9807125.html 很多人都说配置文件的配置很繁琐,如果存储在数据库就方便很多,可以通过自定义UI界面在后台进行路由的配置,然后通过调用Admini