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

时间:2010-04-06 22:49:08

标签: opengl glsl

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

7 个答案:

答案 0 :(得分: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);
}

Fragment Shader:

varying vec2 textureCoord;
void main() {
   vec4 color1 = texture2D(t,textureCoord);
   gl_FragColor = color1;
}

答案 1 :(得分: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(); 
}

顶点着色器只是空的:

#version 330 core

void main()
{
}

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

glDrawArrays(GL_POINTS, 0, 1);

答案 2 :(得分:9)

我将争辩说,最有效的方法将是绘制一个单个“全屏”三角形。要使三角形覆盖全屏,它必须大于实际的视口。在NDC中(如果设置了w=1,则在裁剪空间中),视口将始终是[-1,1]正方形。为了使三角形完全覆盖该区域,我们需要使两个侧面的长度为视口矩形的两倍,以便使第三侧与视口的边缘相交,因此我们可以例如使用以下坐标(在逆时针顺序):​​(-1,-1)(3,-1)(-1,3)

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

当然可以按照demanze's answer中的建议将这种方法与无属性渲染结合使用:

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);
        textcoords = 0.5 * gl_Position.xy + vec2(0.5);
}

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

关于一个保存的顶点着色器调用,以及另一个要在前端处理的少三角形,这不是不是。使用单个trianlge的最显着效果是更少的片段shder调用)

一旦图元的单个像素落入此类块,实际GPU就会为2x2像素大小的块调用片段着色器。这对于计算window-space derivative functions是必需的(纹理采样也隐式需要这些,请参见this question)。

如果图元未覆盖该块中的所有4个像素,则其余片段着色器调用将无济于事(除了为导数计算提供数据外),这就是所谓的 helper调用 em>(甚至可以通过gl_HelperInvocation GLSL function进行查询。有关更多详细信息,另请参见Fabian "ryg" Giesen's blog article

如果渲染具有两个三角形的四边形,则两个都将有一条沿对角线穿过视口的边缘,并且在这两个三角形上,您将在该边缘上生成很多无用的辅助调用。对于完美的正方形视口(纵横比1),效果将最差。如果您绘制单个三角形,则不会有这样的对角线边缘(它位于视口之外并且完全不涉及栅格化器),因此将没有其他辅助程序调用。

请稍等一下,如果三角形在视口边界上延伸,它是否会被裁剪并实际上将更多工作在GPU上?

如果您阅读有关图形管线(甚至是GL规范)的教科书材料,则可能会给人以印象。但是现实世界中的GPU使用了一些不同的方法,例如保护带限幅。我在这里不做详细介绍(这本身就是一个主题,有关详细信息,请查看Fabian "ryg" Giesen's fine blog article),但是一般的想法是光栅化器将仅为视口内的像素生成片段(或无论它是否完全位于其内部,因此只要满足以下两个条件,我们就可以向其抛出较大的三角形:

  • a)三角形仅延伸2D顶部/底部/左侧/右侧剪切平面(与z维度的近/远剪切平面相反)

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

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

但这值得吗?

好吧,片段着色器调用的节省是真实的(尤其是当您使用复杂的片段着色器时),但是在实际情况下,总体效果可能几乎无法测量。另一方面,该方法并不比使用全屏四边形更为复杂,并且使用的是较少的数据,因此即使可能不会产生很大的变化,也不会造成伤害,所以为什么< em>不使用吗?

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

从理论上讲,您可以将其与剪刀测试结合使用以绘制一些仲裁轴对齐的矩形(剪刀测试将非常有效,因为它仅限制了首先生成的片段,这不是硬件中的实际“测试”,会丢弃片段)。但是,这要求您更改要绘制的每个矩形的剪刀参数,这意味着要进行很多状态更改,并且每次绘制调用都将您限制为一个矩形,因此在大多数情况下,这样做不是一个好主意。

答案 3 :(得分:6)

Chistophe Riccio的提示:

  

由于我在视频中说明的原因,大三角形效率更高:

     

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

     

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

答案 4 :(得分:3)

根本不需要使用几何体着色器,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个顶点的绘图调用。

答案 5 :(得分: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上的任何帖子。

答案 6 :(得分:1)

这类似于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);
}