WebGL从零开始(一)——认识WebGL并绘制一个点

2/22/2026 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;  
}
1
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);
}
1
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>
1

然后在js中通过 document.getElementById 获取到canvas对象;再通过canvas来获取WebGL对象。

let canvas = document.getElementById('webgl');
let gl = canvas.getContext('webgl'); // 是 webgl 非 2d
1
2

目前主流的浏览器都已支持webgl,有些过时的浏览器不支持,给他个提示或者其他操作。

if (!gl) {
  alert('您的浏览器不支持WebGL!');
}
1
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>
1
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);
1
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);
1
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);
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>
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
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 更改点的位置、什么是缓冲、属性、全局变量、如何绘制线、面等等,慢慢来。

# 五、参考内容

  1. [WebGLFundamentals](WebGL 基础概念 (opens new window)) 由 Greggman 创作的权威开源教程系列,主张“从零原理出发、拒绝魔法黑盒”,专门针对 WebGL构建系统化学习路径。
  2. [MDN-WebGL-API](WebGLRenderingContext.drawArrays() - Web API | MDN (opens new window)) WebGL的API

# 六、本篇涉及的WebGL-API

嗯,本篇涉及的API是 WebGL 渲染必备的基础API,但一般我们不会直接使用 WebGL的API,而是使用成熟的第三方库如何 Three.js,但是我们学习或者自己编写Web三维引擎库时,这些是必用的。

  1. gl.createShader(type) 定义:用于创建一个着色器对象(shader),该对象可以使用 gl.shaderSource()gl.compileShader()方法配置着色器代码。

    参数

    • type:gl.VERTEX_SHADERgl.FRAGMENT_SHADER两者中的一个。
  2. gl.shaderSource(shader, source) 定义:用于设置 WebGLShader 着色器(顶点着色器及片元着色器)的 GLSL 程序代码。 参数

    • shader:着色器对象。
    • source:包含 GLSL 程序代码的字符串。
  3. gl.compileShader(shader) 定义:用于编译一个 GLSL 着色器,使其成为为二进制数据,然后就可以被着色器程序对象所使用。

  4. gl.getShaderParameter(shader, pname) 定义:返回给定的着色器信息. 参数

    • shader:着色器对象
    • pname:指定要查询的信息属性名称,值: gl.DELETE_STATUS:标示着色器是否被删除,返回值 true、false。 gl.COMPILE_STATUS: 标示着色器是否编译成功 gl.SHADER_TYPE: 标示着色器类型,是顶点着色器,还是片段着色器
  5. gl.createProgram() 定义:用于创建和初始化一个着色器程序对象(program)。

  6. gl.attachShader(program, shader) 定义:向着色器程序对象上添加一个着色器。 参数

    • program:着色器程序对象
    • shader:顶点或片段着色器对象
  7. gl.linkProgram(program) 定义:链接给定的着色器程序对象,从而完成为程序的片元和顶点着色器准备 GPU 代码的过程。 参数

    • program:着色器程序对象
  8. gl.useProgram(program) 定义:将定义好的着色器程序对象添加到当前的渲染状态中。 参数

    • program:着色器程序对象
  9. gl.clearColor(red, green, blue, alpha) 定义:用于设置清空颜色缓冲时的颜色值。

  10. gl.clear(mask) 使用预设值来清空缓冲。 mask 一个用于指定需要清除的缓冲的参数,可以是

    • gl.COLOR_BUFFER_BIT //颜色缓冲区
    • gl.DEPTH_BUFFER_BIT //深度缓冲区
    • gl.STENCIL_BUFFER_BIT //模板缓冲区
  11. 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 指定绘制需要使用多少个点。
Last Updated: 7/16/2026, 8:34:46 AM