4-绘制和变换三角形

本章主要内容:

  • 三角形在三维图形学中的重要地位
  • 使用多个三角形绘制其他类型的基本图形
  • 利用简单的方程对三角形做基本的变换,如移动,旋转和缩放
  • 利用矩阵简化变换

4.1 绘制多个点

不管三维模型的形状多么的复杂,其基本组成部分就是三角形,只不过复杂的模型是由更多的三角形构成而已。

为了绘制多个点,webgl提供了一种很方便的机制,既缓冲区对象,它可以一次性的向着色器中传入多个顶点数据。缓冲区对象是webgl系统中的一块内存区域,我们可以一次性的向缓冲区对象中填充大量的顶点数据,然后将这些数据保存在其中,供顶点着色器使用。

流程如下:

  1. 获取webgl绘图的上下文
  2. 初始化着色器
  3. 设置点的坐标信息
  4. 设置canvas的背景颜色
  5. 清空canvas
  6. 绘制

html代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webgl学习</title>
</head>

<body>
<canvas id="canvas" width="400" height="400"></canvas>
<!-- 引入几个webgl帮助函数,这里的代码很简单,你可以直接点击查看 -->
<script src="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/webgl-utils.js"></script>
<script src="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/webgl-debug.js"></script>
<script src="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/cuon-utils.js"></script>
<script src="./index.js"></script>
</body>

</html>

js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//  顶点着色器 注意这里的\n不能省略。否则不符合语法
var VSHADER_SOURCE = `
attribute vec4 a_Position\n;
void main(){\n
gl_Position = a_Position;\n
gl_PointSize = 10.0;\n
}\n
`
// 片段(片元)着色器
var FSHADER_SOURCE = `
void main(){\n
gl_FragColor = vec4(1.0,0.0,0.0,1.0);\n
}\n
`

function main() {
// 获取canvas
var canvas = document.getElementById('canvas')
// 获取webgl绘图上下文
var gl = canvas.getContext('webgl')

if (!gl) {
console.log('Failed to get the rendering context')
retun
}

// 初始化着色器函数
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('设置着色器失败')
return
}
// 设置顶点位置
var n = initVertexBuffers(gl)
if (n < 0) {
console.log('Failed to set the positions of vertices')
return
}

// 指定清空canvas的颜色
gl.clearColor(0.0, 0.0, 0.0, 1.0)
// 清空canvas
gl.clear(gl.COLOR_BUFFER_BIT)

// 绘制3个点
gl.drawArrays(gl.POINTS, 0, n)
}

function initVertexBuffers(gl) {
// 设置顶点位置
var vertices = new Float32Array([
0.0, 0.5, -0.5, -0.5, 0.5, -0.5
])
// 设置点的个数
var n = 3
// 创建缓冲区对象
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, vertices, gl.STATIC_DRAW)

// 获取attribute的存储位置
var a_Position = gl.getAttribLocation(gl.program, 'a_Position')

if (a_Position < 0) {
console.log('Failed to get the storage of a_Position')
return
}
// 将缓冲区对象分配给a_Position变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
// 将缓冲区对象分配给a_Position
gl.enableVertexAttribArray(a_Position);
return n
}

window.onload = function () {
main()
}

点击查看效果

4.2 使用缓存区对象

缓存对象是webgl系统中的一块存储区,你可以在缓冲区对象中保存想要绘制的所有的顶点数据。如图,先创建一个缓冲区对象,然后向其中写入顶点数据,你就能一次性的向顶点着色器中传入多个顶点的attribute变量数据。

使用缓冲区对象

使用缓冲区对象向顶点着色器传入多个顶点数据,需要遵循5个步骤

  1. 创建缓冲区对象(gl.createBuffer())
  2. 绑定缓冲区对象(gl.bindBuffer())
  3. 将数据写入缓冲区对象(gl.bufferData())
  4. 将缓存区对象分配给一个attribute变量(gl.vertexAttribPointer())
  5. 开启attribute变量(gl.enableVertexAttribArray())

