WebGl入门教程三(绘制多个点)

本篇概念最为复杂,但是也是必须掌握的基础
理解Buffer的概念及用途在日后开发中至关重要

全篇目录

什么是Buffer

buffer是WebGL系统内部划分出的一块区域,可以存放一组顶点数据,比如顶点坐标,把顶点数据写入buffer后,着色器程序执行时就可以从buffer中读取数据,在数据量较大时尤为方便

着色器程序中虽然不能直接读buffer,但可以先通过API来指定着色器变量值为buffer的地址,再指定数据读取方式,例如从哪里开始读?读几个数?下一个顶点的数据在哪里?间接地从buffer中读取数据


数据类型

GLSL ES是强类型语言,js是弱类型的,js数组元素没有限制,而GLSL ES需要非常严格的数据,所以才有了类型化数组

支持的数组类型有

  • Int8Array
  • UInt8Array
  • Int16Array
  • UInt16Array
  • Int32Array
  • UInt32Array
  • Float32Array
  • Float64Array

区别是每个元素所占字节数不同(类型化数组有BYTES_PER_ELEMENT属性,可以获取数组内每个元素所占字节数,比如Float32Array每个元素占4个字节),与普通数组相比,类型化数组不支持push和pop方法,但针对“大量元素都是同一种类型”做了优化

1
const arrVtx = new Float32Array([-1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 0.0, 0.0]);

使用Buffer

■ 创建buffer

1
2
// 若创建失败,则返回null
const vertexBuffer = gl.createBuffer();

■ 把缓冲区对象绑定到目标

因为我们无法直接向缓冲区写入数据,只能向target写入,所以要向缓冲区写数据,必须要先绑定target与buffer。target表示缓冲区对象的用途,值为gl.ARRAY_BUFFER或者gl.ELEMENT_ARRAY_BUFFER,前者表示缓冲区对象包含了顶点数据,后者表示包含了顶点的索引值

1
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

■ 向缓冲区对象写入数据

通过gl.bufferData(target, data, usage)写入数据

1
gl.bufferData(gl.ARRAY_BUFFER, arrVtx, gl.STATIC_DRAW);
  • data是类型化数组
  • usage表示缓冲区数据用途,WebGL会根据用途进行优化

usage:

  • gl.STATIC_DRAW(只向缓冲区对象写入一次数据,但需要绘制很多次)
  • gl.STREAM_DRAW(只向缓冲区对象中写入一次数据,然后绘制若干次)
  • gl.DYNAMIC_DRAW(向缓冲区对象多次写入数据,并绘制很多次)
    区别不很明显,而且只可能会影响性能,不会影响最终结果

■ 将缓冲区对象分配给a_Position变量

buffer准备好了,现在要给着色器变量赋值,并告诉着色器这些数据待会儿怎么用

1
2
3
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// equals
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, arrVtx.BYTES_PER_ELEMENT * 2, 0);

通过gl.vertexAttribPointer(location, size, type, normalized, stride, offset)给attribute对象赋值缓冲区指针,参数含义如下:

参数名 描述
size 缓冲区中每个顶点的分量个数(1~4),表示每个顶点数据中要赋值给着色器变量的分量个数
type 数据格式,值为gl.UNSIGNED_BYTE、gl.SHORT、gl.UNSIGNED_SHORT、gl.INT、gl.UNSIGNED_INT、gl.FLOAT五种
normalized true
stride 指定相邻两个顶点间的字节数,默认为0
offset 缓冲区对象中的偏移量,从缓冲区的offset位置开始写,从头开始就是0

stride参数比较特别,如果每个顶点有n个数据,把stride置为arr.BYTES_PER_ELEMENT * n的效果和0一样。
因为当前顶点数据读取结束后,如果stride为0,则从当前顶点数据结束的位置开始读取下一个顶点的数据
如果stride非0,则从当前顶点数据开始的位置跳过stride指定的字节再开始读取

■ 连接a_Position变量和分配给它的缓冲区对象

1
gl.enableVertexAttribArray(a_Position);

绘制多个点

1
gl.drawArrays(gl.POINTS, 0, arrVtx.length / 2);

第三个参数表示要绘制的顶点个数,本例中数组长度arrVtx.length除以每个顶点的数据数2表示绘制arrVtx中的全部顶点(共5个)


demo全部代码

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
import vsSource from '~/index.vs';
import fsSource from '~/index.fs';

const canvas = document.getElementById('webgl');
const gl = canvas.getContext('webgl');

// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
// 绑定资源
gl.shaderSource(vertexShader, vsSource);
// 编译着色器
gl.compileShader(vertexShader);

// 创建片段着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
// 绑定资源
gl.shaderSource(fragmentShader, fsSource);
// 编译着色器
gl.compileShader(fragmentShader);

// 创建一个着色器程序
const glProgram = gl.createProgram();

// 把前面创建的二个着色器对象添加到着色器程序中
gl.attachShader(glProgram, vertexShader);
gl.attachShader(glProgram, fragmentShader);

// 把着色器程序链接成一个完整的程序
gl.linkProgram(glProgram);

// 使用这个完整的程序
gl.useProgram(glProgram);

// 定位变量位置
const a_Position = gl.getAttribLocation(glProgram, 'a_Position');

const arrVtx = new Float32Array([-1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 0.0, 0.0]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, arrVtx, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);

const a_PointSize = gl.getAttribLocation(glProgram, 'a_PointSize');
gl.vertexAttrib1f(a_PointSize, 30.0);

const u_FragColor = gl.getUniformLocation(glProgram, 'u_FragColor');
gl.uniform4f(u_FragColor, 1.0, 0.0, 0.0, 1.0);

gl.drawArrays(gl.POINTS, 0, arrVtx.length / 2);

本文主要参考《WebGL编程指南》

#

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×