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. 如何替换ROS中默认的Planner

    官方文档参阅:http://wiki.ros.org/pluginlib 有时候,可能会需要将替换ROS默认的planner替换成别的planner或我们自己的planner.这就涉及到了新plann ...

  2. CF - 1110F Nearest Leaf

    题目传送门 题解: 先用题目给定的dfs方式得到dfs序,记录下出入的dfs序. 很明显可以得知的是,以u为根的子树的dfs序在 in[u] - out[u] 的范围之内. 将每个询问先全部存到对应的 ...

  3. lightoj 1201 - A Perfect Murder(树形dp)

    题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1201 题解:简单的树形dp,dp[0][i]表示以i为根结点不傻i的最多有多少 ...

  4. codeforces 361 D. Levko and Array(dp+二分)

    题目链接:http://codeforces.com/contest/361/problem/D 题意:最多可以修改K次数字,每次修改一个数字变成任意值,C=max(a[i+1]-a[i]):求操作之 ...

  5. Java 网络编程:必知必会的 URL 和 URLConnection

    java.net.URL 类将 URL 地址进行了封装,并提供了解析 URL 地址的基本方法,比如获取 URL 的主机名和端口号.java.net.URLConnection 则代表了应用程序和 UR ...

  6. 【Offer】[42] 【连续子数组的最大和】

    题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 输入一个整型数组,数组里有正数也有负数.数组中的一个或连续多个整数组成一个子数组.求所有子数组的和的最大值.要求时间复杂度为O(n). ...

  7. NetCore下的HTTP请求IHttpClientFactory

    使用方式 IHttpClientFactory有四种模式: 基本用法 命名客户端 类型化客户端 生成的客户端 基本用法 在 Startup.ConfigureServices 方法中,通过在 ISer ...

  8. Java开学测试

    这次开学测试要求做一个信息系统,该系统完成学生成绩录入,修改,计算学分积点和查询学生成绩的简单功能. 下面是我写的代码 //信1805-3班 20183641 赵树琪 package test; im ...

  9. maven引入jar包冲突问题

    1.原因 使用maven过程中,经常会遇到jar包重复加载或者jar包冲突的问题,但是有些jar包是由于maven加载了其他jar包自动引入的,并非自己主动添加的,导致和自己添加的jar包版本冲突 举 ...

  10. Redis删除集群以及重新启动集群

    有时候我们搭建完集群以后,对集群进行了一些错误的操作,导致集群出现了不可预料的问题,这时候想要删除集群重新启动一个原始的集群,那么如何删除原来旧的集群呢? 1.关闭所有开启的Redis节点 kill ...