27

我正在片段着色器中进行光线投射。为此,我可以想出几种方法来绘制全屏四边形。在裁剪空间中绘制一个四边形,并将投影矩阵设置为单位矩阵,或者使用几何着色器将一个点变成一个三角形带。前者使用立即模式,在 OpenGL 3.2 中已弃用。后者我出于新奇而使用,但它仍然使用即时模式来绘制一个点。

4

6 回答 6

24

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

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

这种方法当然可以与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);
}

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

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

一旦图元的单个像素落入这样的块中,真正的 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 倍,因此我们可以非常确定根本不需要裁剪它。

但是这值得吗?

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

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

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

于 2020-01-14T18:12:53.587 回答
20

您可以发送两个三角形来创建一个四边形,它们的顶点属性分别设置为 -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);
}

片段着色器:

varying vec2 textureCoord;
void main() {
   vec4 color1 = texture2D(t,textureCoord);
   gl_FragColor = color1;
}
于 2010-04-07T10:21:47.550 回答
13

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

#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(); 
}

顶点着色器只是空的:

#version 330 core

void main()
{
}

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

glDrawArrays(GL_POINTS, 0, 1);
于 2012-02-18T16:47:09.703 回答
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);
}

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

于 2018-08-01T03:47:32.413 回答
4

这类似于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);
}
于 2020-01-14T16:30:17.400 回答
2

以下来自将 fbo 纹理绘制到屏幕对齐四边形的类的绘制函数。

Gl.glUseProgram(shad);      

Gl.glBindBuffer(Gl.GL_ARRAY_BUFFER, vbo);           
Gl.glEnableVertexAttribArray(0);
Gl.glEnableVertexAttribArray(1);
Gl.glVertexAttribPointer(0, 3, Gl.GL_FLOAT, Gl.GL_FALSE, 0, voff);
Gl.glVertexAttribPointer(1, 2, Gl.GL_FLOAT, Gl.GL_FALSE, 0, coff);  

Gl.glActiveTexture(Gl.GL_TEXTURE0);
Gl.glBindTexture(Gl.GL_TEXTURE_2D, fboc);
Gl.glUniform1i(tileLoc, 0);

Gl.glDrawArrays(Gl.GL_QUADS, 0, 4);

Gl.glBindTexture(Gl.GL_TEXTURE_2D, 0);
Gl.glBindBuffer(Gl.GL_ARRAY_BUFFER, 0); 

Gl.glUseProgram(0); 

实际的四边形本身和坐标来自:

private float[] v=new float[]{  -1.0f, -1.0f, 0.0f,
                                1.0f, -1.0f, 0.0f,
                                1.0f, 1.0f, 0.0f,
                                -1.0f, 1.0f, 0.0f,

                                0.0f, 0.0f,
                                1.0f, 0.0f,
                                1.0f, 1.0f,
                                0.0f, 1.0f
};

vbo的绑定和设置我留给你。

顶点着色器:

#version 330

layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 coord;

out vec2 coords;

void main() {
    coords=coord.st;
    gl_Position=vec4(pos, 1.0);
}

因为位置是原始的,也就是说,没有乘以任何矩阵,所以四边形的 -1, -1::1, 1 适合视口。查找 Alfonse 的教程,链接到他在 openGL.org 上的任何帖子。

于 2011-10-13T02:40:39.063 回答