4.3 创建缓冲区对象

使用缓冲区对象之前,必须创建它

var vertexBuffer = gl.createBuffer()

如图表示的是创建缓冲区对象前后webgl系统的中间状态。上面是创建前,下面是创建后。

创建缓冲区对象

gl.createBuffer()

类型 参数 含义
返回值 非null 新创建的缓冲区对象
null 创建缓冲区对象失败
错误

相应的gl.deleteBuffer(buffer)函数可以用来删除缓冲区对象

gl.deleteBuffer(buffer)

类型 参数 含义
参数 buffer 待删除的缓冲区对象
返回值
错误

4.4 绑定缓冲区

创建缓冲区之后的第2个步骤就是将缓冲区对象绑定在webgl系统中已经存在的“目标”上,这个“目标”表示缓冲区对象的用途。这样webgl才能够正确处理其中的内容。

gl.bindBuffer(target,buffer)

类型 参数 含义
target gl.ARRAY_BUFFER 表示缓冲区对象中包含了顶点的数据
gl.ELEMENT_ARRYA_BUFFER 表示缓冲区对象中包含了顶点的索引值
buffer 指定之前由gl.createBuffer() 返回的待绑定的缓冲区对象
null 禁用对target的绑定
返回值
错误 INVALID_ENUM target不是上述值之一,这时将保持原有的绑定情况不变

绑定缓冲区对象

4.5 向缓冲区对象中写入数据(gl.bufferData())

gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW)

该方法的效果是将第2个参数vertices中的数据写入了绑定到第1个参数gl.ARRAY_BUFFER上的缓冲区对象。我们不能直接向缓冲区对象写入数据,只能向“目标”写入数据。所以向缓冲区对象写入数据前,必须先绑定。

该方法执行后,webgl系统的内部状态如下:

向缓冲区对象中写入数据

gl.bufferData(target,data,usage)

类型 参数 含义
target gl.ARRAY_BUFFER或gl.ELEMENT_ARRAY_BUFFER
data 写入缓冲区对象的数据
usage gl.STATIC_DRAW 只会想缓冲区对象中写入一次数据,但需要绘制多次
gl.STREAM_DRAW 只会向缓冲区对象中写入一次数据,然后绘制若干次
gl.DYNAMIC_DRAW 会向缓冲区对象多次写入数据,并绘制很多次
返回值
错误 INVALID_ENUM target不是上述值之一,这时将保持原有的绑定情况不变

4.6 类型化数组

为了绘制三维图形,webgl通常需要同时处理大量相同类型的数据,例如顶点的坐标和颜色数据。为了优化性能,webgl为每种数据类型引入了一种特殊的数组。浏览器事先知道了数组中的数据类型,所以处理起来更加有效率。

webgl中使用的各种类型话数组

数组类型 每个元素所占字节数 描述
Int8Array 1 8位整型数
UInt8Array 1 8位无符号整型数
Int16Array 2 16位整型数
UInt16Array 2 16位无符号整型数
Int32Array 4 32位整型数
UInt32Array 4 32位无符号整型数
Float32Array 4 单精度32位浮点数
Float64Array 8 双精度64位浮点数

类型化数组的方法和属性

方法、属性和常量 描述
get(index) 获取第index个元素值
set(index,value) 设置第index个元素值为value
set(array,offset) 从第offset个元素开始,将数组array中的值填充进去
length 数组的长度
BYTES_PER_ELEMENT 数组中每个元素所占的字节数

类型化数组的创建方法和普通数组一样

4.7 将缓冲区对象分配给attribute变量(gl.vertexAttribPointer)

你可以使用gl.vertexAttrib[1234]f系列函数为attribute变量分配值。但是这些方法一次只能向attribute变量分配一个值。而现在,你需要将整个数组中的所有值一次性的分配给attribute变量。而gl.vertexAttribPointer解决了这个问题。

gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,0,0)

gl.vertexAttribPointer(location,size,type,normalized,stride,offset) 将gl.ARRAY_BUFFER的缓冲区对象分配给由location指定的attribute变量

