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编程指南》。

代码和数据地址

上一篇

目录

下一篇

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

  1. WebGL简易教程(十五):加载gltf模型

    目录 1. 概述 2. 实例 2.1. 数据 2.2. 程序 2.2.1. 文件读取 2.2.2. glTF格式解析 2.2.3. 初始化顶点缓冲区 2.2.4. 其他 3. 结果 4. 参考 5. ...

  2. WebGL简易教程(七):绘制一个矩形体

    目录 1. 概述 2. 示例 2.1. 顶点索引绘制 2.2. MVP矩阵设置 2.2.1. 模型矩阵 2.2.2. 投影矩阵 2.2.3. 视图矩阵 2.2.4. MVP矩阵 3. 结果 4. 参考 ...

  3. WebGL简易教程——目录

    目录 1. 绪论 2. 目录 3. 资源 1. 绪论 最近研究WebGL,看了<WebGL编程指南>这本书,结合自己的专业知识写的一系列教程.之前在看OpenGL/WebGL的时候总是感觉 ...

  4. WebGL简易教程(八):三维场景交互

    目录 1. 概述 2. 实例 2.1. 重绘刷新 2.2. 鼠标事件调整参数 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL简易教程(七):绘制一个矩形体>中,通过一个绘制矩 ...

  5. WebGL简易教程(九):综合实例:地形的绘制

    目录 1. 概述 2. 实例 2.1. TerrainViewer.html 2.2. TerrainViewer.js 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL简易教程(八 ...

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

    目录 1. 概述 2. 示例:绘制一个点(改进版) 1) attribute变量 2) uniform变量 3) varying变量 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL ...

  7. WebGL简易教程(三):绘制一个三角形(缓冲区对象)

    目录 1. 概述 2. 示例:绘制三角形 1) HelloTriangle.html 2) HelloTriangle.js 3) 缓冲区对象 (1) 创建缓冲区对象(gl.createBuffer( ...

  8. WebGL简易教程(四):颜色

    目录 1. 概述 2. 示例:绘制三角形 1) 数据的组织 2) varying变量 3. 结果 4. 理解 1) 图形装配和光栅化 2) 内插过程 5. 参考 1. 概述 在上一篇教程<Web ...

  9. WebGL简易教程(十三):帧缓存对象(离屏渲染)

    目录 1. 概述 2. 示例 2.1. 着色器部分 2.2. 初始化/准备工作 2.2.1. 着色器切换 2.2.2. 帧缓冲区 2.3. 绘制函数 2.3.1. 初始化顶点数组 2.3.2. 传递非 ...

随机推荐

  1. 用故事解析setTimeout和setInterval(内含js单线程和任务队列)

    区别: setTimeout(fn,t): 延迟调用,超过了时间就调用回调函数,返回一个id,使用clearTimeout(id)取消执行. 注意:取消了里面的回调函数就不执行了哦,而不是取消的时候就 ...

  2. 用java实现取1-100之间的99个不重复的随机数 然后输出没有被取出的数字

    package cn.kgc.springtest2.demo1.dao; import java.util.BitSet; /** * @author * @create 2019-08-02 17 ...

  3. HDU 4322Candy 最大费用最大流

    由于被小孩子不喜欢的糖果的对小孩产生的效力是一样的,所以我们在网络流的时候先不考虑. 1 - 源点0到1~N个糖果,容量为1,费用为02 - 根据like数组,like[i][j] == 1时在糖果j ...

  4. CodeM 资格赛 B 可乐 思维

      分析: 我们假设购买一种可乐p瓶,我们可以得到期望:p*(m/n*a[i]+(n-m)/n*b[i]),由这个式子我们可以看出唯一的变量是i,所以可以遍历i找出式子的最大值 #include &l ...

  5. 电脑修改密码后,git push 报错unable to access

    电脑修改密码后,git push 时报错 remote: Permission to xxx A. fatal: unable to access  解决这个问题有两种方法,一种是界面修改,一种是命令 ...

  6. 【Offer】[26] 【树的子结构】

    题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 输入两棵二叉树A和B,判断B是不是A的子结构.图中右边的树是左边的子结构  思路分析 先对树A进行遍历,找到与树B的根结点值相同的节点 ...

  7. Visual Studio 调试 —— 附加到进程(远程服务器)

    一般在部署环境下不会有 Visual Studio 开发工具的,所以需要有 Remote Debugger(远程调试器) 才可以进行远程调试. Remote Debugger 获取 方法一:Visua ...

  8. 2019CSP初赛基础知识整理

    一.硬件 计算机发展:   年代 元件 第一代 1946~1958 电子管 第二代 1959~1964 晶体管 第三代 1965~1970 集成电路 第四代 1971~? 大规模集成电路 世界上第一台 ...

  9. SpringCloud(三)Ribbon与Feign

    上一篇使用了Eureka与Ribbon组件做了最简单的的服务注册与发现,我们知道Eureka是实现服务治理中心的组件,但是上一篇Eureka没有实现集群,这样没有保证到Eureka Server的高可 ...

  10. CentOS7上OpenResty安装

    1,OpenResty安装 通过repl源安装: sudo yum-config-manager --add-repo https://openresty.org/yum/cn/centos/Open ...