这是我参与「第四届青训营 」笔记创作活动的的第14天
初始 WebGL
Why WebGL / Why GPU ?
- WebGL 是什么?
- GPU ≠ WebGL ≠ 3D
- WebGL 为什么不像其他前端技术那么简单?
- WebGL 涉及到硬件底层的一些概念和原理
Modern Graphics System
- 光栅(Raster):几乎所有的现代图形系统都是基于光栅来绘制图形的,光栅就是指构成图像的像素阵列。
- 像素(Pixel):一个像素对应图像上的一个点,它通常保存图像上的某个具体位置的颜色等信息。
- 帧缓存(Frame Buffer):在绘图过程中,像素信息被存放于帧缓存中,帧缓存是一块内存地址。
- CPU (Central Processing Unit):中央处理单元,负责逻辑计算。
- GPU (Graphics Processing Unit):图形处理单元,负责图形计算。
图形绘制的过程:
- 对图像进行轮廓提取/meshing ,然后对轮廓进行网格化;
- 对网格的图元进行光栅化;
- 光栅化之后把点阵数据送到帧缓存;
- 读取帧缓存的内容,显示到设备上(渲染)。
The Pipeline
- 把数据处理成光栅或者说是像素的阵列,这个过程叫做渲染管线。
- 图形系统可以定制不同的渲染管线,它是一个流程化的过程。
渲染管线处理(稍微不完全恰当的比喻):它就是像是活字印刷一样,把图源给拼接起来,然后存到帧缓冲当中去,之后把它批量的渲染到设备上,最终图形被渲染出来。
CPU vs GPU
处理单元为什么要分成 CPU 和 GPU ?
CPU 处理能力比较强大的一个处理单元,运算能力很强大;一般情况下,一个管道对应 CPU 的一个核, CPU 的内核越多相当于管道越多,可以同时处理的任务越多,但是 CPU 的核再多也不可能有成千上万甚至十万百万的内核(目前的结构上做不到)。
一张 800*600 的图片,它的光栅就是它的像素有48万个,假设每个像素的运算都很简单,如果要同时处理这48万个像素的话,就像把一堆沙子传送给 CPU , CPU 处理虽然强大,但是它的一个内核还是要串行处理,所以CPU 来处理这样的任务效率就会比较低。
- GPU 由大量的小运算单元构成
- 每个运算单元只负责处理很简单的计算
- 每个运算单元彼此独立
- 因此所有计算可以并行处理
WebGL & OpenGL
WebGL 实际上是 OpenGL 的子集,它是 OpenGL 在浏览器端的实现。OpenGL 是图形渲染的引擎,在嵌入式设备是 OpenGL ES 。
web.eecs.umich.edu/~sugih/cour…
WebGL Startup
- 创建 WebGL 上下文
- 创建 WebGL Program
- 把数据送到 GPU 当中, GPU 通过代码(不是 JavaScript 代码,它是用 GLSL 语言写的)处理、运算数据把数据变成像素点的颜色、透明度等信息,运行这个语言、处理像素的过程是通过 WebGL Program 进行的,在 WebGL Program 里面会有渲染管线,渲染管线里面会提供的处理代码(一般叫着色器,因为它是一个着色的过程),把内容处理好之后就把这些数据送入缓冲区。
- 将数据存入缓冲区
- 将缓冲区数据读取到 GPU
- GPU 执行 WebGL 程序,输出结果
Create WebGL Context
考虑老版本浏览器兼容性问题:
The Shaders (着色器)
在 WebGL 标准的管线里面有两种着色器:
- Vertex Shader (顶点着色器)
- 主要处理图形的轮廓。
- Fragment Shader (片段着色器)
- 轮廓处理好之后,把点光栅化以后,把像素点给映射到片段着色器中,片段着色器用来处理颜色。
Create WebGL Program
利用顶点着色器和片段着色器来创建 WebGL 的 Program 。
Data to Frame Buffer
WebGL 里面的坐标系:
与 HTML 坐标系不同:
- y 轴方向相反。
- WebGL 坐标轴最小值是-1,最大值是1。
- WebGL 坐标系是以 canvas 的中心点为坐标原点, HTML 坐标系是以屏幕左上角的点为坐标原点。
通过 Typed Array (类型数组)方式把数据告诉 WebGL 代码。
gl.createBuffer() :创建缓冲区;通过 bindBuffer 方法把缓冲区跟 WebGL 的上下文进行绑定;通过 bufferData 方法把数据(points)送到缓冲区。
Frame Buffer to GPU
顶点着色器同时执行了三个顶点,被同时执行了三次。
Output
输出结果:
code.juejin.cn/pen/7119692…
为什么 WebGL 那么难?
2D 绘制:
只绘制一个纯色的三角形,整体上2D 比 WebGL 更简单,但绘制效率是差不多的。
如果绘制10万个三角形,2D 是一个指令系统,需要 for 循环一个一个三角形去绘制,在这种情况下,三角形的位置需要人工去绘制,所以再怎么底层优化也没办法提高效率,但是 WebGL 会把三角形的所有顶点全部取出来,然后把它存成一个很大的数组,接着对数组进行压缩,压缩完以后全部顶点结构化,所有顶点一个批次直接传递给渲染管线,渲染管线里的顶点着色器同时处理这30万个顶点,处理完后传给片段着色器,片段着色器同时处理这10万个三角形里的上百万个像素。所以 2D 渲染10万次, WebGL 渲染一次,性能差别非常大。
WebGL 原生的写法很难,我们可以把它做一个封装。
Mesh.js (2D 图形库)
绘制多边形通用三角剖分的方式, mesh.js 对三角剖分进行了封装。
记得引入 mesh.js (import {Renderer, Figure2D, Mesh2D} from '@mesh.js/core'与第一行相当)
Draw Polygon with 2D Triangulations
使用 Earcut (割耳朵)算法进行三角剖分:
3D Meshing
在3D 里面跟2D 是一样的,因为3D 的曲面比较复杂,所以一般情况下很少在3D 实时绘制时去做三角剖分,通常情况下都是把模型数据离线的用一些设计工具给处理好,然后把这些三角形的数据给导出,接着把数据直接导入给 WebGL 代码,所以我们不会实时地对三维物体的轮廓进行三角剖分。
spritejs.com/demo/#/3d/w…
Transforms
平移:
旋转:
缩放:
旋转和缩放是线性变换,从线性变换到齐次矩阵。
若干个线性矩阵乘以P0 , M1×M2×……×Mn 定义成新的矩阵 M 。
通过齐次矩阵(第三个)把平移变成线性变换(第二个),齐次矩阵需要升维的(原来是一个二维的,就是一个2D 的线性变换要用一个三维的齐次矩阵(最底下一行:0 0 …… 0 1)来处理)
Apply Transforms
Vertex Shader :
3D Matrix
3D 标准模型的四个齐次矩阵(mat4):
- 投影矩阵 Projection Matrix ,用来处理坐标系的。
- 模型矩阵 Model Matrix ,对模型的顶点做线性变换,来去改变图形的大小、位置、旋转方向。
- 视图矩阵 View Matrix ,3D 视图还有个视角问题,模拟摄像机在某个位置观察物体。
- 法向量矩阵 Normal Matrix ,物体表面的每个点有垂直向外的法线,我们要得到法线的信息,法线信息是用法向量矩阵描述的。
Read more
The book of shaders
Mesh.js
glsl-doodle
SpriteJS
ThreeJS
ShaderToy
css-doodle