WebGL浅入浅出

豆皮粉儿们,又见面了,今天这一期,由“Kakashi”,给我们带来webgl的学习,webgl一种很强的数据可视化的图形库,同时数据可视化也是前端发展的一个重要方向之一,值得入坑和学习。

本文作者:Kakashi
前言: 本篇文章预计阅读耗时【15min】 将较为全面基础的带你学习 如何使用WebGL(偏重) && 相关原理(简略)。 经过本次学习 你将可以从0到1搭建自己的webgl简易渲染库

一 、背景: 1.1 为什么要用WebGL? 先热身一下吧,看个问题:
如果你有一堆作业没有做完,可以选择帮手替你完成,你会选择?

A. 1个老教授
B. 9个小学生
  • 选A的同学,你真的忍心让老教授帮忙写语文填空?
  • 选B的同学,你真的放心让小学生帮你解线性代数?
    所以我们要看看“作业”内容究竟是什么,有多少作业量。
  • 如果题目是 1+2 ,9个小学生 = 人海战术免费劳动力,没有技术含量,纯粹体力活,效率会很高。
  • 如果题目是线性代数 ,老教授 >> 99 个小学生。
    回到我们的主题,类似的,由于设计目标的不同,CPU和GPU也是大不相同的。它们分别针对了两种不同的应用场景:
  • CPU需要很强的通用性来处理各种不同的数据类型,同时又要逻辑判断又会引入大量的分支跳转和中断的处理。这些都使得CPU的内部结构异常复杂。
  • GPU面对的则是类型高度统一的、相互无依赖的大规模数据和不需要被打断的纯净的计算环境。

(图片来自nVidia CUDA文档。其中绿色的是计算单元,橙红色的是存储单元,橙黄色的是控制单元。)
GPU在概念上适用于高度并行计算,因为GPU可以通过计算隐藏内存访问延迟,而不是通过大数据缓存和流控制来避免内存访问延迟。
简而言之,CPU 基于低延时的设计,GPU是基于大的吞吐量设计。
1.2 什么是WebGL? WebGL是一种3D绘图标准,这种绘图技术标准允许把JavaScript和OpenGL ES 2.0结合在一起,通过增加OpenGL ES 2.0的一个JavaScript绑定,WebGL可以为HTML5 Canvas提供硬件3D加速渲染(部分计算GPU),这样Web开发人员就可以借助系统显卡来在浏览器里更流畅地展示3D场景和模型了,还能创建复杂的导航和数据视觉化。显然,WebGL技术标准免去了开发网页专用渲染插件的麻烦,可被用于创建具有复杂3D结构的网站页面,甚至可以用来设计3D网页游戏等等。
总结一下,WebGL的本质 —— JavaScript操作OpenGL接口。

当然,WebGL只是绑定了一层,内部的一些核心内容,如着色器,材质,灯光等都是需要借助GLSL ES语法来操作的。

1.2.1 WebGL / OpenGL / ES X.0之间的关系?
  • OpenGL(Open Graphics Library)
    一种图形应用程序编程接口规范(Application Programming Interface,API)。它是一种可以对图形硬件设备特性进行访问的软件库,OpenGL被设计为一个现代化的、硬件无关的接口,因此我们可以在不考虑计算机操作系统或窗口系统的前提下,在多种不同的图形硬件系统上,完全通过软件的方式实现OpenGL的接口。
  • OpenGL ES(embedded system)
    OpenGL ES是OpenGL的一个特殊版本,主要针对嵌入式设备使用,专用于嵌入式计算机、智能手机、家用游戏机等设备。OpenGL ES 2003-2004年被首次提出来,其中两次重要升级分别在2007年(OpenGL ES 2.0)和2012年(OpenGL ES 3.0),WebGL就是基于OpenGL ES 2.0的。
二、如何使用WebGL 2.1 入门示例,不再是hello world!
let gl = (document.getElementById( 'canvas' ) as HTMLCanvasElement).getContext('webgl') as WebGLRenderingContextBase; // 指定一个覆盖(清空)canvas的rgba颜色,本质是setColor,它把背景色存到了webgl system中的glCOLOR_BUFFER_BIT,得手动render一下 gl.clearColor(0.0, 0.0, 0.5, 1.0); // 清除canvas,会清除全部,再使用背景色 填充 gl.clear(gl.COLOR_BUFFER_BIT); }

【WebGL浅入浅出】运行结果&流程示意:

结论:
  1. WebGL需要依赖canvas这个载体获取对应的绘图上下文
  2. WebGL API基于OpenGL ES,函数命名相对应
  3. WebGL基于多缓冲区模型
2.2 绘制一个点 如果类似canvas 2d的话,你可能以为WebGL绘制一个点:
gl.drawColor(1.0, 1.0, 1.0, 1.0) // 点的颜色 gl.drawPoint(0, 0, 0, 10) // 点的位置大小

