用GPU的强大性能WebGL(Web Graphics Library)是在浏览器中渲染交互的3D图形引擎,使得前端程序员能够利用GPU的强大性能,创造出丰富的视觉体验。
渲染流程
它利用GLSL语言代码片段,在GPU上运行,代码块里面必须包括顶点着色器和片元着色器,顶点着色器用来计算一系列顶点的位置,片元着色器用来计算每个像素点的颜色插值。然后调用gl.drawArrays或gl.drawElements在GPU上执行。GPU通过以下四种方式获取数据:
- 属性(Attributes)和缓冲:属性用来指明怎么从缓冲中获取所需数据并将它提供给顶点着色器。 例如你可能在缓冲中用三个32位的浮点型数据存储一个位置值。
- 全局变量(Uniforms)
- 纹理(Textures):多数情况存放的是图像数据
- 变量(Varyings):线还是三角形,顶点着色器中设置的可变量会在片段着色器运行中获取不同的插值。
说了这么多不如直接上代码先画一个简单的正方形如图:
gitee.com/feng-lianxi... 这里面的代码不用全部都懂,只要注意哪里是片元着色器,那个是顶点着色器,怎么传递个GPU的,然后调用什么方法渲染出来就行了。
css
// Set clear color to black, fully opaque
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// Clear the color buffer with specified clear color
gl.clear(gl.COLOR_BUFFER_BIT);
这两句话的意思是将画布用黑色初始化。顶点着色器:
ini
const vsSource = `
attribute vec4 a_position; //一个4维的向量的顶点属性变量
void main() {
gl_Position = a_position; //gl_Position为系统默认变量,
}
`;
片元着色器:
ini
const fsSource = `
void main() {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); //白色,gl_FragColor为系统默认变量
}
`;
接下来就是怎么给顶点着色器定义值和赋值的问题了:GLSL通过定义缓冲区,向缓冲区绑定数据,然后规定顶点着色器以什么样的方式取缓冲区的数据。最后规定以什么样的方式在画布上画出这些点。
-
建立缓冲区并且缓冲区绑定数据
javascript// Create a buffer and put three 2d clip space points in it var positionBuffer = gl.createBuffer(); // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer) gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); var positions = [0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5]; //gl.bufferData(target, sizeOrData, usage); //target:gl.ARRAY_BUFFER:存储顶点数据,gl.ELEMENT_ARRAY_BUFFER:存储索引数据 //usage:`gl.STATIC_DRAW`: 数据内容不会或很少更改。`gl.DYNAMIC_DRAW`: 数据内容会频繁更改。`l.STREAM_DRAW`: 数据内容每次绘制时都会更改 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
-
规定顶点着色器以什么样的方式取缓冲区的数据
go// look up where the vertex data needs to go. var positionAttributeLocation = gl.getAttribLocation(program, "a_position"); // Turn on the attribute gl.enableVertexAttribArray(positionAttributeLocation); // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER) var size = 2; // 2 components per iteration var type = gl.FLOAT; // the data is 32bit floats var normalize = false; // don't normalize the data var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position var offset = 0; // start at the beginning of the buffer gl.vertexAttribPointer( positionAttributeLocation, size, type, normalize, stride, offset);
这里面规定的是从缓冲区的头部开始,一次取两个,不进行归一化处理。
-
规定以什么样的方式在画布上画出这些点
inivar primitiveType = gl.TRIANGLE_STRIP; var offset = 0; var count = 4; gl.drawArrays(gl.TRIANGLE_STRIP, offset, count);
有必要单独讲一下 drawArrays 方法:gl.drawArrays(mode, first, count):
mode
: 指定要绘制的图元类型。常见的模式包括:markdown- `gl.POINTS`: 绘制一系列点。 - `gl.LINES`: 绘制一系列单独的线段。 - `gl.LINE_STRIP`: 绘制一条连续的折线。 - `gl.LINE_LOOP`: 绘制一条闭合的折线。 - `gl.TRIANGLES`: 绘制一系列单独的三角形。 - `gl.TRIANGLE_STRIP`: 绘制一系列连续的三角形(共享顶点)。 - `gl.TRIANGLE_FAN`: 绘制一系列以第一个顶点为中心的三角形。
first
:diff- 指定从哪个顶点开始绘制。它是一个偏移量,表示从顶点数组的第一个顶点开始跳过的顶点数。
count
:diff- 指定要绘制的顶点数量。
在我们这里的意思就是,从数据开始进行绘制,绘制一系列三角形。并使用片元着色器进行填充。
坐标系统
上面注意到我们设定的坐标都是-1到1之间的数,webgl的坐标系统是右手坐标系,正中心是0点,最小值是-1,最大值是1,但是我们写的时候,大部分想基于屏幕的像素,这就需要对顶点坐标进行处理:
java
// an attribute will receive data from a buffer
attribute vec2 a_position;
uniform vec2 u_resolution;
// attribute vec4 a_position;
// all shaders have a main function
void main() {
// gl_Position is a special variable a vertex shader
// is responsible for setting
vec2 zeroToOne = a_position - u_resolution / 2.0;
vec2 zeroToTwo = zeroToOne / u_resolution * 2.0;
gl_Position = vec4(zeroToTwo, 0, 1);
}
给属性赋值:
ini
var resolutionUniformLocation = gl.getUniformLocation(program, "u_resolution");
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
这样就可以使用屏幕坐标系赋值了:
ini
var positions = [320, 240, 0, 240, 320, 0, 0, 0];
修改正方形颜色
首先定义个全局的颜色变量,片元着色器如下:
csharp
precision mediump float;
uniform vec4 u_color;
void main() {
// gl_FragColor is a special variable a fragment shader
// is responsible for setting
gl_FragColor = u_color; // return redish-purple
}
然后给片元着色器赋随机值:
javascript
// get the color
var colorUniformLocation = gl.getUniformLocation(program, "u_color");
// random color
gl.uniform4fv(colorUniformLocation, [Math.random(), Math.random(), Math.random(), 1]);
这样阵正方形每次都被渲染成不同的颜色。
通过点击事件画点
接下来我们可以思考怎么通过点击事件在画布上画点了。无非就是点击后获取屏幕坐标,转换成canvas上的坐标位置,然后赋值给bufferData,然后调用gl.drawArrays方法,将这些这个点画出来就行了:
ini
canvas.addEventListener('click', function(e) {
let x = e.clientX;
let y = e.clientY;
let rect = e.target.getBoundingClientRect();
x = x - rect.left;
y = rect.bottom - y;
console.log(x, y);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([x, y, 0, 1]), gl.STATIC_DRAW);
gl.drawArrays(gl.POINTS, 0, 1);
});
总结
你可能已经体会到webgl的工作其实比较简单,就是根据顶点着色器和片元着色器,去绘制点、线、三角形。虽然三维图形很复杂,只是程序员的计算复杂了。webgl的api只做光栅化处理,这个概念还是比较容易理解的。这一节有了大概的认识后,我们下一节将深入讲解webgl的基本原理。