在OpenGL 3.2中绘制全屏四边形的最佳方法是什么?

Bry*_*man 21 opengl glsl

我在片段着色器中进行光线投射.为了这个目的,我可以想出几种方法来绘制全屏四边形.在投影矩阵设置为单位矩阵的情况下在剪辑空间中绘制四边形,或使用几何着色器将点转换为三角形条带.前者使用立即模式,在OpenGL 3.2中弃用.后者我使用了新颖性,但它仍然使用立即模式来说明一点.

Adi*_*cin 17

您可以发送两个创建四边形的三角形,其顶点属性分别设置为-1/1.

您不需要将它们与顶点/片段着色器中的任何矩阵相乘.

这是一些代码示例,简单,因为它是:)

顶点着色器:

const vec2 madd=vec2(0.5,0.5);
attribute vec2 vertexIn;
varying vec2 textureCoord;
void main() {
   textureCoord = vertexIn.xy*madd+madd; // scale vertex attribute to [0-1] range
   gl_Position = vec4(vertexIn.xy,0.0,1.0);
}
Run Code Online (Sandbox Code Playgroud)

片段着色器:

varying vec2 textureCoord;
void main() {
   vec4 color1 = texture2D(t,textureCoord);
   gl_FragColor = color1;
}
Run Code Online (Sandbox Code Playgroud)

  • 顺便说一下,这符合你以前的想法,但在OpenGL 3.2中肯定不会弃用.您仍然可以通过顶点数组/缓冲区等发送顶点属性.OpenGL仍然是一个立即模式渲染API. (3认同)

der*_*ass 15

我会认为,最有效的方法将是在绘制单个“全屏幕”三角。对于覆盖全屏的三角形,它需要比实际视口大。在 NDC(以及裁剪空间,如果我们设置w=1)中,视口将始终是[-1,1]正方形。为了让三角形完全覆盖这个区域,我们需要有两条边是视口矩形的两倍长,这样第三条边将穿过视口的边缘,因此我们可以例如使用以下坐标(在逆时针顺序): (-1,-1), (3,-1), (-1,3).

我们也不需要担心 texcoords。为了[0,1]在可见视口中获得通常的归一化范围,我们只需要将顶点的相应 texcoord 设置为 2 倍大,并且重心插值将为任何视口像素产生与使用四边形时完全相同的结果。

这种方法当然可以与demanze 的回答中建议的属性渲染相结合:

out vec2 texcoords; // texcoords are in the normalized [0,1] range for the viewport-filling quad part of the triangle
void main() {
        vec2 vertices[3]=vec2[3](vec2(-1,-1), vec2(3,-1), vec2(-1, 3));
        gl_Position = vec4(vertices[gl_VertexID],0,1);
        texcoords = 0.5 * gl_Position.xy + vec2(0.5);
}
Run Code Online (Sandbox Code Playgroud)

为什么单个三角形会更有效率?

这与保存的顶点着色器调用和前端处理的少一个三角形无关。使用单个三角形的最显着效果是减少了片段着色器调用

一旦基元的单个像素落入这样的块,真实的 GPU 总是为 2x2 像素大小的块(“四边形”)调用片段着色器。这对于计算窗口空间导数函数是必需的(纹理采样也隐含需要这些函数,请参阅此问题)。

如果图元没有覆盖该块中的所有 4 个像素,则剩余的片段着色器调用将不会做任何有用的工作(除了为导数计算提供数据)并且将成为所谓的辅助调用(甚至可以通过gl_HelperInvocationGLSL 函数)。有关更多详细信息,另请参阅Fabian "ryg" Giesen 的博客文章

如果你渲染一个有两个三角形的四边形,两个三角形都会有一条边穿过视口,并且在两个三角形上,你会在对角边处生成很多无用的辅助调用。对于完美的方形视口(纵横比 1),效果最差。如果您绘制单个三角形,则不会有这样的对角边(它位于视口之外,根本不涉及光栅化器),因此不会有额外的辅助调用。

等一下,如果三角形跨过视口边界,它会不会被剪裁并实际上在 GPU 上投入更多工作?

如果您阅读有关图形管道(甚至 GL 规范)的教科书材料,您可能会有这种印象。但现实世界的 GPU 使用一些不同的方法,例如Guard-band clipping。我不会在这里详细介绍(这将是一个单独的主题,有关详细信息,请查看Fabian "ryg" Giesen 的精美博客文章),但总体思路是光栅化器仅会为像素内部的像素生成片段。视口(或剪刀矩形)无论如何,无论图元是否完全位于其中,因此如果以下两个都为真,我们可以简单地向其抛出更大的三角形:

  • a) 三角形只扩展了 2D 顶/底/左/右剪裁平面(与 z 维近/远剪裁平面相反,后者更难处理,特别是因为顶点也可能位于相机后面

  • b) 实际顶点坐标(以及光栅化器可能对其进行的所有中间计算结果)可以用 GPU 的硬件光栅化器使用的内部数据格式表示。光栅化器将使用特定于实现的宽度的定点数据类型,而顶点坐标是 32 位单精度浮点数。(这基本上是定义保护带大小的东西)

