WebGL是如何工作的?

33

我想深入了解WebGL的工作原理。我希望获得大多数人并不关心的知识,因为这些知识对于普通的WebGL程序员来说并非必要。例如,渲染系统的每个部分(浏览器、图形驱动程序等)在将图像呈现到屏幕上时扮演的角色是什么?每个浏览器是否都必须创建一个JavaScript / HTML引擎/环境才能在其中运行WebGL?为什么Chrome在WebGL兼容性方面领先于其他所有浏览器?

那么,有哪些好资源可以开始学习?从我浏览几分钟看到的情况来看,Kronos规范有些不足,我更想知道它是如何在浏览器中实现的,以及需要改变你的系统中还需要做什么才能使其成为可能。

2个回答

46
希望这篇小文对你有所帮助。它概述了我在WebGL和3D方面所学到的大部分内容。顺便说一句,如果我有任何错误,请有人纠正我,因为我也还在学习!
架构
浏览器只是一个Web浏览器。它所做的就是通过JavaScript公开WebGL API,程序员使用API完成其他所有操作。
据我所知,WebGL API本质上只是一组(由浏览器提供的)JavaScript函数,其包装OpenGL ES规范。因此,如果您了解OpenGL ES,则可以很快地采用WebGL。不要将其与纯OpenGL混淆,"ES"很重要。
WebGL规范故意保持非常低级,使得大量实现需要在不同应用程序之间重新实现。编写自动化框架取决于社区,选择使用哪个框架(如果有的话)则取决于开发人员。自己编写不是很难,但这意味着要花费大量的开销来重新发明轮子。(我的WebGL框架Jax已经开发了一段时间了。)
图形驱动程序提供了OpenGL ES的实现,它实际上运行您的代码。此时,它在机器硬件上运行,甚至低于C代码。虽然这是使WebGL成为可能的原因,但这也是一把双刃剑,因为OpenGL ES驱动程序中的错误(我已经注意到了相当多)将显示在您的Web应用程序中,并且您不会知道,除非您可以指望您的用户群体提交包括操作系统、视频硬件和驱动程序版本在内的连贯错误报告。这里是这些问题的调试过程。 在Windows上,WebGL API和硬件之间存在一个额外的层:ANGLE或“Almost Native Graphics Layer Engine”。由于Windows上的OpenGL ES驱动程序通常很糟糕,所以ANGLE接收这些调用并将它们转换为DirectX 9调用。

在三维空间中绘制

现在您知道了这些组件是如何组合在一起的,让我们来看看更低级别的解释,说明所有内容是如何组合在一起生成3D图像的。

JavaScript

首先,JavaScript代码从HTML5 canvas元素中获取一个3D上下文。然后,它注册了一组着色器,这些着色器是用GLSL([Open] GL Shading Language)编写的,基本上类似于C语言。
其余的过程非常模块化。您需要使用在着色器中定义的uniform和attribute将顶点数据和任何其他要使用的信息(例如顶点颜色、纹理坐标等)传送到图形管线中,但是这些信息的确切布局和命名取决于开发人员。
JavaScript设置初始数据结构并将其发送到WebGL API,该API将其发送到ANGLE或OpenGL ES,最终将其发送到图形硬件。

顶点着色器

一旦着色器可以使用这些信息,着色器必须经过两个阶段来转换信息以生成3D对象。第一阶段是顶点着色器,它设置网格坐标。(此阶段完全在视频卡上运行,在上述所有API之下。)通常,顶点着色器执行的过程看起来像这样:
gl_Position = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * VERTEX_POSITION

其中VERTEX_POSITION是一个四维向量(x、y、z 和 w,通常设置为 1);VIEW_MATRIX是代表相机视角的4×4矩阵;MODEL_MATRIX是一个4×4矩阵,将对象空间坐标(即,在应用旋转或平移之前与对象本地相关的坐标)转换为世界空间坐标;PROJECTION_MATRIX代表相机的透镜。

通常情况下,VIEW_MATRIXMODEL_MATRIX 会被预先计算并称为 MODELVIEW_MATRIX,有时候三者都会被预先计算到 MODELVIEW_PROJECTION_MATRIXMVP 中。虽然这些通常被认为是优化,但我希望有时间进行一些基准测试。如果每帧都这样做,那么在 JavaScript 中进行预计算实际上可能比较慢,因为 JavaScript 本身并不是很快。在这种情况下,通过 GPU 上的数学计算获得的硬件加速可能比在 JavaScript 中使用 CPU 更快。当然,我们可以希望未来的 JS 实现将通过更快地运行来解决这个潜在的问题。

剪裁坐标

