平面着色需要这么多的顶点复制吗?

Does flat shading require this much vertex duplication?

我是 WebGL (1.0) / OpenGL 的新手,我无法理解平面和平滑着色的顶点 -- 以及在这种情况下是否可以对平面着色进行数据优化:

假设我想使用 icosphere(2 细分)。它有 42 个点定义它的 80 个面。这些点坐标位于单位球体上。

平坦和平滑阴影的 icospheres 将出现在同一屏幕上。

使用平滑着色,法线将与位置向量相同,所以我免费获得它们。因此,我可以在 a_positionv_normal 的一个缓冲区中使用 42 vec3,并使用 240 unsigned_byte 的索引缓冲区来访问对象的它们。便宜!

但是如果使用平面着色,每个 面都会有自己的法线, 我认为这意味着对于 WebGL 1.0,每个面都会有三个重复的法线。 80 个面意味着 a_position 有 240 vec3(有很多重复向量),a_normal 有 240 vec3(其中三分之二只是重复向量)。我看不出有任何其他方法可以做到这一点。另一方面,我可以将位置数据和普通数据一起添加到同一个缓冲区中,我不需要索引缓冲区。

我已经开始工作了,而且看起来很快,但我是对的吗?重要吗?

Icosphere property Count Floats needed
Faces 80
Positions (smooth) 42 126 (+240 indices)
Normals (smooth) 42 0 (reuse positions)
Positions (flat) 240 720
Normals (flat) 240 720

我觉得要么我在学习中遗漏了一些东西,要么这就是 OpenGL 的现实,我应该习惯它,因为它天生就很快。

您说得对,对于平面着色,您必须复制位置。也就是说,因为顶点是位置、法线和所有其他属性的整个元组。

然而,这种重复对渲染时间的影响几乎为零。它增加了一些内存开销,是的,但就渲染过程而言,相同数量的数据被传输并合并到渲染过程中。事实上,某些属性在整体上的重复使得缓存更可预测,因为不涉及数据间接(即查找不同的法线,取决于渲染的面)。所以这实际上有理论上的性能提升。

你做的完全正确。

事实上,平面着色的最便携实现将需要为每个绘制的三角形复制顶点,这会带来内存使用的开销(在复杂几何体的情况下相当大)。潜在地,它也可能影响渲染性能,但这取决于硬件(现在应该不会引起注意)。这就是基本的 WebGL 1.0 允许做的事情。

然而,具有 OES_standard_derivatives 扩展的 WebGL 2.0 和 WebGL 1.0 提供了另一种选择 - 通过导数直接在片段着色器中计算三角形法线:

#extension GL_OES_standard_derivatives : enable
...
varying vec4 Position;
varying vec3 View;
...
void main()
{
  vec3 Normal = normalize (cross (dFdx (Position.xyz / Position.w), dFdy (Position.xyz / Position.w)));
  if (!gl_FrontFacing) { Normal = -Normal; }
  ...
  gl_FragColor = computeLighting (normalize (Normal), normalize (View), Position);

这需要每个片段的光照(例如 Phong shading 而不是 Gouraud 着色)。着色结果不会与 CPU 上的复制顶点和预计算三角形法线完全相同,但视觉效果将是相同的 - 具有可区分三角形的平面着色。

实际上,GL_OES_standard_derivatives 被广泛采用。 事实上,来自桌面 OpenGL 2.0 的 GLSL 1.1 从一开始就支持衍生产品(不需要扩展)——只是 OpenGL ES 2.0(因此,WebGL 1.0)决定排除它。

但是,有些人抱怨在各种 GPU 上执行衍生工具。精确导数的计算成本很高,因此 GLSL 规范允许返回更快的近似值——这对旧图形硬件至关重要。在实践中,该方法主要适用于平面着色,尽管其中一个 OpenGL ES implementation (Qualcomm) 有一个奇怪的行为,返回值的符号翻转。

例如,这是几年前为 Android 设备完成的 research(不知道 WebGL 是否会遇到同样的问题 - 网络浏览器可能会变黑 -列出损坏的实现或对已知的驱动程序错误应用一些变通方法):