WebGL调研(一)
一、背景
公司各类项目中绘图的使用评率很高,用的第三方库都是Echart,而Echart在大量数据渲染的性能问题一直是项目中的痛点,众所周知,Echart的底层渲染基于zrender, zrender是二维绘图引擎,它提供 Canvas、SVG、VML 等多种渲染方式,底层的渲染是基于下层图形库,进行cpu渲染,在数据不大时,cpu渲染与gpu渲染性能区别不大,而在渲染大量数据时,gpu 绘图性能优势就体现出来,而浏览器的gpu渲染是基于WebGL
二、WebGL 介绍
WebGL(Web图形库)是一个JavaScript API,可在任何兼容的Web浏览器中渲染高性能的交互式3D和2D图形,而无需使用插件。WebGL通过引入一个与OpenGL ES 2.0非常一致的API来做到这一点,该API可以在HTML5 <canvas>元素中使用。 这种一致性使API可以利用用户设备提供的硬件图形加速。
使用:
const canvas = document.querySelector("#glcanvas");
const gl = canvas.getContext('webgl')|| canvas.getContext('experimental-webgl');
experimental-webgl 与 webgl 的区别在于,experimental-webgl是浏览器供应商在webgl规范尚未标准化的添加的前缀,基本所有的浏览器两者已没有区别
三、WebGL框架与引擎介绍
WebGL 框架和引擎按照定位可以分成这三种类型:
WebGL 封装:定位是简化 WebGL 开发,最大的特点是必须自己写 GLSL 才能用。
渲染引擎:定位是三维物体及场景展示,一般会抽象出场景、相机、灯光等概念,上手门槛低,不需要自己写 GLSL。
游戏引擎:定位是游戏开发,在前面的渲染引擎基础上,还提供了骨骼动画、物理引擎、AI、GUI 等功能,以及可视化编辑器来设计关卡,支撑大型游戏的开发。
WebGL封装
由于WebGL的API非常繁琐,比如创建一个最常见物体在 WebGL 中需要这样写,其中反复调用 bindBuffer 和 bufferData,很容易写错:
const positions = [1,1,-1,1,1,1,1,-1,1,1,-1,-1,-1,1,1,-1,1,-1,-1,-1,-1,-1,-1,1,-1,1,1,1,1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,-1,-1,1,-1,1,-1,-1,1,1,1,1,-1,1,1,-1,-1,1,1,-1,1,-1,1,-1,1,1,-1,1,-1,-1,-1,-1,-1]; const normals = [1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1]; const texcoords = [1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1]; const indices = [0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23]; const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); const normalBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW); const texcoordBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texcoords), gl.STATIC_DRAW); const indicesBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
twgl.js
twgl.js 就是简化webGL调用最典型的做法 如上述代码用twgl:
const arrays = {
position: [1,1,-1,1,1,1,1,-1,1,1,-1,-1,-1,1,1,-1,1,-1,-1,-1,-1,-1,-1,1,-1,1,1,1,1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,-1,-1,1,-1,1,-1,-1,1,1,1,1,-1,1,1,-1,-1,1,1,-1,1,-1,1,-1,1,1,-1,1,-1,-1,-1,-1,-1],
normal: [1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1],
texcoord: [1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1],
indices: [0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23],
};
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
twgl 的定位只是减少重复代码,并没有进一步抽象,所以使用它和直接用 WebGL 在学习成本上没太大区别,因此非常适合初学者,但也意味着它没什么独特的功能。
regl
和 twgl 单纯简化代码相比,regl 提供了跟高层的抽象,将原本的过程式转成了函数式,使得看起来更符合直觉,比如下面这个入门三角形比原生 WebGL 要少很多代码。
const drawTriangle = regl({
frag: `void main() { gl_FragColor = vec4(1, 0, 0, 1); }`,
vert: `attribute vec2 position; void main() { gl_Position = vec4(position, 0, 1); }`,
attributes: {
position: [[0, -1], [-1, 0], [1, 1]]
},
count: 3
})
regl 的原理是动态生成 WebGL 相关的 JavaScript 代码然后执行,所以它比 twgl 能提供更加简化的代码,也能更灵活地设计对外 API,减少 WebGL 本身过程式带来的限制,功能也更多,同时文档详尽,单元测试覆盖率95%,还有工具来追踪性能变化,可以很放心地使用,但由于做了一层封装,导致使用它和原生 WebGL 写法差异较大
除了twgl、regl 还有ogl、luma.gl等其他库以及不仅仅算对webgl的封装,也会提供场景树、相机等概念。
渲染引擎
由于 WebGL 本身只是光栅引擎,基于它开发需要了解矩阵变换并编写着色器,所以 WebGL 学习门槛很高,光入门就要看特别长的文档,对于大部分应用而言,比起前面的 WebGL 封装,最好还是选择渲染引擎,因为大部分渲染引擎也提供了自定义 Shader 功能,也提供了 GPU 实例等功能,只是一般不能改渲染管线。
Filament
Filament 是 Google 基于 C++ 开发的跨平台物理渲染引擎,支持 Android、iOS、Windows、Mac 等系统,还提供了基于 WebAssembly 的 Web 版本,它用在了 Google 地图和搜索这两个核心 APP 中。
尽管只是顺带支持 Web,但完善的实现使得它的渲染效果很突出,在渲染管线上使用了较为新颖的 Clustered forward renderer,因此能支持大量光源。
缺点:
1、核心是 C++ 写的,如果功能不满足只能去改 C++,而这几乎是必须的,因为暴露给 JavaScript 的 API 很少,最简单的点击交互都不支持,各个组件的设置也很少,比如相机要实现无交互时自动旋转就得改 C++ 代码,不熟悉 C++ 将寸步难行。
2、只支持 WebGL 2.0,这点比较致命,导致无法在 iOS 的浏览器上使用,看了一下它有调用 glDrawBuffers,但不清楚用于做什么。
Claygl
Claygl 是 ECharts 核心开发者 pissang 大神的开发的 WebGL 游戏引擎,它还用在了 ECharts-gl 项目中,使得 ECharts 在三维图表方面远超所有竞品,最大的亮点是支持延迟着色,目前除了前面提到的 Filament 和后面的 LayaAir,其它 WebGL 引擎都是传统的前向着色,这种管线在渲染时,会对每个物体计算所有光照的贡献,类似如下的写法:
for node of nodes:
for light of lights:
output += bdrf(node, light)
这种实现的复杂度随着光源和场景中对象的增加而增加,导致光源越多性能越差,这也是为什么在 Web 上看到的三维效果大部分都只使用一个主光源,类似下面这种效果,只有一个太阳光:

