8
votes

What is the best way to sample fullscreen textures in a fragment shader, so for example the g-buffer in a deferred renderer, or the scene texture within a postprocess shader?

At the moment I use the following two ways:

  • Pass the screen size to the shader as a uniform and calculate (u,v) from gl_FragCoord:

    vec2 texCoord = gl_FragCoord.xy / vScreenSize;
    vec3 c = texture( tex, texCoord ).rgb;
    

    Does not seem to be ideal since a division is required and supplying the shader with the screen size is cumbersome.

  • Convert gl_FragCoord.xy into an ivec and use texelFetch:

    vec3 c = texelFetch( tex, ivec2(gl_FragCoord.xy), 0 ).rgb;
    

    Also not ideal because the conversion from float to int is required.

So is there a better way? I really just want to sample a buffer at the exact point where my pixel shader is drawing.


// EDIT:


Ok, with the suggestions of interpolated texture-coordinates that come from the vertex shader I managed to figure out the following code:

Vertex shader:

#version 150

uniform mat4 im_ModelViewProjectionMatrix;

in vec3 iv_Vertex;
noperspective out vec2 vVertCoord;

void main( void )
{
    gl_Position = im_ModelViewProjectionMatrix * vec4(iv_Vertex, 1.0);
    vVertCoord = (gl_Position.xy / gl_Position.w) * (0.5) + vec2(0.5);
}

I basically calculate normalized device coordinates (NDC) from the clip space position with the perspective division and then map the NDCs (that range from [-1,1]) into the interval [0,1]. This works great for fullscreen quads (even without the perspective division because coordinates are so simple). The arbitrary geometry I need to draw as light-geometry in my deferred renderer has some problems though. In the vertex shader I output vVertCoord as a color for red=x and green=y:

#version 150

noperspective in vec2 vVertCoord;
out vec4 colorOut;

void main( void )
{
    colorOut = vec4(vVertCoord.x, vVertCoord.y, 0, 1);
}

This is the result when I am inside a point-light-sphere, everything looks fine (the black lines are rendered on purpose):

All fine

However, if I move closer to the light-geometry this is the result:

Not ok

What is that red patch in the top left corner doing there? You don't want to see the results in real-color with the debugging colors disabled because it looks like an lsd-trip, everything warps around when you move the camera. Is this precision related? Note that all is fine when I use gl_FragCoord in the pixel shader instead.

1
Is there any particular reason why your texture coordinates are based on gl_FragCoord and not a normal interpolated attribute? In the first example that would already eliminate the need for the division. p.s. instead of vScreenSize you can have the reciprocal of screen size (turning the division into a multiplication) - Lawrence Kok
You probably don't want the latter, that will eliminate the possibility of doing any texture filtering. While you might think you don't really care about that, in a deferred shading engine it can be useful to do G-Buffer generation and shading at different resolutions (particularly on lower-end hardware which is usually quite fill-rate limited). - Andon M. Coleman

1 Answers

2
votes

You don't have to do any special math if you just pass in an interpolated vertex coordinate from your vertex shader.

For example, if you were drawing a simple unit square that covered your screen, you could just and would then recieve the full screen texture, you could just do something like this:

vertex shader pseudocode:
layout(position = 0) in vec3 in_vertex;
out vec3 out_vertex;
void main()
{
    //Do your matrix multiplications to your in_vertex to get its final position...
    out_vertex = in_vertex;
}

Your vertex shader will then properly interpolate the in_vertex to be on the range x:0...1, y:0...1 (as long as you're drawing a unit square) and pass it to your fragment shader. Your fragment shader will then use it like so:

fragment shader pseudocode:
in vec3 out_vertex;
uniform sampler2D tex;
void main()
{
    gl_fragcolor = texture(tex,vec2(out_vertex.x,out_vertex.y));
}

No other math is needed, as long as you take care that the out_vertex will only ever be in the range of 0...1. To extend this example a bit, imagine here is our square:

(0,1)+-----------+(1,1) | | | | | | | | | | (0,0)+-----------+(0,1)

And we wanted to sample this point in the exact center:

(0,1)+-----------+(1,1) | | | | | * | | | | | (0,0)+-----------+(0,1)

Our vertex shader will automatically interpolate that position from the other 4 positions and pass the following vec3 to the fragment shader:

out_vertex = vec3(0.5,0.5,0);

Which can then be used to sample the texture successfully