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

643ylb08  于 2022-12-12  发布在  其他
关注(0)|答案(6)|浏览(228)

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

vnjpjtjt

vnjpjtjt1#

我认为最有效的方法是绘制一个“全屏”三角形。要使三角形覆盖整个屏幕,它需要比实际的视口大。在NDC中(如果我们设置w=1,也包括剪切空间),视口将始终是[-1,1]正方形。对于完全覆盖此区域的三角形,我们需要两条边的长度是视口矩形的两倍,这样第三条边将穿过视口的边缘,因此我们可以例如使用以下坐标(以逆时针顺序):一个月二个月一个月一个月三个月一个月一个月四个月一个月
我们也不需要担心纹理坐标。要得到可见视口中通常的归一化[0,1]范围,我们只需要使顶点的相应纹理坐标变大,并且重心插值将对任何视口像素产生与使用四边形时完全相同的结果。
这种方法当然可以与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);
        texcoords = 0.5 * gl_Position.xy + vec2(0.5);
}

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

这 * 不是 * 关于一个保存的顶点着色器调用,以及在前端要处理的少一个三角形。使用单个三角形的最显著的效果将是更少的片段着色器调用
一旦图元的单个像素福尔斯2x2像素大小的块(“quads”)中,真实的的GPU总是会为该块调用片段着色器。这对于计算window-space derivative functions是必要的(纹理采样也隐含地需要这些,请参阅此问题)。
如果基元没有覆盖该块中的所有4个像素,则剩余的片段着色器调用将不会执行任何有用的工作(除了为导数计算提供数据之外),并且将是所谓的 * 辅助调用 *(什至可以通过gl_HelperInvocation GLSL function查询)。另请参见Fabian "ryg" Giesen's blog article了解更多详细信息。
如果你用两个三角形渲染一个四边形,两个三角形都有一条边对角穿过视口,并且在两个三角形上,你会在对角边上产生很多无用的辅助调用。(长宽比1)。如果您绘制单一三角形,将没有这样的对角边(它位于视口之外,与光栅化器完全无关),因此将没有额外的辅助调用。

等一下,如果三角形延伸到视口边界,它不会被裁剪,实际上会在GPU上投入 * 更多 * 工作吗?

如果你读过有关图形管线的教材(甚至GL规范),你可能会有这样的印象。但实际的GPU使用一些不同的方法,如 * 保护带削波 *。(这将是一个单独的主题,有关详细信息,请参阅Fabian "ryg" Giesen's fine blog article),但是一般的想法是光栅化器将仅为视口内的像素产生片段(或剪式矩形)无论如何,不管基元是否完全位于矩形内部,所以如果以下两个条件都成立,我们可以简单地向它扔更大的三角形:

  • a)三角形仅延伸2D顶/底/左/右裁剪平面(与z维近/远裁剪平面相反,z维近/远裁剪平面更难以处理,尤其是因为顶点也可能位于相机的 * 后面 *)
  • B)实际的顶点坐标(以及光栅化器可能对它们进行的所有中间计算结果)可以用GPU硬件光栅化器使用的内部数据格式表示。光栅化器将使用特定于实现的宽度的定点数据类型,而顶点坐标是32位单精度浮点数。(这基本上定义了保护带的大小)

我们的三角形只比视口大3倍,所以我们可以非常确定根本不需要裁剪它。
"但这值得吗"
好吧,片段着色器调用的节省是真实的(特别是当你有一个复杂的片段着色器时),但整体效果在现实世界中可能几乎无法衡量。另一方面,这种方法并不比使用全屏四画面更复杂,而且使用 * 更少的数据 *,所以即使可能不会有很大的差异,也不会有什么坏处,所以为什么 * 不 * 使用它呢?

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

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

qeeaahzv

qeeaahzv2#

可以发送两个三角形创建一个四边形,并将其顶点属性分别设置为-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;
}
wh6knrhe

wh6knrhe3#

完全不需要使用几何着色器、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个顶点的调用。

ctzwtxfj

ctzwtxfj4#

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

#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的dummy draw命令:

glDrawArrays(GL_POINTS, 0, 1);
h7appiyu

h7appiyu5#

这与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);
}
goqiplq2

goqiplq26#

以下代码来自类的draw函数,该类将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适合视口。

相关问题