如果想实现接近现实世界的效果就必须支持多光源,尤其是室内和夜晚,比如类似下面这种,使用前向渲染性能太差,无法做到实时渲染。

要解决这个问题只能使用延迟着色或者分簇(Clustered)前向着色,目前主流的桌面游戏引擎都是使用延迟着色,并配合前向着色来支持透明物,ClayGL 也是目前唯一实现这种管线的开源渲染引擎。
游戏引擎
Unreal Engine
大名鼎鼎的虚幻引擎,可以使用 Emscripten 编译出 WebAssembly 版本的项目,直接运行在浏览器中。但是引擎+数据本身体积过大,放在web上运行太过臃肿,所以从 4.24 版本开始不默认提供这个功能,基本放弃HTML5版本
three.js
Three.js 是最知名的 WebGL 项目,Contributions 人数高达 1313,和 React 是一个量级的,尽管它自身的定位只是渲染引擎,但社区硬是把不少游戏引擎的功能都加上了,比如物理引擎、贴花、动画等,在源码中有大量例子,很适合学习。
Babylon
同时最后的一个亮点在于支持WebGPU(WebGL升级版)
四、总结
调研了些WebGL的内容,发现WebGL学习成本很高,同时结合公司的项目,针对数据可视化框架来说,图形渲染只是其中的一部分,后续会分享各类数据可视化框架以及结合公司的项目痛点给出自己的解决方案

浙公网安备 33010602011771号