我正在片段着色器中进行光线投射。我可以想到几种方法来绘制全屏四边形。一种是使用投影矩阵设置为单位矩阵在裁剪空间中绘制四边形,另一种是使用几何着色器将点转换为三角形带。前者使用立即模式,在OpenGL 3.2中已弃用。后者我使用新颖性,但仍然使用立即模式绘制一个点。
我正在片段着色器中进行光线投射。我可以想到几种方法来绘制全屏四边形。一种是使用投影矩阵设置为单位矩阵在裁剪空间中绘制四边形,另一种是使用几何着色器将点转换为三角形带。前者使用立即模式,在OpenGL 3.2中已弃用。后者我使用新颖性,但仍然使用立即模式绘制一个点。
我认为最有效的方法是绘制一个“全屏”三角形。为了使三角形覆盖整个屏幕,它需要比实际视口更大。在NDC(以及剪切空间中,如果我们设置 w=1
),视口将始终为[-1,1]
正方形。为了使三角形完全覆盖这个区域,我们需要让两边的长度是视口矩形的两倍,这样第三边就会穿过视口的边缘,因此我们可以例如使用以下坐标(按逆时针顺序):(-1,-1)
、(3,-1)
、(-1,3)
。
我们也不需要担心纹理坐标。为了在可见视口中获得通常的归一化[0,1]
范围,我们只需要使顶点对应的纹理坐标增加一倍,而重心插值将为任何视口像素产生与使用四边形时完全相同的结果。
当然,我们也可以像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_HelperInvocation
GLSL函数查询)。请参见Fabian“ryg”Giesen的博客文章以获取更多详细信息。
如果您使用两个三角形渲染四边形,则两个三角形都将在视口对角线上生成大量无用的帮助程序调用。对于完美的正方形视口(宽高比1),效果最差。如果您只画一个三角形,则没有这样的对角线边缘(它位于视口之外,根本不会影响光栅化器),因此不会产生额外的帮助调用。
等等,如果三角形跨越视口边界,那么它不会被裁剪,并且实际上会在GPU上产生更多的工作吗?
如果您阅读有关图形管道(甚至GL规范)的教材材料,您可能会有这样的印象。但是,现实世界中的GPU使用一些不同的方法,例如查障剪裁(Guard-band clipping)。我不会在这里详细介绍(这将是一个单独的话题,请查看Fabian“ryg”Giesen的精彩博客文章以获取详细信息),但是一般思路是,无论基元是否完全在视口内部,光栅化器都只会为像素内部(或剪刀矩形)的像素产生片段,因此如果以下两种情况均成立:
a)三角形仅延伸到2D顶部/底部/左侧/右侧剪裁平面(与更棘手的z维度近/远截面相反,这更难处理,特别是因为顶点也可以位于相机后面)
GLuint emptyVAO; glGenVertexArrays(1, &emptyVAO); glBindVertexArray(emptyVAO); glDrawArrays(GL_TRIANGLES, 0, 3);
(感谢geenux提供的空VAO创建)。 - Van der Dekenconst 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;
}
不需要使用几何着色器、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个顶点的绘制调用。
可以使用几何着色器输出全屏四边形:
#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);
GLint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glDrawArrays(GL_POINTS, 0, 1);
- geenux这与 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);
}
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上发布的任何帖子中链接的教程。