WebGL 的基本图元包括:点、线段、三角形。
三角形图元分类
基本三角形(TRIANGLES)
基本三角形是一个个独立的三角形。
假如:提供给着色器 6 个顶点,WebGL 会绘制 2 个三角形,前三个顶点绘制一个,后三个顶点绘制另一个,互不相干。
例如:[v1, v2, v3, v4, v5, v6] 采用基本三角形图元进行绘制,绘制完如下图所示
[v1, v2, v3] 为一个三角形、[v4, v5, v6] 为另一个三角形。
绘制三角形的数量 = 顶点数 / 3
三角带(TRIANGLE_STRIP)
例如:[v1, v2, v3, v4, v5, v6] 采用三角带的方式进行绘制,绘制完如下图所示
则会绘制 [v1, v2, v3], [v3, v2, v4], [v3, v4, v5], [v5, v4, v6] 共计 4 个三角形
绘制三角形数量 = 顶点数 - 2
三角扇(TRIANGLE_FAN)
例如:[v1, v2, v3, v4, v5, v6] 采用三角扇的方式进行绘制,绘制完如下图所示
则会绘制 [v1, v2, v3], [v1, v3, v4], [v3, v4, v5], [v1, v5, v6] 共计 4 个三角形
绘制三角形数量 = 顶点数 - 2
绘制基本三角形
目标:在 canvas 上点击三个位置作为三角形的三个顶点,然后绘制一个红色的三角形。
注意:不涉及到深度信息 z 值,每个顶点之传入 [x, y] 坐标即可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>绘制三角形</title>
<link rel="stylesheet" href="styles/app.css">
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const random = Math.random;
/**
* 获取随机颜色
* @returns { Object } 颜色对象
*/
function randomColor () {
return {
r: random() * 255,
g: random() * 255,
b: random() * 255,
a: random() * 1
}
}
const canvas = document.querySelector('#canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const gl = canvas.getContext('webgl');
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 顶点着色器
const vertexShaderSource = `
// 设置浮点数据类型为中级精度
precision mediump float;
// 接收顶点坐标 (x,y)
attribute vec2 a_Position;
void main () {
gl_Position = vec4(a_Position, 0.0, 1.0);
}
`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
// 片元着色器
const fragmentShaderSource = `
// 设置浮点数据类型为中级精度
precision mediump float;
// 接收 JavaScript 传过来的颜色值(rgba)
uniform vec4 u_Color;
void main(){
vec4 color = u_Color / vec4(255, 255, 255, 1);
gl_FragColor = color;
}
`;
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader,fragmentShaderSource);
gl.compileShader(fragmentShader);
// 着色器程序
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
// 获取着色器程序中变量的指针位置
const a_Position = gl.getAttribLocation(program, 'a_Position')
const u_Color = gl.getUniformLocation(program, 'u_Color');
// 定义三角形的三个顶点
const positions = [
0, 0.5, // 上顶点
-0.5, -0.5, // 左顶点
0.5, -0.5 // 右顶点
];
// 创建缓冲区
const buffer = gl.createBuffer();
// 绑定缓冲区并指定缓冲区的类型
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// 往缓冲区中写入数据
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
// 将属性绑定到缓冲区
gl.enableVertexAttribArray(a_Position);
// 如何读取缓冲区数据
// 指定要修改的顶点属性的索引(允许哪个属性读取当前缓冲区的数据) - a_Position
// 指定每个顶点属性的组成数量(一次读取几个数据) - 2
// 指定数组中每个元素的数据类型 - gl.FLOAT(32 位 IEEE 标准的浮点数,占用 4 个字节)
// 当转换为浮点数时是否应该将整数数值归一化到特定的范围 - false(对于类型 gl.FLOAT 和 gl.HALF_FLOAT,此参数无效)
// 步长(即:每个顶点所包含数据的字节数)0 表示一个属性的数据是连续存放的
// 偏移量(指定顶点属性数组中第一部分的字节偏移量)(在每个步长的数据里,目标属性需要偏移多少字节开始读取;必须是类型的字节长度的倍数)0 * 4 = 0
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 随机颜色
const { r, g, b, a } = randomColor();
// 向片元着色器传递颜色信息
gl.uniform4f(u_Color, r, g, b, a);
// 绘制三角形
// 指定绘制图元的方式 - gl.TRIANGLES(三角形)
// 指定从哪个点开始绘制 - 0
// 指定绘制需要使用到多少个点 - 3
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
</script>
</body>
</html>
如何将三角形的三个顶点数据传递给顶点着色器
因为要传递多个顶点数据,通过缓冲区可以向顶点着色器传递多个顶点数据
// 定义三角形的三个顶点
const positions = [
0, 0.5, // 上顶点
-0.5, -0.5, // 左顶点
0.5, -0.5 // 右顶点
];
// 找到 a_Position 变量的指针位置
const a_Position = gl.getAttribLocation(program, 'a_Position')
// 创建缓冲区
const buffer = gl.createBuffer();
// 绑定缓冲区并指定缓冲区的类型
// 绑定之后,对缓冲区绑定点的的任何操作都会基于该缓冲区(即 buffer) 进行
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// 往缓冲区(即上一步通过 bindBuffer 绑定的缓冲区)中写入数据
步骤分析
创建一个数组,用于保存三角形的顶点坐标信息
创建一个缓冲区
绑定缓冲区,并指定缓冲区类型
往缓冲区写入数据
使用 Float32Array 将 JavaScript 中的弱类型数组转化为强类型数组,然后复制到缓冲区中
gl.STATIC_DRAW 告诉 WebGL 不会频繁改变缓冲区中的数据,WebGL 会根据这个参数做一些优化处理
如何把顶点组成的模型渲染到屏幕上
// 将属性绑定到缓冲区
gl.enableVertexAttribArray(a_Position);
// 允许哪个属性读取当前缓冲区的数据,读取长度,读取类型,如何读取
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 绘制三角形
步骤分析
将属性绑定到缓冲区
告诉 WebGL 允许哪个属性,如何从缓冲区中获取数据的方式
允许哪个属性读取当前缓冲区的数据
一次读取几个数据
指定数组中每个元素的数据类型
当转换为浮点数时是否应该将整数数值归一化到特定的范围 [-1, 1] 之间
步长,即每个顶点所包含数据的字节数,默认值是:0
不能大于 255
如果为 0,则表示下一个顶点的属性紧跟当前顶点之后(表示一个属性的数据是连续存放的)
本例中,一个顶点包含两个分量:x 坐标和 y 坐标,每个分量都是 Float32Array 类型,占 4 个字节,所以 步长 = 2 * 4 就是 8 个字节;但本例中,缓冲区只为一个属性 a_Position 服务,并且缓冲区的数据是连续存放的,因此,可以使用默认值 0 来表示
如果缓冲区为多个属性所共用,步长就不能设置为 0 了,需要进行计算
偏移量(指定顶点属性数组中第一部分的字节偏移量)必须是类型的字节长度的倍数
可理解为:在每个步长的数据里,目标属性需要偏移多少字节开始读取
本例中,缓冲区只为 a_Position 一个属性服务,所以 偏移量 = 0 * 4 为 0
图解步长
假如顶点数组为 [10, 20, 30, 30, 40, 50, 60, 70],每相邻数字代表一个顶点的 x 坐标和 y 坐标。
由于使用的是 Float32Array 浮点数组,每个数字占 4 个字节。
绘制三角形
// 指定绘制图元的方式 - gl.TRIANGLES(三角形)
// 指定从哪个点开始绘制 - 0
// 指定绘制需要使用到多少个点 - 3
gl.drawArrays(gl.TRIANGLES, 0, 3);