类型 参数 含义
location 指定待分配attitude变量的存储位置
size 指定缓冲区中每个顶点分量个数(1,4),若size比attribute变量需要分配的分量数小,缺失分量将按照与gl.vertexAttrib[1234]f相同的规则进行补全。
type gl.UNSIGNED_BYTE 无符号字节,Uint8Array
gl.SHORT 短整型,Int16Array
gl.UNSIGNED_SHORT 无符号短整型,Uint16Array
gl.INT 整型,Int32Array
gl.UNSIGNED_INT 无符号整型,Uint32Array
gl.FLOAT 浮点型,Float32Array
normalize 传入true或者false,表明是否将非浮点型的整数归一化到[0,1]或者[-1,1]的区间
stride 指定相邻两个顶点的字节数,默认为0
offset 指定缓冲区对象中的偏移量(以字节为单位),既attribute变量从缓冲区的何处开始存储,如果是从起始位置,offset设为0

分配缓冲区对象

4.8 开启attribute变量

为了使顶点着色器能够访问缓冲区内的数据,我们需要gl.enableVertexAttribArray(a_Position)方法来开启attribute变量。这时候缓冲区对象和attribute变量之间才能真正连接起来。

gl.enableVertexArray(location)

类型 参数 含义
参数 location 指定attribute变量的存储位置
返回值
错误 INVALID_VALUE location大于等于attribute变量的最大数目

开启attribute变量

同样你也可以关闭分配

gl.disableVertexArray(location)

类型 参数 含义
参数 location 指定attribute变量的存储位置
返回值
错误 INVALID_VALUE location大于等于attribute变量的最大数目

4.9 hello triangle

你已经学会了如何将多个顶点坐标传递给顶点着色器,下面我们使用这些顶点绘制真正的图形

html代码跟之前的一样,这里就不写了,js代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//  顶点着色器 注意这里的\n不能省略。否则不符合语法
var VSHADER_SOURCE = `
attribute vec4 a_Position\n;
void main(){\n
gl_Position = a_Position;\n
}\n
`
// 片段(片元)着色器
var FSHADER_SOURCE = `
void main(){\n
gl_FragColor = vec4(1.0,0.0,0.0,1.0);\n
}\n
`

function main() {
// 获取canvas
var canvas = document.getElementById('canvas')
// 获取webgl绘图上下文
var gl = canvas.getContext('webgl')

if (!gl) {
console.log('Failed to get the rendering context')
retun
}

// 初始化着色器函数
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('设置着色器失败')
return
}
// 设置顶点位置
var n = initVertexBuffers(gl)
if (n < 0) {
console.log('Failed to set the positions of vertices')
return
}

// 指定清空canvas的颜色
gl.clearColor(0.0, 0.0, 0.0, 1.0)
// 清空canvas
gl.clear(gl.COLOR_BUFFER_BIT)

// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, n)
}

function initVertexBuffers(gl) {
// 设置顶点位置
var vertices = new Float32Array([
0.0, 0.5, -0.5, -0.5, 0.5, -0.5
])
// 设置点的个数
var n = 3
// 创建缓冲区对象
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, vertices, gl.STATIC_DRAW)

// 获取attribute的存储位置
var a_Position = gl.getAttribLocation(gl.program, 'a_Position')

if (a_Position < 0) {
console.log('Failed to get the storage of a_Position')
return
}
// 将缓冲区对象分配给a_Position变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
// 将缓冲区对象分配给a_Position
gl.enableVertexAttribArray(a_Position);
return n
}

window.onload = function () {
main()
}

绘制三角形的程序跟绘制多个点的程序基本一样,只有两个地方不一样。

  • 在顶点着色器中,指定顶点尺寸的gl_PointSize = 10.0被删去了。该语句只有在绘制单个顶点的时候才起作用
  • gl.drawArrays() 方法的第一个参数从gl.POINTS被改为了gl.TRIANGLES

点击查看效果