WebGL 与动画实现|青训营笔记

112 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的的第14天

初始 WebGL

Why WebGL / Why GPU ?

  • WebGL 是什么?
    • GPU ≠ WebGL ≠ 3D
  • WebGL 为什么不像其他前端技术那么简单?
    • WebGL 涉及到硬件底层的一些概念和原理

Modern Graphics System

image.png

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

图形绘制的过程: image.png

  1. 对图像进行轮廓提取/meshing ,然后对轮廓进行网格化;
  2. 对网格的图元进行光栅化;
  3. 光栅化之后把点阵数据送到帧缓存;
  4. 读取帧缓存的内容,显示到设备上(渲染)。

The Pipeline

image.png

  • 把数据处理成光栅或者说是像素的阵列,这个过程叫做渲染管线。
  • 图形系统可以定制不同的渲染管线,它是一个流程化的过程。

        渲染管线处理(稍微不完全恰当的比喻):它就是像是活字印刷一样,把图源给拼接起来,然后存到帧缓冲当中去,之后把它批量的渲染到设备上,最终图形被渲染出来。

CPU vs GPU

处理单元为什么要分成 CPU 和 GPU ? image.png image.png
CPU 处理能力比较强大的一个处理单元,运算能力很强大;一般情况下,一个管道对应 CPU 的一个核, CPU 的内核越多相当于管道越多,可以同时处理的任务越多,但是 CPU 的核再多也不可能有成千上万甚至十万百万的内核(目前的结构上做不到)。

一张 800*600 的图片,它的光栅就是它的像素有48万个,假设每个像素的运算都很简单,如果要同时处理这48万个像素的话,就像把一堆沙子传送给 CPU , CPU 处理虽然强大,但是它的一个内核还是要串行处理,所以CPU 来处理这样的任务效率就会比较低。

image.png

  • GPU 由大量的小运算单元构成
  • 每个运算单元只负责处理很简单的计算
  • 每个运算单元彼此独立
  • 因此所有计算可以并行处理

WebGL & OpenGL

WebGL 实际上是 OpenGL 的子集,它是 OpenGL 在浏览器端的实现。OpenGL 是图形渲染的引擎,在嵌入式设备是 OpenGL ES 。 web.eecs.umich.edu/~sugih/cour… image.png

WebGL Startup

  1. 创建 WebGL 上下文
  2. 创建 WebGL Program
    • 把数据送到 GPU 当中, GPU 通过代码(不是 JavaScript 代码,它是用 GLSL 语言写的)处理、运算数据把数据变成像素点的颜色、透明度等信息,运行这个语言、处理像素的过程是通过 WebGL Program 进行的,在 WebGL Program 里面会有渲染管线,渲染管线里面会提供的处理代码(一般叫着色器,因为它是一个着色的过程),把内容处理好之后就把这些数据送入缓冲区。
  3. 将数据存入缓冲区
  4. 将缓冲区数据读取到 GPU
  5. GPU 执行 WebGL 程序,输出结果

image.png

Create WebGL Context

image.png
考虑老版本浏览器兼容性问题: image.png

The Shaders (着色器)

在 WebGL 标准的管线里面有两种着色器:

  1. Vertex Shader (顶点着色器)
    image.png
    • 主要处理图形的轮廓。
  2. Fragment Shader (片段着色器) image.png
    • 轮廓处理好之后,把点光栅化以后,把像素点给映射到片段着色器中,片段着色器用来处理颜色。

Create WebGL Program

利用顶点着色器和片段着色器来创建 WebGL 的 Program 。 image.png

Data to Frame Buffer

WebGL 里面的坐标系:
image.png
与 HTML 坐标系不同:

  • y 轴方向相反。
  • WebGL 坐标轴最小值是-1,最大值是1。
  • WebGL 坐标系是以 canvas 的中心点为坐标原点, HTML 坐标系是以屏幕左上角的点为坐标原点。

image.png
通过 Typed Array (类型数组)方式把数据告诉 WebGL 代码。 image.png gl.createBuffer() :创建缓冲区;通过 bindBuffer 方法把缓冲区跟 WebGL 的上下文进行绑定;通过 bufferData 方法把数据(points)送到缓冲区。

Frame Buffer to GPU

image.png 顶点着色器同时执行了三个顶点,被同时执行了三次。

Output

image.png
输出结果:
image.png code.juejin.cn/pen/7119692…

为什么 WebGL 那么难?

2D 绘制:
image.png
只绘制一个纯色的三角形,整体上2D 比 WebGL 更简单,但绘制效率是差不多的。

如果绘制10万个三角形,2D 是一个指令系统,需要 for 循环一个一个三角形去绘制,在这种情况下,三角形的位置需要人工去绘制,所以再怎么底层优化也没办法提高效率,但是 WebGL 会把三角形的所有顶点全部取出来,然后把它存成一个很大的数组,接着对数组进行压缩,压缩完以后全部顶点结构化,所有顶点一个批次直接传递给渲染管线,渲染管线里的顶点着色器同时处理这30万个顶点,处理完后传给片段着色器,片段着色器同时处理这10万个三角形里的上百万个像素。所以 2D 渲染10万次, WebGL 渲染一次,性能差别非常大。

WebGL 原生的写法很难,我们可以把它做一个封装。

Mesh.js (2D 图形库)

绘制多边形通用三角剖分的方式, mesh.js 对三角剖分进行了封装。 image.png
记得引入 mesh.js (import {Renderer, Figure2D, Mesh2D} from '@mesh.js/core'与第一行相当)

Draw Polygon with 2D Triangulations

使用 Earcut (割耳朵)算法进行三角剖分: image.png image.png

3D Meshing

image.png 在3D 里面跟2D 是一样的,因为3D 的曲面比较复杂,所以一般情况下很少在3D 实时绘制时去做三角剖分,通常情况下都是把模型数据离线的用一些设计工具给处理好,然后把这些三角形的数据给导出,接着把数据直接导入给 WebGL 代码,所以我们不会实时地对三维物体的轮廓进行三角剖分。 spritejs.com/demo/#/3d/w…

Transforms

平移:
image.png
旋转: image.png 缩放:
image.png
旋转和缩放是线性变换,从线性变换到齐次矩阵。 image.png
若干个线性矩阵乘以P0 , M1×M2×……×Mn 定义成新的矩阵 M 。
通过齐次矩阵(第三个)把平移变成线性变换(第二个),齐次矩阵需要升维的(原来是一个二维的,就是一个2D 的线性变换要用一个三维的齐次矩阵(最底下一行:0 0 …… 0 1)来处理)

Apply Transforms

Vertex Shader :
image.png

image.png code.juejin.cn/pen/7119694…

3D Matrix

3D 标准模型的四个齐次矩阵(mat4):

  1. 投影矩阵 Projection Matrix ,用来处理坐标系的。
  2. 模型矩阵 Model Matrix ,对模型的顶点做线性变换,来去改变图形的大小、位置、旋转方向。
  3. 视图矩阵 View Matrix ,3D 视图还有个视角问题,模拟摄像机在某个位置观察物体。
  4. 法向量矩阵 Normal Matrix ,物体表面的每个点有垂直向外的法线,我们要得到法线的信息,法线信息是用法向量矩阵描述的。

Read more

The book of shaders
Mesh.js
glsl-doodle
SpriteJS
ThreeJS
ShaderToy
css-doodle