|
| 1 | +--- |
| 2 | +editLink: false |
| 3 | +outline: deep |
| 4 | +outlineText: "33" |
| 5 | +--- |
| 6 | + |
| 7 | +# 绘制和变换三角形 |
| 8 | + |
| 9 | +已经了解了如何获取`WebGL上下文`,清空`<canvas>`为2D/3D绘图作准备,探究了顶点着色器与片元着色器的功能与特征,以及使用着色器进行绘图的方法。 |
| 10 | + |
| 11 | +本章学习内容: |
| 12 | +1. 三角形在三维图形学中的重要地位,以及WebGL如何绘制三角形 |
| 13 | +2. 使用多个三角形绘制其他类型的基本图形 |
| 14 | +3. 利用简单的方程对三角形做基本地变换,如移动、旋转和缩放 |
| 15 | +4. 利用矩阵简化变换 |
| 16 | + |
| 17 | +**三维模型的基本单位是三角形** |
| 18 | + |
| 19 | +## 绘制多个点 |
| 20 | + |
| 21 | +上一章中的方式只能绘制一个点。对于那些由多个顶点组成的图形,比如三角形、矩形和立方体来说,我们需要一次性地将图形的顶点全部传出顶点着色器,然后才能把图形画出来。 |
| 22 | + |
| 23 | + |
| 24 | +WebGL提供了提供了一种很方便的机制,记缓冲区对象(buffer object),他可以一次性地向着色器传入多个顶点的数据。缓冲区对象是`WebGL`系统中的一块内存区域, |
| 25 | +我们可以一次性地向缓冲区对象中填充大量的顶点数,然后将这些数据保存在其中供`顶点着色器`使用。 |
| 26 | + |
| 27 | +```js |
| 28 | +const VSHADER_SOURCE = |
| 29 | + 'attribute vec4 a_Position;\n' + |
| 30 | + 'void main() {\n' + |
| 31 | + 'gl_Position = a_Position;\n' + |
| 32 | + 'gl_PointSize = 10.0;\n' + |
| 33 | + '}\n' |
| 34 | +const FSHADER_SOURCE = |
| 35 | + ' void main() {\n'+ |
| 36 | + 'gl_FragColor = vec4(1.0, 1.0, 0.0,1.0);\n'+ |
| 37 | + '}\n' |
| 38 | + |
| 39 | + |
| 40 | +function main() { |
| 41 | + var canvas = document.getElementById('webgl') |
| 42 | + var gl = getWebGLContext(canvas) |
| 43 | + if(!gl) { |
| 44 | + console.error('Failed to get the rendering context for WebGL') |
| 45 | + return; |
| 46 | + } |
| 47 | + |
| 48 | + // 初始化着色器 |
| 49 | + if(!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) { |
| 50 | + console.error('Failed to initialize shaders.') |
| 51 | + return; |
| 52 | + } |
| 53 | + |
| 54 | + // 设置顶点着色器 |
| 55 | + var n = initVertexBuffers(gl); // [!code ++] |
| 56 | + |
| 57 | + if(n < 0) { |
| 58 | + console.error('Failed to set the positions of the vertices') |
| 59 | + return; |
| 60 | + } |
| 61 | + |
| 62 | + // // 获取attribut变量的存储位置 |
| 63 | + var a_Position = gl.getAttribLocation(gl.program, 'a_Position') |
| 64 | + // |
| 65 | + // var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor') |
| 66 | + if(a_Position < 0) { |
| 67 | + console.error('Failed to get the storage location of a_Position') |
| 68 | + return; |
| 69 | + } |
| 70 | + // |
| 71 | + |
| 72 | + // 设置canvas背景色 |
| 73 | + gl.clearColor(0.0,0.0,0.0,1.0) |
| 74 | + |
| 75 | + // 清空canvas |
| 76 | + gl.clear(gl.COLOR_BUFFER_BIT); |
| 77 | + |
| 78 | + // 绘制三个点 |
| 79 | + gl.drawArrays(gl.POINTS, 0, 1) // [!code --] |
| 80 | + gl.drawArrays(gl.POINTS, 0, n) // [!code ++] |
| 81 | + |
| 82 | + |
| 83 | +} |
| 84 | + |
| 85 | +function initVertexBuffers(gl) { // [!code ++] |
| 86 | + var vertices = new Float32Array([ // [!code ++] |
| 87 | + 0.0, 0.5, -0.5, -0.5, 0.5, -0.5 // [!code ++] |
| 88 | + ]) // [!code ++] |
| 89 | + var n = 3 // 点的个数 // [!code ++] |
| 90 | + |
| 91 | + // 创建缓冲区对象 // [!code ++] |
| 92 | + var vertexBuffer = gl.createBuffer(); // [!code ++] |
| 93 | + if(!vertexBuffer) { // [!code ++] |
| 94 | + console.error('Failed to create the buffer object') // [!code ++] |
| 95 | + return -1; // [!code ++] |
| 96 | + } // [!code ++] |
| 97 | + |
| 98 | + // 将缓冲区对象绑定到目标 // [!code ++] |
| 99 | + gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer) // [!code ++] |
| 100 | + |
| 101 | + // 向缓冲区对象中写入数据 // [!code ++] |
| 102 | + gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); // [!code ++] |
| 103 | + |
| 104 | + var a_Position = gl.getAttribLocation(gl.program, 'a_Position'); // [!code ++] |
| 105 | + |
| 106 | + // 将缓冲区对象分配给a_Position变量 // [!code ++] |
| 107 | + gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0) // [!code ++] |
| 108 | + |
| 109 | + // 连接a_Position变量与分配给它的缓冲对象 // [!code ++] |
| 110 | + gl.enableVertexAttribArray(a_Position) // [!code ++] |
| 111 | + return n // [!code ++] |
| 112 | +} |
| 113 | + |
| 114 | +``` |
| 115 | +流程: |
| 116 | + |
| 117 | +1. 获取WebGL绘图上下文 |
| 118 | +2. 初始化着色器 |
| 119 | +3. 设置点的坐标信息 |
| 120 | +4. 设置`<canvas>`背景色 |
| 121 | +5. 清空`<canvas>` |
| 122 | +6. 绘制 |
| 123 | + |
| 124 | + |
| 125 | +`initVertexBuffers()`函数的任务是创建顶点缓冲区对象,并将多个顶点的数据保存在缓冲区中,然后将缓冲区传入给顶点着色器。 |
| 126 | + |
| 127 | +`gl.drawArrays(gl.POINTS, 0, n)`, 这里的n为要画的顶点数,因为在`initVertexBuffers()`函数中利用缓冲区对象向顶点着色器传输了多个顶点数据,所以需要通过第3个参数告诉`gl.drawArrays()`函数需要绘制多少个顶点。 |
| 128 | + |
| 129 | + |
| 130 | +### 使用缓冲区对象 |
| 131 | + |
| 132 | +使用缓冲区对象需要遵循五个步骤: |
| 133 | + |
| 134 | +1. 创建缓冲区对象(gl.createBuffer()) |
| 135 | +2. 绑定缓冲区对象(gl.bindBuffer()) |
| 136 | +3. 将数据写入缓冲区对象(gl.bufferData()) |
| 137 | +4. 将缓冲区对象分配给一个attribute变量(gl.vertexAttribPointer()) |
| 138 | +5. 开启attribute变量(gl.enableVertexAttribArray()) |
| 139 | + |
| 140 | + |
| 141 | +#### 创建缓冲区对象 (gl.createBuffer()) |
| 142 | +使用`gl.createBuffer()`方法来创建缓冲区对象 |
| 143 | + |
| 144 | + |
| 145 | + |
| 146 | +| 返回值 | 描述 | |
| 147 | +|-------|-----------| |
| 148 | +| 非null | 新创建的缓冲区对象 | |
| 149 | +| null | 创建缓冲区对象失败 | |
| 150 | +| 错误 | 无 | |
| 151 | + |
| 152 | +可以使用`gl.deleteBuffer(buffer)`函数,删除被`gl.createBuffer()`创建出来的缓冲区对象 |
| 153 | + |
| 154 | +| 参数 | 描述 | |
| 155 | +|--------|-----------| |
| 156 | +| buffer | 待删除的缓冲区对象 | |
| 157 | +| 返回值 | 无 | |
| 158 | +| 错误 | 无 | |
| 159 | + |
| 160 | + |
| 161 | +#### 绑定缓冲区 (gl.bindBuffer()) |
| 162 | + |
| 163 | +将创建好的缓冲区对象绑定到`WebGL`系统中以及存在的目标上。这个目标表示缓冲区对象的用途,这样`WebGL`才能够正确处理其中的内容。 |
| 164 | + |
| 165 | + |
| 166 | + |
| 167 | +gl.bindBuffer(target, buffer) |
| 168 | + |
| 169 | +| 参数 | 描述 | |
| 170 | +|-------------------------|------------------------------------------------------------| |
| 171 | +| target | | |
| 172 | +| gl.ARRAY_BUFFER | 表示缓冲区对象中包含了顶点的数据 | |
| 173 | +| gl.ELEMENT_ARRAY_BUFFER | 表示缓冲区对象中包含了顶点的索引值 | |
| 174 | +| buffer | 指定之前由gl.createBuffer()返回的待绑定的缓冲区对象,如果指定为null,则禁用对target的绑定 | |
| 175 | +| 返回值 | 无 | |
| 176 | + |
| 177 | +| 错误 | 描述 | |
| 178 | +|--------------|------------------------------| |
| 179 | +| INVALID_ENUM | target不是上述值之一,这时将保持原有的绑定情况不变 | |
| 180 | + |
| 181 | + |
| 182 | +#### 向缓冲区对象中写入数据(gl.bufferData()) |
| 183 | + |
| 184 | +开辟空间,并向缓冲区中写入数据。 |
| 185 | + |
| 186 | + |
| 187 | + |
| 188 | +gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) |
| 189 | + |
| 190 | +将第2个参数`vertices`中的数据写入了绑定到第1个参数gl.ARRAY_BUFFER上的缓冲对象。我们不能直接向缓冲对象区写入数据,而只能向“目标”写入数据,所以要向缓冲区写入数据,必须先绑定。 |
| 191 | + |
| 192 | +`gl.bufferData(target, data, usage)` |
| 193 | + |
| 194 | +开辟存储空间,向绑定在`target`上的缓冲区对象写入数据`data` |
| 195 | + |
| 196 | +| 参数 | 描述 | |
| 197 | +|-----------------|----------------------------------------------------------------------------| |
| 198 | +| target | gl.ARRAY_BUFFER 或 gl.ELEMENT_ARRAY_BUFFER | |
| 199 | +| data | 写入缓冲区对象的数据(类型化数组) | |
| 200 | +| usage | 表示程序将如何使用存储在缓冲区对象中的数据。该参数将帮助WebGL优化操作,但是就算传入错误的值,也不会终止程序(仅降低程序运行效率) | |
| 201 | +| gl.STATIC_DRAW | 只会向缓冲区对象中写入一次数据,但需要绘制很多次;表示缓冲区的数据不会或几乎不会改变。在数据上传后,绘制操作会频繁使用这些数据,但不会对它们进行修改 | |
| 202 | +| gl.STREAM_DRAW | 只会向缓冲区对象中写入一次数据,然后绘制若干次;表示缓冲区的数据会快速改变,通常在每次绘制之前都会更新 | |
| 203 | +| gl.DYNAMIC_DRAW | 会向缓冲区对象中多次写入数据,并绘制很多次;表示缓冲区的数据会频繁改变,但不一定在每次绘制时都更新。适合在多个绘制调用之间修改数据的情况 | |
| 204 | + |
| 205 | + |
| 206 | +### 类型化数组 |
| 207 | + |
| 208 | +`javascript`中常见的`Array`对象,是一种通用的类型,既可以存储数字,也可以存错字符串,而并没有对“大量元素都是同一类型”这种情况进行优化。为了解决这个问题,`WebGL`引入了类型化数组。 |
| 209 | + |
| 210 | +浏览器事先知道数组中的数据类型,所以处理起来更加有效率 |
| 211 | + |
| 212 | +WebGL使用的各种类型化数组 |
| 213 | + |
| 214 | +| 数组类型 | 每个元素所占字节数 | 描述(c语言中的数据类型) | |
| 215 | +|--------------|-----------|--------------------------| |
| 216 | +| Int8Array | 1 | 8位整型数(signed char) | |
| 217 | +| UInt8Array | 1 | 8位无符号整型数(unsigned char) | |
| 218 | +| Int16Array | 2 | 16位整型数(signed char) | |
| 219 | +| UInt16Array | 2 | 16位无符号整型数(unsigned char) | |
| 220 | +| Int32Array | 4 | 32位整型数(signed char) | |
| 221 | +| UInt32Array | 4 | 32位无符号整型数(unsigned char) | |
| 222 | +| Float32Array | 4 | 单精度32位浮点数(float) | |
| 223 | +| Float64Array | 8 | 双精度64位符点数(double) | |
| 224 | + |
| 225 | +类型化数组的方法、属性和常量 |
| 226 | + |
| 227 | +| 方法、属性和常量 | 描述 | |
| 228 | +|--------------------|------------------------------| |
| 229 | +| get(index) | 获取第index个元素值 | |
| 230 | +| set(index, value) | 设置第index个元素的值为value | |
| 231 | +| set(array, offset) | 从第offset个元素开始将数组array中的值填充进去 | |
| 232 | +| length | 数组的长度 | |
| 233 | +| BYTES_PER_ELEMENT | 数组中每个元素所占的字节数 | |
| 234 | + |
| 235 | + |
| 236 | +**创建类型化数组的唯一方法就是使用`new`运算符** |
| 237 | + |
| 238 | +```js |
| 239 | +var vertices = new Float32Array([ |
| 240 | + 0.0, 0.5, -0.5, -0.5, 0.5, -0.5 |
| 241 | +]) |
| 242 | + |
| 243 | +// 指定数组元素的个数来创建一个空的类型化数组 |
| 244 | +var vertices = new Float32Array(4) |
| 245 | +``` |
| 246 | + |
| 247 | +**以上就是建立和使用缓冲区的前三个步骤(在WebGL系统中创建缓冲区, 绑定缓冲区对象到目标,向缓冲区对象中写入数据)** |
| 248 | + |
| 249 | + |
| 250 | +### 将缓冲区对象分配给attribute变量(gl.vertexAttribPointer()) |
| 251 | + |
| 252 | +可以使用`gl.vertexAttrib[1234]f系列函数为attribute变量分配值。但是,这些方法一次只能向attribute变量分配一个值。而现在,需要将整个数组中的所有值,一次性第分配给一个attribute变量。 |
| 253 | + |
| 254 | +gl.vertexAttribPointer()方法解决了这个问题,他可以将整个缓冲区对象分配个attribute变量。 |
| 255 | + |
| 256 | +`gl.vertexAttribPointer(location, size, type, normalized, stride, offset)` |
| 257 | + |
| 258 | +将绑定到`gl.ARRAY_BUFFER`的缓冲区对象分配给由`location`指定的`attribute`变量 |
| 259 | + |
| 260 | +| 参数 | 描述 | |
| 261 | +|-------------------|----------------------------------------------------------------------------------------| |
| 262 | +| location | 指定待分配attribute变量的存储位置 | |
| 263 | +| size | 指定缓冲区中每个顶点的分量个数(1到4)。若size比attribute变量需要的分量数小,缺失分量将按照与gl.vertexAttrib[1234]f()相同的规则不全。 | |
| 264 | +| type | 用以下类型之一来指定数据格式 | |
| 265 | +| gl.UNSIGNED_BYTE | 无符号字节,Uint8Array | |
| 266 | +| gl.SHORT | 短整型, Int16Array | |
| 267 | +| gl.UNSIGNED_SHORT | 无符号短整型, Uint16Array | |
| 268 | +| gl.INT | 整型,Int32Array | |
| 269 | +| gl.UNSIGNED_INT | 无符号整型, Unit32Array | |
| 270 | +| gl.FLOAT | 符点型,Float32Array | |
| 271 | +| normalize | 传入true或false,表明是否将非符点型的数据归一化到[0,1]或[-1,1]区间 | |
| 272 | +| stride | 指定相邻两个顶点间的字节数,默认为0 | |
| 273 | +| offset | 指定缓冲区对象中的偏移量(以字节为单位),即attribute变量从缓冲区中的何处开始存储。如果是从起始位置开始的,offset设为0 | |
| 274 | +| 返回值 | 无 | |
| 275 | + |
| 276 | +| 错误 | 描述 | |
| 277 | +|-------------------|--------------------------------------------------------| |
| 278 | +| INVALID_OPERATION | 不存在当前程序 | |
| 279 | +| INVALID_VALUE | location大于等于attribute变量的最大数目(默认为8)。或者stride或offset是负值› | |
| 280 | + |
| 281 | + |
0 commit comments