类似这样就可以了,不幸的是:
WebGL依赖于一种着色器(shader)的绘图机制,复杂而强大,着色器是 WebGL的核心机制。
WebGL中着色器程序可以写成字符串变量:
// 顶点着色器程序 const VSHADER_SOURCE = ` void main(){ gl_Position = vec4(0.5,0.5,0.0,1); gl_PointSize = 10.0; }`; // 片元着色器程序 const FSHADER_SOURCE = ` void main(){ gl_FragColor = vec4(1.0,0.0,0.0,1.0); }`;

当然也支持通过ajax请求读取glsl文件,又或者参考MDN GLSL示例,写在script标签中。
接下来我们初始化着色器(包括加在着色器代码,创建程序,链接,编译等一套流程):
if (!initShaders(this.gl, VSHADER_SOURCE, FSHADER_SOURCE)) { console.error('初始化着色器失败'); return; }

绘制点:
// 指定一个覆盖(清空)this.canvas的背景颜色 this.gl.clearColor(0.0, 0.0, 0.0, 1.0); // 用指定的颜色清空颜色缓冲,从而清空canvas this.gl.clear(this.gl.COLOR_BUFFER_BIT); // 将1个点绘制出来 this.gl.drawArrays(this.gl.POINTS, 0, 1); this.gl.POINTS指定WebGL的绘制模式为点绘制。

运行结果&流程示意:

(思考一下,为什么画在了屏幕右上角?悄悄提示:坐标系统)
结论:
  1. WebGL需要两种着色器,使用GLSL ES语言来编写(需要学习成本)。
    1. 顶点着色器(Vertex shader):描述顶点特性(位置,尺寸)。
    2. 片元着色器(Fragment shader):描述 片元(像素)处理过程。
  2. 着色器运行在WebGL系统中,而不是JS程序。
  3. WebGL 位置使用向量,使用右手坐标系。
    2.3 绘制多个动态的点上一节绘制出了一个点,但是点的位置、颜色之类属性是在代码中写死的,这种“硬编码”便于理解,但是缺乏可拓展性,怎么动态的设置点的信息呢?
    我们的目标,将位置信息通过Javascript动态传给顶点着色器,需要经过下面的流程:
  4. 在顶点着色器,声明attribute变量。
  5. 将attribute变量赋值给gl_Positon变量。
  6. 向attribute变量传输数据。

我们需要在第三步来绘制多个点,举个例子:
一共绘制20个点,采取的策略是:
  • 每次绘制一个点,绘制20次?
  • 绘制1次,一次性绘制20个点?
    当然选择后者。我们需要借助 缓冲区对象,一次性传入多个顶点数据。
    2.4 绘制一个箭头查看Canvas的绘图API,我们会发现它能画直线、矩形、圆、弧线、贝塞尔曲线。
    于是,我们看了看WebGL绘图API,发现:
    void gl.drawArrays(mode, first, count);

    mode指定绘制图元的方式,可能值如下:
  • gl.POINTS: 绘制一系列点。
  • gl.LINE_STRIP: 绘制一个线条。即,绘制一系列线段,上一点连接下一点。
  • gl.LINE_LOOP: 绘制一个线圈。即,绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。
  • gl.LINES: 绘制一系列单独线段。每两个点作为端点,线段之间不连接。
  • gl.TRIANGLE_STRIP:绘制一个三角带。
  • gl.TRIANGLE_FAN:绘制一个三角扇。
  • gl.TRIANGLES: 绘制一系列三角形。每三个点作为顶点。

const VERTEXRS = [V0,V1,V2,V3,V4,V5];

如图所示,WebGL可只能绘制三种图元:点、线、三角形。不过所有其他图元,从圆到球体、从立方体到复杂三维模型,都可以由一个个三角面片组成。实际上,我们可以用三角形绘制出一切你想绘制的东西。
那么绘制一个箭头的思路可以有很多,举两个例子:

不过不同的绘制方案直接是有区别的,会影响到法线,进而影响面片方向(正面还是背面)的判断,这些就不深入介绍啦。
结论:
  1. WebGL只能绘制点,线,三角形,但是万物可以由三角形组成(原则:数量越少越好)。
2.5 绘制一个正方体 2.5.1 关于三维世界 二维图形和三维图形,最明显的区别是:三维图形有深度,也就是z轴。我们需要考虑两个角度:
  1. 观察方向:观察者在什么位置,在看场景的哪一部分?

观察者的位置称为视点(eye point),从视点出发沿观察方向的射线 叫做视线(viewing direction),通过视图矩阵确定观察方向:

  1. 可视空间:观察者能看到多远?(能看多少?是否需要远小近大)
  2. 正摄投影:长方体可视空间,也称盒状空间:

正射投影可以通过投影矩阵确定可视空间:
Matrix4.setOrtho(left, right, bottom, top, near, far);

  • 透视投影:四棱锥/金字塔可视空间:

透视投影可以通过投影矩阵确定可视空间:
Matrix4.setPerspective(fov, aspect, near, far);