我们的 tiranlge 仅比视口大 3 倍,因此我们可以非常确定根本不需要剪辑它。

但是这值得吗?

好吧,片段着色器调用的节省是真实的(尤其是当您拥有复杂的片段着色器时),但在实际场景中,整体效果可能几乎无法衡量。另一方面,该方法并不比使用全屏四边形更复杂,并且使用的数据更少,因此即使可能不会产生很大的差异,也不会受到伤害,那么为什么使用它呢?

这种方法可以用于各种轴对齐的矩形,而不仅仅是全屏矩形吗?

从理论上讲,您可以将其与剪刀测试结合起来绘制一些任意轴对齐的矩形(并且剪刀测试将非常有效,因为它只是首先限制了产生哪些片段,这不是真正的“测试” " 在丢弃片段的硬件中)。但是,这需要您更改要绘制的每个矩形的剪刀参数,这意味着许多状态更改并将您限制为每个绘制调用的单个矩形,因此在大多数情况下这样做不是一个好主意。

  • 如果有人坚持这个答案的绘制代码:`GLuint emptyVAO; glGenVertexArrays(1, &emptyVAO); glBindVertexArray(空VAO); glDrawArrays(GL_TRIANGLES, 0, 3);`(对于空 VAO 创建,感谢 [geenux](/sf/users/63333091/))。 (2认同)

小智 12

要输出全屏四边形几何着色器,可以使用:

#version 330 core

layout(points) in;
layout(triangle_strip, max_vertices = 4) out;

out vec2 texcoord;

void main() 
{
    gl_Position = vec4( 1.0, 1.0, 0.5, 1.0 );
    texcoord = vec2( 1.0, 1.0 );
    EmitVertex();

    gl_Position = vec4(-1.0, 1.0, 0.5, 1.0 );
    texcoord = vec2( 0.0, 1.0 ); 
    EmitVertex();

    gl_Position = vec4( 1.0,-1.0, 0.5, 1.0 );
    texcoord = vec2( 1.0, 0.0 ); 
    EmitVertex();

    gl_Position = vec4(-1.0,-1.0, 0.5, 1.0 );
    texcoord = vec2( 0.0, 0.0 ); 
    EmitVertex();

    EndPrimitive(); 
}
Run Code Online (Sandbox Code Playgroud)

顶点着色器只是空的:

#version 330 core

void main()
{
}
Run Code Online (Sandbox Code Playgroud)

要使用此着色器,您可以使用带有空VBO的虚拟绘图命令:

glDrawArrays(GL_POINTS, 0, 1);
Run Code Online (Sandbox Code Playgroud)


d3m*_*nz3 12

根本不需要使用几何着色器、VBO 或任何内存。

顶点着色器可以生成四边形。

layout(location = 0) out vec2 uv;

void main() 
{
    float x = float(((uint(gl_VertexID) + 2u) / 3u)%2u); 
    float y = float(((uint(gl_VertexID) + 1u) / 3u)%2u); 

    gl_Position = vec4(-1.0f + x*2.0f, -1.0f+y*2.0f, 0.0f, 1.0f);
    uv = vec2(x, y);
}
Run Code Online (Sandbox Code Playgroud)

绑定一个空的 VAO。发送 6 个顶点的绘制调用。

  • 无用的计算,只需硬编码顶点位置 (2认同)

Mag*_*nus 7

这与demanze的答案类似,但我认为它更容易理解。另外,这只是使用 TRIANGLE_STRIP 用 4 个顶点绘制的。

#version 300 es
out vec2 textureCoords;

void main() {
    const vec2 positions[4] = vec2[](
        vec2(-1, -1),
        vec2(+1, -1),
        vec2(-1, +1),
        vec2(+1, +1)
    );
    const vec2 coords[4] = vec2[](
        vec2(0, 0),
        vec2(1, 0),
        vec2(0, 1),
        vec2(1, 1)
    );

    textureCoords = coords[gl_VertexID];
    gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
}
Run Code Online (Sandbox Code Playgroud)


kva*_*ark 6

Chistophe Riccio的提示:

我在视频中说明了一个大三角形的效率更高:

http://www.youtube.com/watch?v=WFx6StqpRdY

http://www.youtube.com/watch?v=WnUS8kzA3dI&feature=related

  • Chrostophe能够更好地解释,但我的理解是,在四边形的情况下,对角线边缘使得PS执行不相干,这至少会损害性能. (4认同)
  • 这些视频中的任何内容都没有解释. (3认同)
  • @fen(以及其他任何人)在[AMD演示文稿](https://www.slideshare.net/DevCentralAMD/vertex-shader-tricks-bill-bilodeau)中有一个D3D + HLSL代码示例推荐它,pp.12 -14.没有解释,但一个很好的图表. (2认同)