一、WebGL 是什么 WebGL(Web 图形库)是一个 JavaScript API,可在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形,而无需使用插件。WebGL 通过引入一个与 OpenGL ES 2.0 非常一致的 API 来做到这一点,该 API 可以在 HTML5 <canvas>
元素中使用。这种一致性使 API 可以利用用户设备提供的硬件图形加速。[摘自 MDN]
本质:一个用来创建 2D/3D 图形网页的 JavaScript API
二、WebGL 能做什么 2D 框架/引擎 PIXI.js demo:https:///gallery/ Pixi.js 使用 WebGL,是一个超快的 HTML5 2D 渲染引擎。作为一个 Javascript 的 2D 渲染器,Pixi.js 的目标是提供一个快速的、轻量级而且是兼任所有设备的 2D 库。Pixi 渲染器可以开发者享受到硬件加速,但并不需要了解 WebGL。
Phaser.js demo:https:///examples Phaser 是一个开源的桌面和移动 HTML5 2D 游戏开发框架,支持 JavaScript 和 TypeScript。高性能: 快速、免费、易于维护。一方面,开发者可以直接通过 Koding 平台上的 VM 开发系统进行代码编写及预览。另一方面,也可以在支持 Canvas 的浏览器中直接安装 Phaser 来进行游戏开发。
two.js demo:https://two./examples/ two.js 是一个二维的绘图 API,用于较新的 Web 浏览器,可基于不同上下文绘制,包括 svg、canvas 和 webgl。
3D 框架/引擎 Three.js demo:https://three/examples/#webgl_animation_keyframes 当下最流行的 WebGL 库, 轻量级,容易使用,很多 webgl 库都是基于它来构建。
Babylon.js demo:https://www./community/ Babylon.js 是一个使用 HTML5 和 WebGL 构建 3D 游戏的 JavaScript 框架。
Cesium.js demo:https://sandcastle./ Cesium 是一款开源的基于 JavaScript 的 3D 地图框架。Cesium 能够跨平台、跨浏览器支持绝大多数的浏览器和移动端浏览器;使用 WebGL 进行 3D 图形展示。可应用于三维数字地球, 数据可视化, 创建虚拟场景等功能。
三、大场景案例分析 场景分类 Gis 主场景https://sandcastle./?src=3D%20Tiles%20Photogrammetry%20Classification.html 3D 室内漫游场景https://dashboard./scene/!7defaa68-4293-446c-b55a-d731d3506047/viewer 2D 室内编辑场景https://dashboard./scene/!7defaa68-4293-446c-b55a-d731d3506047/viewer 各场景痛点 Gis 主场景
Gis 数据老旧与当前场景现实情况不一致,需要引入当前场景的最新数据,数据量大。 中心场景效果要求较高(光照,阴影,反射等),对中心模型的处理很多。 主场景的需要还原真实环境(实时天气,整个中心场景的统筹看板),对接数据过大。 3D 室内漫游场景
1 比 1 精确还原场景,对数据精确度要求很高,同时还需要附加一些效果。 2D 室内编辑场景
💡 WebGL 基本绘图元素只有点、线、三角形,如下图中的这种复杂图形,它也是由三角形构成。 编辑场景中拆分、新增、测量、合并、辅助线等操作很多,吸附、碰撞检测等计算量很大。 场景中构件种类很多,不同种类的渲染呈现方式也不同,支持的操作功能亦不相同。 性能问题总结 四、解决方案 1. 自定义 GeoJSON 数据结构及大数据压缩方案 GeoJSON 是一种开放标准的地理空间数据交换格式,可表示简单的地理要素及其非空间属性。GeoJSON 以 JavaScript 对象表示法 (JSON) 为基础,是对各种地理数据结构进行编码时所采用的格式。该格式使用地理坐标参考系(世界大地测量系统 1984),并且以十进制度作为单位。
{ 'type' : 'FeatureCollection' , 'features' : [ { 'type' : 'Feature' , 'geometry' : { 'type' : 'Point' , // 点 'coordinates' : [102.0, 0.5] }, 'properties' : { 'prop0' : 'value0' }, }, { 'type' : 'Feature' , 'geometry' : { 'type' : 'LineString' , // 线段 'coordinates' : [ [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0] ] }, 'properties' : { 'prop0' : 'value0' , 'prop1' : 0.0 }, }, { 'type' : 'Feature' , 'geometry' : { 'type' : 'Polygon' , // 面 'coordinates' : [ [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] ] }, 'properties' : { 'prop0' : 'value0' , 'prop1' : { 'this' : 'that' } }, }, ... ] }
由于每个场景中需要显示的元素很多。导致每个楼层的 GeoJSON 的数据过大(大约每层 4MB),这样通过 http 传输就会很慢,再加上还要请求其他的挂接数据,导致页面在渲染前耗费的时间就占用了页面所有时间的一半。
思路 从 GZip 来的灵感 => Deflate 算法 => LZ77 + Huffman。
LZ77 与LZ78 是亚伯拉罕·蓝波与杰可布·立夫在 1977 年以及 1978 年发表的论文中的两个无损数据压缩算法。这两个算法是大多数 LZ 算法变体,如 LZW、LZSS 以及其它一些压缩算法的基础。与最小冗余编码器或者行程长度编码器不同,这两个都是基于字典的编码器。LZ77 是“滑动窗”压缩算法,这个算法后来被证明等同于 LZ78 中首次出现的显式字典编码技术。
下面我们来举一个例子。
有一份数据文件的内容如下https://www. https://sso.
其中有些部分的内容,前面已经出现过了,下面用()括起来的部分就是相同的部分。https://www. (https://)sso(.)
我们使用 (两者之间的距离,相同内容的长度) 这样一对信息,来替换后一块内容。https://www. (26,8)sso(26,14)
(26,8) 中,26 为相同内容块与当前位置之间的距离,8 为相同内容的长度。 (23,4) 中,26 为相同内容块与当前位置之间的距离,14 为相同内容的长度。
由于(两者之间的距离,相同内容的长度)这一对信息的大小,小于被替换内容的大小,所以文件得到了压缩。
霍夫曼编码 (英语:Huffman Coding),又译为哈夫曼编码 、赫夫曼编码 ,是一种用于无损数据压缩的熵编码(权编码)算法。由美国电脑科学家大卫·霍夫曼(David Albert Huffman)在 1952 年发明。[摘自维基百科]
字符 空格 a e f h i m n s t l o p r u x 频率 7 4 4 3 2 2 2 2 2 2 1 1 1 1 1 1 编码 111 010 000 1101 1010 1000 0111 0010 1011 0110 11001 00110 10011 11000 00111 10010
我们可以看到,Huffman 树的建立方法就保证了,出现次数多的符号,得到的 Huffman 编码位数少,出现次数少的符号,得到的 Huffman 编码位数多。读文件,统计每个符号的出现次数。根据每个符号的出现次数,建立 Huffman 树,得到每个符号的 Huffman 编码。将每个符号的出现次数的信息保存在压缩文件中,将文件中的每个符号替换成它的 Huffman 编码,并输出。
优化前后对比 优化前:约 4MB 优化后:约 800KB
2. 片元处理及计算过多 片元处理优化(减少需要处理的片元数目) 控制绘制顺序 为了最大限度地避免 overdraw,一个重要的优化策略就是控制绘制顺序。由于深度测试的存在,如果我们可以保证物体都是从前往后绘制的,那么就可以很大程度上减少 overdraw。这是因为,在后面绘制的物体由于无法通过深度测试,因此,就不会再进行后面的渲染处理。 时刻警惕透明物体 由于透明度物体处理时,会导致一些硬件优化策略失效(比如说遮挡剔除,把看不到物体的顶点剔除)。对于透明度混合技术,需要关闭深度写入,所以要保持正确的渲染顺序。透明度测试没有关闭深度测试,但由于它的实现使用了 discard 或 clip 操作。也就是说,只要在执行了所有的片元着色器后,GPU 才知道哪些片元会被真正渲染到屏幕上,这样,原先那些可以减少 overdraw 的优化就都无效了。 处理方式:控制绘制顺序,同时先对不透明物体从前往后排序后绘制,再绘制透明物体,进行混合。
减少片元计算 减少实时光照和阴影,可以使用光照贴图代替实时计算。
3. draw call 次数过多 使用批处理 简单理解就是将使用同一个材质的物体一起处理 们之间的不同就是顶点数据的差别。如果使用不同材质,但要使用批处理,也可以将这些纹理合并到同一张大纹理称为图集,再使用不同的采样坐标对纹理采样即可。如果需要微小的不同,使用顶点颜色数据来储存。批处理是 OpenGL 的概念,WebGL 有实例绘制,告诉 GPU 使用共享模型来绘制每一个实例 ,也是一次绘制多个物体。
在 WebGL2.0 绘制方法是:
gl.drawArraysInstanced(mode,first,count,instanceCount); gl.drawElementsInstanced(mode, count, type , offset, instanceCount);
下面这个方法调整实例位置:
gl.vertexAttribDivisor(index,divisor);
WebGL1.0 可以使用扩展:
var ext = gl.getExtension('ANGLE_instanced_arrays' ); ext.drawArraysInstancedANGLE(); ext.drawElementsInstancedANGLE();
在 ThreeJS 中使用 InstanceMesh 或 InstanceGeometry。
Mesh 合并 Mesh 网格 ,Mesh 是指模型的网格,3D 模型是由多边形拼接而成,而多边形实际上是由多个三角形拼接而成的。所以一个 3D 模型的表面是由多个彼此相连的三角面构成。三维空间中,构成这些三角面的点以及三角形的边的集合就是 Mesh。
为了尽量降低 drawcall,即调用 mesh 绘制的次数。因为一次性处理一个大 mesh 比多次绘制小 mesh 要快得多。从而达到性能优化
在 ThreeJS 中使用 THREE.Geometry 对象的上面的 merge 方法。
举个 🌰:当我们使用普通组的情况,绘制 20000 个立方体,帧率在 15 帧左右,如果我们选择合并以后,再绘制两万,就会发现,我们可以轻松的渲染 20000 个立方体,而且没有性能的损失。合并的代码如下:
var geometry = new THREE.Geometry();for (var i = 0; i < 20000; i++) { var cube = addCube(); // 新调用立方体方法 cube.updateMatrix(); geometry.merge(cube.geometry, cube.matrix); // 合并mesh } scene.add(new THREE.Mesh(geometry, cubeMaterial));
和组的优缺点对比优点:性能不会有损失。因为将所有的的网格合并成为了一个,性能将大大的增加。
缺点:组能够对每个单独的个体进行操作,而合并网格后则失去对每个对象的单独控制。想要移动、旋转或缩放某个方块是不可能的。
4. 场景复杂三角面过多 优化几何体 尽可能减少模型中三角形面片的数目,需要美工人员的帮助。
这种将顶点一分为多的原因主要有两个:一个是为了分离纹理坐标(uv splits),另一个是为了产生平滑的边界(smoothing splits)。它们的本质,其实都是因为对于 GPU 来说,顶点的每一个属性和顶点之间必须是一对一的关系。而分离纹理坐标,是因为建模时一个顶点的纹理坐标有多个。例如,对于一个立方体,它的 6 个面之间虽然使用了一些相同的顶点,但在不同面上,同一个顶点的纹理坐标可能并不相同。对于 GPU 来说,这是不可理解的,因此,它必须把这个顶点拆分成多个具有不同纹理坐标的顶点。而平滑边界也是类似的,不同的是,此时一个顶点可能会对应多个法线信息或切线信息。这通常是因为我们要决定一个边是一条硬边(hard edge)还是一条平滑边。
模型的 LOD 技术(多细节层次) ThreeJS 实现了 LOD 模型。这种技术的原理是,当一个物体离摄像机很远时,模型上的很多细节是无法被察觉到的。因此,LOD 允许当对象逐渐远离摄像机时,减少模型上的面片数量,从而提高性能。
面剔除技术 OpenGL 允许检查所有正面朝向(Front facing)观察者的面,并渲染它们,而丢弃所有背面朝向(Back facing)的面,这样就节约了我们很多片段着色器的命令(它们很昂贵!)。我们必须告诉 OpenGL 我们使用的哪个面是正面,哪个面是反面。OpenGL 使用一种聪明的手段解决这个问题——分析顶点数据的连接顺序(Winding order)。
默认情况下,逆时针的顶点连接顺序被定义为三角形的正面。WebGL 也是这样,具体设置代码为:
gl.enable(gl.CULL_FACE); gl.cullFace(gl.FRONT_AND_BACK);
5. 节点吸附、标签避障计算影响性能 利用 Web Workers 进行复杂计算 Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。此外,他们可以使用XMLHttpRequest
执行 I/O (尽管responseXML
和channel
属性总是为空)。一旦创建, 一个 worker 可以将消息发送到创建它的 JavaScript 代码,通过将消息发布到该代码指定的事件处理程序(反之亦然)。[摘自 MDN]
// filter-sprites-worker.js onmessage = function (e) { console.log('Message received from main script' ); var workerResult = handleFilterSprites(e.data); // 处理避障结果函数 console.log('Posting message back to main script' ); postMessage(workerResult); }
class view3D { constructor () { this.initView(); this.initEvents(); this.initWorkers(); } // 初始化视图 initView () { // ... } // 初始化事件 initEvents () { // ... } // 相机改变 cameraChange () { this.autoFiterSprites(); } // 初始化worker initWorkers () { this.calcWorker = new Worker('filter-sprites-worker.js' ); this.calcWorker.onmessage = this.onmessage; } // 处理worker结果回调 onmessage(e) { console.log(e, 'Message received from worker' ); } // 进行标签自动避障计算 autoFiterSprites () { // 标签组及相机参数传入worker计算 this.calcWorker.postMessage([spritesGruops, this.camera]); } // 场景销毁函数 disposed () { this.removeEvents(); this.calcWorker.terminate(); // ... } }
优缺点对比优点:主线程运行的同时 worker 线程也在运行,相互不干扰,在 worker 线程运行结束后把结果返回给主线程。这样做的好处是主线程可以把计算密集型或高延迟的任务交给 worker 线程执行,这样主线程就会变得轻松,不会被阻塞或拖慢。
缺点:worker 一旦新建,就会一直运行,不会被主线程的活动打断,这样有利于随时响应主线程的通性,但是也会造成资源的浪费,所以不应过度使用,用完注意关闭。或者说:如果 worker 无实例引用,该 worker 空闲后立即会被关闭;如果 worker 实列引用不为 0,该 worker 空闲也不会被关闭。
五、优化前后对比 优化前 优化后 数据大小 约 4MB 约 800KB 页面加载时间 8s 3s 用户体验 节点吸附操作会有明显卡顿 页面操作流畅,用户体验提升
六、WebGL 总结 WebGL 给 Web 图形开发者打开了一扇新的大门,浏览器可以不借助插件做炫酷三维效果 。 WebGL 对前端开发来说入门比较简单,丰富的框架和引擎便于上手和搭建需求效果 。 WebGL 进阶和深入还有很长的路要走,实时渲染技术与图形学知识、计算几何知识都需要有涉猎 。 目前市场上的 WebGL 开发现状:
企业:WebGL 开发很难招,尤其是一个专家级别的,基本都靠相互内推、挖墙脚。
个人:WebGL 企业很少,基本就那几家,近几年相关企业明显增多。
但是 WebGL 开发的工资 是真的高 :
七、WebGPU WebGPU 是什么 WebGPU 是一套基于浏览器的图形 API,浏览器封装了现代图形 API(Dx12、Vulkan、Metal),提供给 Web 3D 程序员,为 Web 释放了更多的 GPU 硬件的功能。WebGPU 不是 WebGL 的延续,也不对标 OpenGL,而是下一代全新的基于 Web 的图形 API 。
WebGPU 的优点 WebGPU 尝鲜 目前 WebGPU 虽然还未正式发布,但已经比较成熟了,也有相关的 Demo 可供学习。
对于 Chrome: 下载 Chrome Canary:https://www.google.com/intl/zh-CN/chrome/canary/ 打开chrome://flags/#enable-unsafe-webgpu
对于 Safari: 下载 Safari Technology Preview:https://developer.apple.com/safari/technology-preview/ 选中 Safari → Preferences → Advanced → Develop menu→ Experimental Features → WebGPU
WebGPU 的未来 提到 WebGPU 的未来,更应该考虑的是 Web 生态和 Native 生态的问题。尤其是面向未来,面向元宇宙时代。我们到底应该怎么选择我们 3D 场景,是通过浏览器 Web 来实现?还是选择桌面端 Native 来实现呢?
我想不同的人,不同的企业,都会有不同的答案。我个人是非常看好 WebGPU 的发展前景,但是前景好不等于市场就一定会选择,非常受制于现有生态。