作为webgl的第一篇文章,首先我推荐几个学习webgl(opengl)的资源:
learnopengl中文官网:https://learnopengl-cn.github.io/, 这个教程是学习opengl最好的地方,因为webgl是基于opengl的,所以我认为这也是学习webgl最好的地方,如果你熟悉C++的话,建议根据这个教程学习。
webgl理论基础:https://webglfundamentals.org/webgl/lessons/zh_cn/, 这个教程是讲解webgl的,从原理上讲解webgl,但是我认为这个教程没有learnopengl中文官网的教程详细,很多地方直接帖代码,并没有讲解清楚。但是它的确是我所看到的讲解webgl中不错的教程。
learnwebgl: http://learnwebgl.brown37.net/index.html, 一套完整的webgl教程,但是只有英文版本,我也只是看了其中的几篇文章,内容讲解的很详细。
这篇文章主要讲解webgl的完整的绘制过程。
gl的绘制过程可以分为以下四个步骤:
这个阶段首先是创建WebGLRenderingContext,你可以将其看作是一根画笔,我们可以利用它完成三维图形的绘制。
const gl = canvas.getContext("webgl");
if (!gl) {
alert("你不能使用webgl");
}
告诉webgl绘制的目标窗口大小。
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
使用指定颜色清空屏幕,防止之前的绘制污染画布。
gl.clearColor(0, 0, 0, 0); // 设定clearColor的颜色缓冲
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 用clearColor的颜色,清屏
着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。着色器入口点是main函数。与Javascript不同的是GLSL是强类型语言,如果你习惯了javascript这种弱类型语言,开始写GLSL可能会有些不习惯,例如将int类型传递给float类型会导致编译错误,不过多写总会适应。
在webGL(openGL)中所有的东西都是3D的,但是显示器的屏幕却是2D的,所以webGL的主要工作就是将3D的点转化成屏幕上的2D的点。这个过程由图形渲染管线(简称管线)管理,管线的主要工作分为两部分:
以下是learnoopengl中文官网给出的着色器的定义。
图形渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。
管线的主要工作过程可用下图表示:
我们一般只关心顶点着色器和片段着色器,非必要一般不会去写几何着色器。
接下来,通过绘制一个三角形,来讲解。
顶点着色器主要负责,接收用户输入的点坐标数据和每个点的颜色数据。因为我们要绘制一个三角形,所以需要传入三角形的顶点坐标,在webgl中顶点坐标通过attribute传递。下面是顶点着色器的代码。
// 顶点着色器代码
const vertex_source = `
attribute vec3 aPos;
void main() {
gl_Position = vec4(aPos, 1);
}
`;
gl_Position是webgl内置的变量,用于接收要绘制的顶点坐标。
接下来创建并编译顶点着色器,使用getShaderParameter获取编译信息,抛出编译失败错误。
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertex_source); // 绑定着色器代码
gl.compileShader(vertexShader); // 编译着色器
var success = gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS);
if (!success) {
throw "colud not compile vertex shader:" + gl.getShaderInfoLog(vertexShader);
}
片段着色器主要负责处理当前顶点的颜色,也是利用webgl内置的变量gl_FragColor接受颜色值,传递给下一个阶段。下面是片段着色器代码。
// 片段着色器代码
const fragment_source = `
precision mediump float;
uniform vec3 uColor;
void main() {
gl_FragColor = vec4(uColor, 1.0);
}
`;
这里引入了一个uniform全局变量,与attribute不同的是,uniform在每个顶点处理中都保持一致,所以称为全局变量。
同样,也需要创建和编译片段着色器。
// 创建片段着色器
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragment_source); // 绑定着色器代码
gl.compileShader(fragmentShader); // 编译着色器
success = gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS);
if (!success) {
throw "colud not compile vertex shader:" + gl.getShaderInfoLog(fragmentShader);
}
将顶点着色器和片段着色器链接成一个程序。和编译着色器类似,也可以通过getProgramParameter方法获得链接信息,如果失败抛出链接错误。最后链接成功了,删除着色器。最后程序运行必须要调用useProgram方法使用创建的着色器程序,useProgram必须在设置变量数据前调用。
// 链接着色器程序
var program = gl.createProgram();
gl.attachShader(program, vertexShader); // 添加着色器
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!success) {
throw "colud not link shader: " + gl.getProgramInfoLog(program);
}
// 已经链接成程序,可以删除着色器
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
gl.useProgram(program);
在glsl中,我们设定了一个attribute变量和一个uniform变量,需要我们手动为这两个变量赋值。
attribute接受的是一组数据,通常是顶点坐标值或者顶点的颜色值。过程如下:
// 三角形的顶点数据
const vertexs: number[] = [
0, 0.5, 0,
-0.5, 0, 0,
0.5, 0, 0
];
const aPosAttLocation = gl.getAttribLocation(program, "aPos"); // 获得变量位置
const aPosAttBuffer = gl.createBuffer(); // 创建缓冲
gl.bindBuffer(gl.ARRAY_BUFFER, aPosAttBuffer); // 绑定缓冲到指定类型
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexs), gl.STATIC_DRAW); // 传入数据
gl.vertexAttribPointer(aPosAttLocation, 3, gl.FLOAT, false, 0, 0); // 告诉gl如何解析数据
gl.enableVertexAttribArray(aPosAttLocation); // 启用数据
因为,webgl的标准屏幕空间范围为-1~+1,所以这里坐标中的0.5表示在画布的四分之一位置,关于屏幕空间和现代openGl中的坐标系统,我将在下一篇文章中讲解。
uniform全局变量,在所有顶点渲染过程中保持一致,传递数据过程相对比较简单。
const uColorLocation = gl.getUniformLocation(program, "uColor");
gl.uniform3f(uColorLocation, 0.36, 0.42, 0.60);
将三角形的颜色设置为rgb(0.36, 0.42, 0.60)。
直接调用如下代码绘制:
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.TRIANGLES指定了绘制的模式,表示绘制一个三角形,第二个参数表示需要运行的起始顶点索引,第三个表示着色器运行的总的顶点个数。
按照上面的步骤,打开浏览器,应该能看到下图所示的三角形:
如果你存在疑惑,没有得到想要的结果,可以在这里找到源码:https://github.com/pengfeiw/webgl-demos/blob/master/src/demos/webglStart/index.ts。
(完)