WEBGL的随笔 | 青训营笔记

195 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 18 天

WebGL—— 一个技术定义

WebGL入门指南

WebGL 标准是由科纳斯组织(Khronos Group)开发和维护的,该组织还是管理着 OpenGL、COLLADA等其他你也许早有耳闻的规范的标准组织。以下是科纳斯组织的网站上对WebGL的官方描述:

WebGL是免授权费的,跨平台的应用程序接口API,它将OpenGL ES 2.0作为在HTML网页内的3D绘图环境,作为低级别文档对象模型接口开放。它使用OpenGL渲染语言GLSL ES,并可被整洁地与其他3D内容上层或下层的网页内容捆绑。它是使用JavaScript编程开发语言开发适合动态3D网页应用的理想工具,并已被主流互联网浏览器集成。

WebGL是一套API

WebGL是一套JavaScript编程接口,并且JavaScript是WebGL的唯一入口,所以也并不需要任何特殊的HTML标签。WebGL中的3D渲染与使用Canvas元素的2D绘画类似,所有的功能都通过JavaScript API调用。事实上,要想调用WebGL接口,只需要使用已有的Canvas元素,并设置一个特殊的绘制上下文即可。
WebGL是基于OpenGL ES 2.0的
OpenGL ES是历史悠久的3D渲染标准OpenGL的精简版本。“ES”是指“Embedded Systems”,即“嵌入式系统”,也就是说,它是为小型的计算机设备量身定制的,其中最典型的就是智能手机和平板电脑。目前iPhone、iPad、Android手机和平板电脑都使用了OpenGL ES来进行3D渲染。WebGL标准的设计者认为,沿着OpenGL ES的足迹前行,打造一个协调一致、跨平台、跨浏览器的3D Web API会更加切实可行。
WebGL和其他网络内容可以整合在一起
WebGL内容可以放置于其他页面内容之上或者之下。3D(canvas)画布元素可以作为页面的一部分,也可以充满整个页面。

用 WebGL 实现并行计算的原理

在现代化的图形 API(Vulkan/Metal/Direct3D)中提供了 Compute Shader 供开发者编写计算逻辑。考虑到 WebGPU 仍在开发中,目前在 Web 端能使用的图形渲染 API 只有 WebGL1/2,它们都不支持 Compute Shader(WebGL 2.0 Compute 已废弃),因此只能“曲线救国”。在本文的最后一节我们将展望未来的技术手段。

我们先忽略具体的 API 用法,从 CPU 和 GPU 的角度看两者在并行计算过程中是如何协作的,前者也常被称作 host,后者为 device。第一步为数据初始化,需要从 CPU 内存中拷贝数据到 GPU 内存中,在 WebGL 中会通过纹理绑定完成。第二步 CPU 需要准备提交给 GPU 的指令和数据,完成计算程序的编译,在 WebGL 中通过调用一系列 API 实现。在第三步中将计算逻辑分配给 GPU 各个核心执行,因此这段逻辑也叫做“核函数”。最后把计算结果从 GPU 内存中拷贝回 CPU 内存,在 WebGL1 中通过读取纹理中像素值完成。

下面我们从 GPU 编程模型和执行模型入手,顺便引出线程和线程组的概念,这也是 GPU 可数据并行的关键。下图展示了网格与线程组的层次关系,并不局限于 DirectCompute。

  • 通过 dispatch(x, y, z) 分配一个 3 维的线程网格(Grid),其中的线程共享全局内存空间;
  • 网格中包含了许多线程组(Work Group、Thread Group、Thread Block、本地工作组不同叫法),每一个线程组中又包含了许多线程,线程组也是 3 维的,一般在 Shader 中通过 numthreads(x, y, z) 指定。它们可以通过共享内存或同步原语进行通信;
  • Shader 程序最终会运行在每一个线程上。对于每一个线程,可以获取自己在线程组中的 3 维坐标,也可以获取线程组在整个线程网格中的 3 维坐标,以此映射到不同的数据上,实现数据并行的效果;

1676605360745.png

多采样渲染缓冲对象

在WebGL2中,有了一个新的特性,叫做Multisampled Renderbuffer,中文叫做:多采样渲染缓冲对象;通过多采样渲染缓冲对象,可以在帧缓冲区的渲染缓冲对象上实现MSAA(multisampled antialiasing), 然后通过下面的流程实现最终实现渲染的去锯齿:

pre-z pass –> rendering pass to FBO –> postprocessing pass 
–> render to window

函数renderbufferStorageMultisample

和多采样渲染缓冲对象相关的一个重要的函数就是gl.renderbufferStorageMultisample,下面是函数的签名:

gl.renderbufferStorageMultisample(target, samples, internalFormat, width, height);

该函数的第一个target是渲染缓冲对象的“目标”,samples表示采样数,internalFormat表示数据格式,width、height表示渲染缓冲对象的宽高。

下面是使用该函数的简单代码片段:

var frameBuffer = gl.createFrameBuffer();
var colorRenderbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, colorRenderbuffer);
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, FRAMEBUFFER_SIZE.x, FRAMEBUFFER_SIZE.y);
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorRenderbuffer);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);

这和webgl1 中创建帧缓冲区的代码类似,并没有太大差别,不同的是如下一行:

gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, FRAMEBUFFER_SIZE.x, FRAMEBUFFER_SIZE.y);

通过gl.renderbufferStorageMultisample方法指定了渲染缓冲对象的多重采样,采样数是4。

多采样纹理附件

多采样纹理附件又是什么东西呢,好吧,其实在WebGL2中,没有这个多采样纹理附件,在OPENGL才有,为什么提到这个多采样纹理附件,大部分时间,我们的离屏渲染都需要渲染到一个纹理对象上面,才能进一步使用。

在没有多采样纹理附件,只有多采样渲染缓冲对象的情况下,要实现MSAA,只能渲染到渲染缓冲对象上,但是渲染缓冲对象的内容不能直接传递给纹理对象。

那么应该怎么做呢?需要使用另外一个重要的函数:

gl.blitFramebuffer函数

通过gl.blitFramebuffer函数,可以把多采样渲染缓冲对象的内容传递给纹理对象。下面是该函数的签名:

gl.blitFramebuffer(srcX0, srcY0, srcX1, srcY1,
                        dstX0, dstY0, dstX1, dstY1,
                        mask, filter);

该函数的作用就是,把一个帧缓冲区(read framebuffer)上的指定区域像素转移给另外一个帧缓冲区(draw framebuffer)上的指定区域。

总结

到现在,我们虽然还没有使用 WebGL 绘制三维图形,但我们已经进入了 WebGL 世界。我们可以使用 WebGL 绘制了简单的图形。但是这只是 WebGL 的绘制的冰山一角,我们使用 WebGL 当然不是为了绘制这样一个简单的图形。为了绘制更复杂的图形,我们还有很多的细节需要去了解。但是无论如何,我们都已经开启了 WebGL 的第一步,其实问题也并没有我们想象的那么难。