WebGL从零开始(一)——认识WebGL并绘制一个点
# 前言
之前一直使用WebGL相关的库,但并未系统性的学习过,随着相关库的使用以及技术学习的必要和业务想法的需要,有必要让自己系统性学习一下WebGL,提升专业技术水平。 本篇最后将学习的资料地址附上,后续有其他参考的文献也会在相应篇章中附录。
# 一、什么是WebGL
这里引用 WebGLFundamentals (opens new window) 网站中的描述:
事实上WebGL仅仅是一个光栅化引擎,它可以根据你的代码绘制出点,线和三角形。
WebGL在电脑的GPU中运行。因此你需要使用能够在GPU上运行的代码。这样的代码需要提供成对的方法。每对方法中一个叫顶点着色器,另一个叫片段着色器,并且使用一种和C或C++类似的强类型的语言 GLSL。(GL着色语言)。每一对组合起来称作一个 program(着色程序)。
没错,WebGL是一个运行在GPU中的光栅化引擎,脱胎于 OpenGL ES,是专用于JavaScript绘制3D图形的API。
# 二、着色器
着色器分为了顶点着色器和片段(片元)着色器,它们是成对存在的方法,一起放入一个着色程序中,一般一个典型的WebGL应用会有多个着色程序。
# 1.顶点着色器
顶点着色器的主要内容就是生成顶点的坐标,着色器使用的是GLSL语言,每个着色器都有一个 main 方法,例如:
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_PointSize = 10.0;
}
2
3
4
- 这个顶点着色器的作用就是将所有的点坐标固定在原点(0,0,0)。
gl_Position是一个特殊的全局变量,这是内置好的,用于存储顶点着色器的输出位置;vec4是四维向量,包含(x,y,z,w),w是齐次坐标的缩放因子(通常为1.0)gl_PointSize也是一个全局变量,是用于设定点的大小,很好理解。- 还要注意在GLSL中
;不可少;数值要用带小数点。这两点新手特容易忘,忘了就渲染不出来,并且浏览器报错提示指向attachShader的方法。
# 2.片段着色器
片段着色器的主要内容是为当前光栅化的像素提供颜色值,例如:
precision mediump float;
void main(){
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
2
3
4
5
- 这个片段着色器的作用就是将所有的像素渲染为黑色。
precision mediump float目的是设置浮点数的精度为中等精度,mediump是 WebGL 中常用的精度级别,平衡性能和精度。当有输入变量时必须设置,否则无效。gl_FragColor也是一个特殊的全局变量,于设置当前像素(片段)的输出颜色。- 这里的
vec4变量的4个值就是rgba值。
上面只是简单的认识一下着色器,后面在案例中都会实际的应用到,包括动态变化顶点的坐标、大小、颜色,这些就需要我们传入变量进行控制。
# 三、如何实际应用-以绘制一个点为例
# 1.创建WebGL对象
在浏览器中,创建 WebGL 对象的方式是通过 canvas 实现的。
<canvas id='webgl' width='500' height='500'></canvas>
然后在js中通过 document.getElementById 获取到canvas对象;再通过canvas来获取WebGL对象。
let canvas = document.getElementById('webgl');
let gl = canvas.getContext('webgl'); // 是 webgl 非 2d
2
目前主流的浏览器都已支持webgl,有些过时的浏览器不支持,给他个提示或者其他操作。
if (!gl) {
alert('您的浏览器不支持WebGL!');
}
2
3
# canvas 2D坐标系与WebGL坐标系
canvas画布的无论多大,WebGL的坐标范围都是-1到1,每个轴!
同时 canvas画布的原点(0,0)是画布的左上角,y轴是向下的, 而WebGL的坐标原点默认是画布的中心,y轴是向上的;
# 2.创建着色器
现在需要建立着色器了,着色器语言可以用字符串的方式创建,这里的例子是以非Javascript类型的标签存放的。
<script id="vertex-shader-source" type="notjs">
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_PointSize = 10.0;
}
</script>
<script id="fragment-shader-source" type="notjs">
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0.5, 1);
}
</script>
2
3
4
5
6
7
8
9
10
11
12
上面就是非常基础的建立顶点着色器和片段着色器的语言。
有了GLSL语言,就可以编译着色器了(shader)。通过gl.createShader(type) 来创建着色器对象,再通过 gl.shaderSource(shader, source) 为着色器提供数据源,最后编译生成着色器:gl.compileShader(shader) ,例如下面:
// 创建着色器方法,输入参数:渲染上下文,着色器类型,数据源
function createShader(gl, type, source) {
const shader = gl.createShader(type); // 创建着色器对象
gl.shaderSource(shader, source); // 提供数据源
gl.compileShader(shader); // 编译 -> 生成着色器
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
const vertexShaderSource = document.querySelector("#vertex-shader-source").text;
const fragmentShaderSource = document.querySelector("#fragment-shader-source").text;
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
gl.createShader接收一个参数,创建指定的着色器对象,gl.VERTEX_SHADER顶点着色器类型gl.FRAGMENT_SHADER片段着色器类型
# 3.链接到着色程序
接下来就需要将创建好的两个成对的着色器链接(link)到一个着色程序(program)中,并且使用。
// 创建program的方法
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
// 得到创建的 program 便于后面使用
const program = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(program);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 4.渲染绘制
到目前位置,绘制一个点的基本要求已具备:webgl对象、着色器、着色程序,那么现在就画出它!
首先用gl.clearColor(0, 0, 0, 1)来清空画布,这里的 0, 0, 0, 1 对应的是 rgba 值,也就是画布是黑色的;
然后需要调用gl.clear(gl.COLOR_BUFFER_BIT)将当前颜色缓冲填充为预先设定的清除颜色(默认黑色),用于每帧渲染前重置画面;嗯,缓冲这个概念还没提及到,暂时不用管,后续会涉及,这里关注 用于每帧渲染前重置画面 这个效果就行,每次进行重新渲染都需要调用一次。
最后调用gl.drawArrays 进行渲染绘制。
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);
2
3
4
gl.drawArrays(mode, first, count)渲染绘制方法,mode是GL枚举类型,指定绘制的图形:gl.POINTS: 绘制点。gl.LINE_STRIP: 绘制一个线条。即,绘制一系列线段,上一点连接下一点。gl.LINE_LOOP: 绘制一个线圈。即,绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。(闭合)gl.LINES: 绘制一系列单独线段。每两个点作为端点,线段之间不连接。gl.TRIANGLE_STRIP:绘制一个三角带gl.TRIANGLE_FAN:绘制一个三角扇gl.TRIANGLES: 绘制一系列三角形。每三个点作为顶点。(常用)
first指定从哪个点开始绘制。count指定绘制需要使用多少个点。
最终结果如图:
# 5.完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGL(一)绘制一个点</title>
</head>
<body>
<canvas id='webgl' width='500' height='500'></canvas>
<script id="vertex-shader-source" type="notjs">
void main() {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_PointSize = 10.0;
}
</script>
<script id="fragment-shader-source" type="notjs">
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0.5, 1);
}
</script>
<script>
let canvas = document.getElementById('webgl');
let gl = canvas.getContext('webgl'); // 是 webgl 非 2d
if (!gl) {
alert('您的浏览器不支持WebGL!');
// …… 其他操作
}
// 创建着色器方法,输入参数:渲染上下文,着色器类型,数据源
function createShader(gl, type, source) {
const shader = gl.createShader(type); // 创建着色器对象
gl.shaderSource(shader, source); // 提供数据源
gl.compileShader(shader); // 编译 -> 生成着色器
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
const vertexShaderSource = document.querySelector("#vertex-shader-source").text;
const fragmentShaderSource = document.querySelector("#fragment-shader-source").text;
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
// 创建program的方法
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
// 得到创建的 program 便于后面使用
const program = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(program);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 1);
</script>
</body>
</html>
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
# 四、总结
本篇了解了什么是WebGL、着色器,以及以绘制一个点为例讲述WebGL渲染的基本流程,后续将慢慢进行其他部分的学习,比如如何通过 JS 更改点的位置、什么是缓冲、属性、全局变量、如何绘制线、面等等,慢慢来。
# 五、参考内容
- [WebGLFundamentals](WebGL 基础概念 (opens new window)) 由 Greggman 创作的权威开源教程系列,主张“从零原理出发、拒绝魔法黑盒”,专门针对 WebGL构建系统化学习路径。
- [MDN-WebGL-API](WebGLRenderingContext.drawArrays() - Web API | MDN (opens new window)) WebGL的API
# 六、本篇涉及的WebGL-API
嗯,本篇涉及的API是 WebGL 渲染必备的基础API,但一般我们不会直接使用 WebGL的API,而是使用成熟的第三方库如何 Three.js,但是我们学习或者自己编写Web三维引擎库时,这些是必用的。
gl.createShader(type)定义:用于创建一个着色器对象(shader),该对象可以使用gl.shaderSource()和gl.compileShader()方法配置着色器代码。参数:
- type:
gl.VERTEX_SHADER或gl.FRAGMENT_SHADER两者中的一个。
- type:
gl.shaderSource(shader, source)定义:用于设置WebGLShader着色器(顶点着色器及片元着色器)的 GLSL 程序代码。 参数:- shader:着色器对象。
- source:包含 GLSL 程序代码的字符串。
gl.compileShader(shader)定义:用于编译一个 GLSL 着色器,使其成为为二进制数据,然后就可以被着色器程序对象所使用。gl.getShaderParameter(shader, pname)定义:返回给定的着色器信息. 参数:- shader:着色器对象
- pname:指定要查询的信息属性名称,值:
gl.DELETE_STATUS:标示着色器是否被删除,返回值 true、false。gl.COMPILE_STATUS: 标示着色器是否编译成功gl.SHADER_TYPE: 标示着色器类型,是顶点着色器,还是片段着色器
gl.createProgram()定义:用于创建和初始化一个着色器程序对象(program)。gl.attachShader(program, shader)定义:向着色器程序对象上添加一个着色器。 参数:- program:着色器程序对象
- shader:顶点或片段着色器对象
gl.linkProgram(program)定义:链接给定的着色器程序对象,从而完成为程序的片元和顶点着色器准备 GPU 代码的过程。 参数:- program:着色器程序对象
gl.useProgram(program)定义:将定义好的着色器程序对象添加到当前的渲染状态中。 参数:- program:着色器程序对象
gl.clearColor(red, green, blue, alpha)定义:用于设置清空颜色缓冲时的颜色值。gl.clear(mask)使用预设值来清空缓冲。 mask 一个用于指定需要清除的缓冲的参数,可以是gl.COLOR_BUFFER_BIT //颜色缓冲区gl.DEPTH_BUFFER_BIT //深度缓冲区gl.STENCIL_BUFFER_BIT //模板缓冲区
gl.drawArrays(mode, first, count)mode是GL枚举类型,指定绘制的图形:gl.POINTS: 绘制点。gl.LINE_STRIP: 绘制一个线条。即,绘制一系列线段,上一点连接下一点。gl.LINE_LOOP: 绘制一个线圈。即,绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。(闭合)gl.LINES: 绘制一系列单独线段。每两个点作为端点,线段之间不连接。gl.TRIANGLE_STRIP:绘制一个三角带gl.TRIANGLE_FAN:绘制一个三角扇gl.TRIANGLES: 绘制一系列三角形。每三个点作为顶点。
first指定从哪个点开始绘制。count指定绘制需要使用多少个点。