WebGL-WebGL2迁移实践
2020/6/18 11:26:31
本文主要是介绍WebGL-WebGL2迁移实践,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
WebGL2是2017年推出的Web 3D图形API,是对WebGL1的一次重大升级。在近几年的发展中,各家硬件设备及浏览器也逐渐完善了对WebGL2的支持。最近JSAPI在尝试实现一些新功能时发现需要使用到WebGL2的新特性,于是做了下迁移到WebGL2的调研和尝试,原来还挺简单的,快来一起迁移起来吧~
WebGL2
首先需要了解下WebGL2相对于第一代有什么变化,能给项目带来什么收益。
WebGL2的发展
盗图一张来说明下WebGL的发展过程:
WebGL2是基于OpenGL ES 3.0实现的Web API,核心是WebGL2RenderingContext
接口,是对WebGL1上下文对象的一次扩展,在100%向后兼容的基础上还提供了非常多的新特性。
目前各家浏览器的支持情况如下:
可以看到除了Safari(包括iOS Safari)、QQ浏览器、IE之外,其他浏览器基本都能支持了。在不能支持的浏览器上也可以做降级处理,采用WebGL1。
注: 除了WebGL之外,还有更加全新的Web3D图形API,那就是WebGPU,是浏览器基于现代图形API(Dx12、Vulkan、Metal)封装的应用接口,据说能实现更低的驱动开销,提供更好的计算性能。不过目前看来还没有得到广泛支持,而且与WebGL差别很大,不易兼容,所以暂不考虑WebGPU方案,感兴趣的同学可以关注下最新进展。
WebGL2的优势
1)无需扩展就能使用的重要特性
在使用WebGL1的过程中我们常用到一些扩展来支持一些高端功能,比如利用WEBGL_depth_texture
深度纹理可以用来实现基于深度的雾化效果、阴影贴图等。
扩展应用起来比较麻烦,不仅要考虑各浏览器的兼容性,比如:
const ext = gl.getExtension('WEBGL_depth_texture') || gl.getExtension('MOZ_WEBGL_depth_texture') || gl.getExtension('WEBKIT_WEBGL_depth_texture'); 复制代码
而且提供的扩展方法也是在扩展对象上,方法名还有后缀,比如:
const ext = gl.getExtension('ANGLE_instanced_arrays'); ext.vertexAttribDivisorANGLE(location, 0); 复制代码
不过很多扩展都已经成为了WebGL2的标准内容了,也就可以在上下文对象上直接调用原本扩展才能提供的方法了,而且有更多的参数支持。以下是JSAPI中有使用到或者计划使用的扩展功能:
功能 | WebGL1扩展 | WebGL2支持情况 |
---|---|---|
深度纹理 | WEBGL_depth_texture | 支持 |
实例化渲染 | ANGLE_instanced_arrays | 支持 |
顶点数组对象 | OES_vertex_array_object | 支持 |
Int32类型索引 | OES_element_index_uint | 支持 |
显卡信息 | WEBGL_debug_renderer_info | 不支持 |
上下文丢失/恢复 | WEBGL_lose_context | 不支持 |
经整理WebGL2已纳入标准的扩展有:
2)多绘制缓冲
多绘制缓冲,也叫做MRT(Multiple Render Targets),是促使我们迁移到WebGL2的动力之一。它可以支持在一个帧缓冲区上绑定多个颜色缓冲区,一次绘制可以在多个缓冲区中写入数据,可用于实现延迟渲染和泛光效果。运用这项技术后,光照及阴影的计算只需要在每个片元上执行一次,而且可以尽量和几何渲染过程解耦。不过也会带来一些问题,性能提升程度也待验证。
注: WebGL也可以使用WEBGL_draw_buffers
来实现多个颜色缓冲区的绑定。
3)纹理功能增强
WebGL2支持了更多的纹理格式和压缩格式,太多了就不一一列举了。可以做什么呢?我目前能想到的一个是浮点帧缓冲,在颜色缓冲区中保存超过1.0的数值,以应用HDR技术调整光照效果。另一个是可以利用压缩纹理优化纹理缓存空间和上传耗时。
如果使用JPG或PNG图片格式,使用texImage2D
上传图片数据到GPU时会进行解压,转为位图。位图格式占用空间较大,传输时间较长。而如果使用压缩格式,则可以不进行解压,同时节省空间和时间。WebGL1中通过扩展可以使用压缩纹理格式,但很多是要区分硬件条件的,S3TC基本上只是PC支持,PVTC只有iOS,而WebGL2中多种压缩格式都可以在任何环境下得到支持。
另外WebGL2还支持了纹理数组,多个相同大小的纹理切片可以共享一个纹理单元。纹理的访问也可以支持直接选取纹素。如何应用还待慢慢发掘。
4)Uniform缓冲对象
Uniform Buffer Object可以让我们像attribute变量一样,把uniform变量写入缓冲区,多次使用。一方面在uniform变量较多时,使用gl.bufferData
和gl.bindBufferRange
两次调用替代N次gl.uniformXXX
,节省上传时间。另一方面,当多个着色器共用一些uniform变量时,可以一次写入,多处绑定即可。这种情况非常常见,比如matrix、center、zoom。
WebGL2的缺陷
- 浏览器兼容性:如上文所述,Safari、QQ浏览器均不支持WebGL2,且还是主流浏览器,所以必须做好二手准备,降级为WebGL1
- 可能会被下一代WebGPU替代:到时候再换不迟,毕竟看过下文后你会发现切换到WebGL2真的没什么成本
WebGL1迁移WebGL2
迁移本身成本很小,只是因为要做WebGL1兼容所以有些特殊处理,一步步来吧:
上下文兼容处理:优先获取webgl2上下文
这一步很简单:
// 优先使用WebGL2,若不支持则降级为WebGL(Safari/QQ浏览器) let gl = canvas.getContext('webgl2', options); if (gl) { gl.isWebGL2 = true; } else { gl = canvas.getContext('webgl', options); gl.isWebGL2 = false; } 复制代码
扩展兼容处理:将扩展方法复制到上下文对象
对于一些WebGL2已经支持的扩展内容,在调用时做两种上下文的区分非常麻烦:
if (gl.isWebGL2) { gl.vertexAttribDivisor(location, 0); } else { const ext = gl.getExtension('ANGLE_instanced_arrays'); ext.vertexAttribDivisorANGLE(location, 0); } 复制代码
如果把扩展的方法直接赋加到上下文对象上,且与WebGL2使用相同的名字,那就可以使用同一套代码了。刚好扩展的属性及参数名都是有规律可循的,与WebGL2提供的方法相比都是多了一个后缀,具体改造方法如下,搬移至迁移WebGL1到WebGL2:
function getAndApplyExtension(gl, name) { const ext = gl.getExtension(name); if (!ext) { return null; } const fnSuffix = name.split("_")[0]; const enumSuffix = '_' + fnSuffix; for (const key in ext) { const value = ext[key]; const isFunc = typeof (value) === 'function'; const suffix = isFunc ? fnSuffix : enumSuffix; let name = key; // WEBGL_compressed_texture_s3tc // 和WEBGL_compressed_texture_pvrtc不是true if (key.endsWith(suffix)) { name = key.substring(0, key.length - suffix.length); } if (gl[name] !== undefined) { if (!isFunc && gl[name] !== value) { console.warn("conflict:", name, gl[name], value, key); } } else { if (isFunc) { gl[name] = function(origFn) { return function() { return origFn.apply(ext, arguments); }; }(value); } else { gl[name] = value; } } } return ext; } if (!gl.isWebGL2) { getAndApplyExtension(gl, 'ANGLE_instanced_arrays'); getAndApplyExtension(gl, 'WEBGL_depth_texture'); getAndApplyExtension(gl, 'OES_vertex_array_object'); } 复制代码
处理完之后要记得将以前调用扩展方法的代码都更新一下。
纹理操作适配
以下是我自己踩过的坑,主要涉及到纹理的操作,请大家一定注意:
1)texImage2D的语法
texImage2D
在WebGL2中的使用语法不同,必须传入width
、height
、border
:
// WebGL1: void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView? pixels); void gl.texImage2D(target, level, internalformat, format, type, ImageData? pixels); void gl.texImage2D(target, level, internalformat, format, type, HTMLImageElement? pixels); void gl.texImage2D(target, level, internalformat, format, type, HTMLCanvasElement? pixels); void gl.texImage2D(target, level, internalformat, format, type, HTMLVideoElement? pixels); void gl.texImage2D(target, level, internalformat, format, type, ImageBitmap? pixels); // WebGL2: void gl.texImage2D(target, level, internalformat, width, height, border, format, type, GLintptr offset); void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLCanvasElement source); void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLImageElement source); void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLVideoElement source); void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ImageBitmap source); void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ImageData source); void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView srcData, srcOffset); 复制代码
2)深度纹理格式
如果你也使用了深度纹理,那么在调用texImage2D
时还需要注意,在WebGL2中internalformat
不可使用gl.DEPTH_COMPONENT
(虽然有这个属性),在WebLG1中不可使用gl.DEPTH_COMPONENT16
(虽然有这个属性),而且这些属性还是只读属性,所以我做了这样的兼容处理,这样上传时就能统一使用gl.DEPTH_COMPONENT24
,暂时还没发现问题:
if (!gl.isWebGL2) { gl.DEPTH_COMPONENT24 = gl.DEPTH_COMPONENT; } 复制代码
另外需要注意的是,在WebGL1internalformat
和format
都是相同的,但是在WebGL2中是有对应关系的,比如这里不管internalformat
是gl.DEPTH_COMPONENT16
还是gl.DEPTH_COMPONENT24
、gl.DEPTH_COMPONENT32F
,对应的format
都是gl.DEPTH_COMPONENT
。
3)深度纹理过滤
这真的是这次迁移过程中遇到的最大的坑。当我切换到WebGL2之后,从深度纹理中读取到的数据都是0,其他纹理没问题,深度测试也没问题,最后定位到问题出在纹理过滤的设置上。
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, options.minFilter || gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, options.magFilter || gl.LINEAR); 复制代码
如上,之前都是采用线性过滤,WebGL1没有问题,但在WebGL2里就不好使了(也不知道为啥,求指教)。在mac上还好,还有个警告,windows上连警告都没有🙄️️。这里需要设置为gl.NEAREST
才能正常使用。
着色器无需升级
总的来说,迁移成本还是相当低的,本来以为需要将着色器代码按GLSL 3.0的语法重写一遍,实际上是可以兼容旧着色器代码的,而如果要使用新语法,则只需要在着色器第一行注明:
#version 300 es 复制代码
需要注意不能有空格或者空行。GLSL 3.0具体有哪些改变也可以参考迁移WebGL1到WebGL2。
这篇关于WebGL-WebGL2迁移实践的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-01后台管理开发学习:新手入门指南
- 2024-11-01后台管理系统开发学习:新手入门教程
- 2024-11-01后台开发学习:从入门到实践的简单教程
- 2024-11-01后台综合解决方案学习:从入门到初级实战教程
- 2024-11-01接口模块封装学习入门教程
- 2024-11-01请求动作封装学习:新手入门教程
- 2024-11-01登录鉴权入门:新手必读指南
- 2024-11-01动态面包屑入门:轻松掌握导航设计技巧
- 2024-11-01动态权限入门:新手必读指南
- 2024-11-01动态主题处理入门:新手必读指南