应用所有这些后,gl_Position 变量将具有一组 XYZ 坐标,在 [-1,1] 范围内,并具有 W 分量。这些被称为剪裁坐标。
值得注意的是,剪辑坐标是顶点着色器真正需要生成的唯一内容。只要你产生了剪辑坐标结果,就可以完全跳过上面执行的矩阵变换。(我甚至尝试过用四元数替换矩阵;它能正常工作,但我放弃了这个项目,因为我没有获得我所希望的性能改进。)
在将剪辑坐标提供给gl_Position之后,WebGL通过gl_Position.w除以结果,从而产生所谓的归一化设备坐标。从那里,将像素投影到屏幕上只需要乘以1/2的屏幕尺寸,然后加上1/2的屏幕尺寸即可。[1]以下是一些将剪辑坐标转换为800x600显示器上的二维坐标的示例:
clip = [0, 0]
x = (0 * 800/2) + 800/2 = 400
y = (0 * 600/2) + 600/2 = 300

clip = [0.5, 0.5]
x = (0.5 * 800/2) + 800/2 = 200 + 400 = 600
y = (0.5 * 600/2) + 600/2 = 150 + 300 = 450

clip = [-0.5, -0.25]
x = (-0.5  * 800/2) + 800/2 = -200 + 400 = 200
y = (-0.25 * 600/2) + 600/2 = -150 + 300 = 150

像素着色器

确定像素绘制的位置后,像素被传递给像素着色器,该着色器选择像素的实际颜色。这可以通过多种方式完成,从简单地硬编码特定颜色到纹理查找再到更高级的法线和视差映射(这实质上是通过“欺骗”纹理查找来产生不同效果的方法)。

深度和深度缓冲区

到目前为止,我们忽略了剪辑坐标的Z分量。以下是其工作原理。当我们乘以投影矩阵时,第三个剪辑分量会得出一个数字。如果该数字大于1.0或小于-1.0,则该数字超出了投影矩阵的视图范围,分别对应于矩阵zFar和zNear值。

所以,如果它不在[-1,1]的范围内,则完全被裁剪。如果它在该范围内,则将Z值缩放为0到1 [2],并与深度缓冲区[3]进行比较。深度缓冲区等于屏幕尺寸,因此如果使用800x600的投影,则深度缓冲区的宽度为800像素,高度为600像素。我们已经有了像素的X和Y坐标,因此将它们插入深度缓冲区以获取当前存储的Z值。如果Z值大于新的Z值,则新的Z值比先前绘制的任何内容都更接近,并替换它[4]。此时,可以安全地点亮所讨论的像素(或在WebGL的情况下,将像素绘制到画布上),并将Z值存储为新的深度值。
如果Z值大于存储的深度值,则被视为“在”已绘制的任何内容之后,并且该像素将被丢弃。
[1]实际转换使用gl.viewport设置从标准化设备坐标转换为像素。

[2]实际上它是根据gl.depthRange的设置进行缩放的。默认值为0到1。

[3]假设您有深度缓冲区,并使用gl.enable(gl.DEPTH_TEST)启用了深度测试。

[4]您可以使用gl.depthFunc设置如何比较Z值。


1
非常好的描述。谢谢。 - Oscar
1
一篇非常精彩的解释 :) - Kirill Fuchs
2
OpenGL ES从未用于在桌面显卡上渲染WebGL,OpenGL ES仅用于移动硬件,在操作系统不是Windows的桌面上,浏览器引擎将WebGL API调用转换为OpenGL调用。这些信息也可以在这篇非常好的文章中找到:codeflow.org/entries/2013/feb/02/why-you-should-use-webgl - LJᛃ
2
关于您提到解决驱动程序错误的评论。请在http://crbug.com或firefox等处提交错误报告。浏览器不希望您必须处理驱动程序错误,他们将尽力解决这些问题,并使WebGL在任何地方都具有一致的行为。这包括编写新测试,以便驱动程序不会退化。但是,只有在了解错误并且您提交错误报告后,他们才能做到这一点。 - gman

11

我会阅读这些文章。

http://webglfundamentals.org/webgl/lessons/webgl-how-it-works.html

假设这些文章是有帮助的,WebGL 运行在浏览器中。它渲染到一个 canvas 标签上。你可以将 canvas 标签想象成一个 img 标签,但是你使用 WebGL API 生成图像而不是下载图像。

和其他 HTML5 标签一样,canvas 标签可以用 CSS 进行样式设置,位于页面的其它部分之上或之下。它与页面的其它部分混合。还可以通过 CSS 与页面的其它部分组合变换、旋转和缩放。这与 OpenGL 或 OpenGL ES 有很大的区别。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接