2.5.2 关于正方体 通过之前学习,我们联想到,两个三角形可以拼成一个矩形平面,作为正方体一面。一个正方体也就是 62 = 12个三角形,每个三角形 3个顶点,也就是 12 3 = 36 个顶点。我们的顶点着色器传入36个顶点就可以了。但是用正常逻辑一想,正方体实际上不是只有8个顶点吗?显然我们定义36个是有重复的,并没有被多个三角形公用。
如你所愿,WebGL提供了一个通过gl.drawElements函数的复用方案:
立方体被拆成6个面,每个面都由两个三角形组成,每个三角形都有三个顶点,和顶点列表中的三个点相关联。三角形列表中的数字表示该三角形的三个顶点在顶点列表中的索引值。如下图所示:

结论:
  1. WebGL绘制3D 较为复杂,需要依赖视图矩阵和投影矩阵实现 3d视觉效果,但是建模部分还是依赖于最小图元-三角形。
2.6 其他疑惑
  • 怎么样平移,旋转,缩放?
    A:线性代数上场啦。举个例子,平移需要将平移矩阵[Tx,Ty,Tz]传入顶点着色器,逐顶点操作,加到对应顶点坐标分量上,再赋值给gl_positon。旋转、缩放类似。
  • 怎么样写文字?
    A:参考链接,或者可以用tiny-sdf库 ,文字即图片的方案。
  • 怎么实现动画?
    A:WebGL不提供动画接口,主要依赖requestAnimation方法。
  • 怎么实现渐变色?
    A:借用顶点着色器缓冲区对象,通过varying变量, 从顶点着色器向片元着色器传输数据。
  • 怎么实现背景图片?
    A:纹理映射(Texture Mapping)。在片元着色器将准备好的纹理(Image转换)取出赋值给片元。
  • 怎么画圆?
    A:把正方形削成圆形。 片元着色器内可以通过坐标判断放弃绘制一些点。
三、WebGL的流程原理 简单说来,WebGL绘制过程包括以下三步:
1、获取顶点坐标

2、图元装配(即画出一个个三角形)

3、光栅化(生成片元,即一个个像素点)

完整链路:

四、WebGL的现状 看到下图,笔者不禁一把辛酸泪。

对于WebGL而言,最大的问题就是兼容性。
一.浏览器支不支持?(入门难度:安装最新chrome) WebGL普及程度相对较高。2.0版本Safari似乎正在接入,或许对WebGL2.0的推广有所助力。


二.显卡支不支持?(地狱难度:换显卡)
三.显卡和浏览器都ok的话,设置合不合理?(难以评估难度:对非开发者极不友好)
  • Case1:浏览器设置开启硬件加速。
    打开 chrome 设置(地址栏输入chrome://settings/),『显示高级设置』,找到『系统』,在『使用硬件加速模式』选项前打钩,重启浏览器。
  • Case2:浏览器设置禁用webgl
    WebGL:--disable-webgl
    打开 chrome://flags 面板(将这行字输入地址栏),ctrl/command + f 搜索『--disable-webgl』并启用该选项。重启浏览器。
  • Case3:浏览器设置显卡黑名单
    Hardware accelerated:WebGL 已启用,并获得了显卡支持Software only, hardware acceleration unavailable:WebGL 已启用,但没有显卡支持,只有软件渲染支持Unavailable:WebGL 既没有显卡支持也没有软件支持。

WebGL 应用一般需要显卡加速,如果你的浏览器显示 WebGL 未获得显卡支持(即显示为后面两项),如上图webgl2 Unavailable,则两种可能:
  1. 你电脑的显卡可能进入了 chrome 的黑名单。
  2. 2.你的显卡不行(^_^)
    有一些显卡和显卡驱动因为 bug 太多会导致浏览器崩溃甚至系统崩溃,所以很多浏览器都有一个显卡黑名单,对这些有问题的显卡和驱动,浏览器将不启用硬件加速。可以在 chrome://flags 中开启 --ignore-gpu-blacklist 来无视这个黑名单(不推荐,可能会引发浏览器崩溃或者系统崩溃)
    ---
    附录一些常用的库:
  3. Three (基于原生WebGL封装运行的三维引擎,在所有WebGL引擎中,Three.js是国内文资料最多、使用最广泛的三维引擎)
  4. stats.js (JavaScript性能监控器,可以测试webgl代码的渲染性能)
  5. dat.GUI(google开发人员创建的,是一个轻量级的图形用户界面库(GUI 组件),使用这个库可以很容易地创建出能够改变代码变量的界面组件)
  6. ShaderToy(一个酷炫有趣的网站,学习shader编程)
  7. rainbow(一个简易的webgl渲染库,包含本次全部demo??)
参考资料及鸣谢
  • 图解WebGL&Three.js工作原理:
    https://www.cnblogs.com/wanbo...
  • 《WebGL编程指南》:【必看】

    推荐阅读