你好,这是一篇硬核文章。需要潜心阅读,如果你觉得有用请点赞收藏!

今天,我们要讲的是 WebGL
WebGL 图形系统,也是最难学的一个。为啥说它难学呢?我觉得这主要有两个原因。
1.WebGL 这种技术本身就是用来解决最复杂的视觉呈现的。比如说,大批量绘制复杂图形和 3D 模型,这类比较有难度的问题就适合用 WebGL 来解决。

2.WebGL 相对于其他图形系统来说,是一个更靠近底层的系统。因为,不管是 HTML/CSS、SVG 还是 Canvas,都主要是使用其 API 来绘制图形的,所以我们不必关心它们具体的底层机制。

3.我们只要理解创建 SVG 元素的绘图声明,学会执行 Canvas 对应的绘图指令,能够将图形输出,这就够了。

4.要使用 WebGL 绘图,我们必须要深入细节里。我们必须要和内存、GPU 打交道,真正控制图形输出的每一个细节。

5.想要学好 WebGL,我们必须先理解一些基本概念和原理。那今天,我会从图形系统的绘图原理开始讲起,主要来讲 WebGL 最基础的概念,包括 GPU、渲染管线、着色器。然后,我会带你用 WebGL 绘制一个简单的几何图形。希望通过这个可视化的例子,能够帮助你理解 WebGL 绘制图形的基本原理,打好绘图的基础。

图形系统是怎样绘图的?

我们来说说计算机图形系统的主要组成部分,以及它们在绘图过程中的作用。知道了这些,我们就能很容易理解计算机图形系统绘图的基本原理了。

一个通用计算机图形系统主要包括 6 个部分,分别是输入设备、中央处理单元、图形处理单元、存储器、帧缓存和输出设备

光栅(Raster):几乎所有的现代图形系统都是基于光栅来绘制图形的,光栅就是指构成图像的像素阵列。
像素(Pixel):一个像素对应图像上的一个点,它通常保存图像上的某个具体位置的颜色等信息。
帧缓存(Frame Buffer):在绘图过程中,像素信息被存放于帧缓存中,帧缓存是一块内存地址。
CPU(Central Processing Unit):中央处理单元,负责逻辑计算。
GPU(Graphics Processing Unit):图形处理单元,负责图形计算。

1658479436(1).png

知道了这些概念,我带你来看一个典型的绘图过程,帮你来明晰一下这些概念的实际用途。
首先,数据经过 CPU 处理,成为具有特定结构的几何信息。
然后,这些信息会被送到 GPU 中进行处理。在 GPU 中要经过两个步骤生成光栅信息。这些光栅信息会输出到帧缓存中,最后渲染到屏幕上。

1658479529(1).png

这个绘图过程是现代计算机中任意一种图形系统处理图形的通用过程。它主要做了两件事。

1.一是对给定的数据结合绘图的场景要素(例如相机、光源、遮挡物体等等)进行计算,最终将图形变为屏幕空间的 2D 坐标。
2.二是为屏幕空间的每个像素点进行着色,把最终完成的图形输出到显示设备上。这整个过程是一步一步进行的,前一步的输出就是后一步的输入,所以我们也把这个过程叫做渲染管线(RenderPipelines)。在这个过程中,CPU 与 GPU 是最核心的两个处理单元,它们参与了计算的过程。

下面绘图实战来了,请准备好小凳子

WebGL 完整的绘图过程实在比较复杂,为了帮助你理解,请先了解绘图流程后然后再进行代码编写!

1658480089(1).png

1.创建 WebGL 上下文
创建 WebGL 上下文这一步和 Canvas2D 的使用几乎一样。

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

2.创建 WebGL 程序

这里的 WebGL 程序是一个 WebGLProgram 对象,它是给 GPU 最终运行着色器的程序,而不是我们正在写的三角形的 JavaScript 程序。

要创建这个 WebGL 程序,我们需要编写两个着色器(Shader)。着色器是用 GLSL 这种编程语言编写的代码片段,这里我们先不用过多纠结于 GLSL 语言,在后续的课程中我们会详细讲解。那在这里,我们只需要理解绘制三角形的这两个着色器的作用就可以了。

const vertex = `
  attribute vec2 position;

  void main() {
    gl_PointSize = 1.0;
    gl_Position = vec4(position, 1.0, 1.0);
  }
`;


const fragment = `
  precision mediump float;

  void main()
  {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  }    
`;

那我们为什么要创建两个着色器呢?这就需要我们先来理解 顶点和图元 这两个基本概念了。
在绘图的时候,WebGL 是以顶点和图元来描述图形几何信息的
顶点就是几何图形的顶点,比如,三角形有三个顶点,四边形有四个顶点。
图元是 WebGL 可直接处理的图形单元,由 WebGL 的绘图模式决定,有点、线、三角形等等。

因此,WebGL 绘制一个图形的过程,一般需要用到两段着色器,
一段叫顶点着色器(Vertex Shader)负责处理图形的顶点信息,
另一段叫片元着色器(Fragment Shader)负责处理图形的像素信息。

更具体点来说,我们可以把顶点着色器理解为处理顶点的 GPU 程序代码。
它可以改变顶点的信息(如顶点的坐标、法线方向、材质等等)
从而改变我们绘制出来的图形的形状或者大小等等。
顶点处理完成之后,WebGL 就会根据顶点和绘图模式指定的图元,计算出需要着色的像素点,然后对它们执行片元着色器程序。
简单来说,就是对指定图元中的像素点着色。WebGL 从顶点着色器和图元提取像素点给片元着色器执行代码的过程,就是我们前面说的生成光栅信息的过程,我们也叫它光栅化过程。所以,片元着色器的作用,就是处理光栅化后的像素信息。

创建着色器的目的是为了创建 WebGL 程序,那我们应该如何用顶点着色器和片元着色器代码,来创建 WebGL 程序呢?首先,因为在 JavaScript 中,顶点着色器和片元着色器只是一段代码片段,所以我们要将它们分别创建成 shader 对象。代码如下所示:

const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertex);
gl.compileShader(vertexShader);


const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragment);
gl.compileShader(fragmentShader);

接着,我们创建 WebGLProgram 对象,并将这两个 shader 关联到这个 WebGL 程序上。WebGLProgram 对象的创建过程主要是添加 vertexShader 和 fragmentShader,然后将这个 WebGLProgram 对象链接到 WebGL 上下文对象上。代码如下:

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

最后,我们要通过 useProgram 选择启用这个 WebGLProgram 对象。这样,当我们绘制图形时,GPU 就会执行我们通过 WebGLProgram 设定的 两个 shader 程序了。

gl.useProgram(program);

好了,现在我们已经创建并完成 WebGL 程序的配置。接下来, 我们只要将三角形的数据存入缓冲区,也就能将这些数据送入 GPU 了。那实现这一步之前呢,我们先来认识一下 WebGL 的坐标系。

预知后续如何请听下回分解...


森森
7 声望0 粉丝

技术积累,分享