Search code examples
openglunity-game-engineglslshadervertex-shader

How to get accurate fragment screen position, like gl_FragCood in vertex shader?


I did some calculations using projected gl_Position and screen parameters, but position seems distorted in polygons close to the camera. But when I use...

vec2 fragmentScreenCoordinates = vec2(gl_FragCoord.x / _ScreenParams.x, gl_FragCoord.y / _ScreenParams.y);

...I got pretty accurate xy results.

Pretty output gl_FragCoord.xy coordinates: enter image description here

Calculating from projected vertices results in interpolated values all over the faces, which I cannot use for sampling screen aligned textures.

Ugly interpolated output from gl_Position: enter image description here

Is there a way to produce this gl_FragCoord-like value in vertex shader? I really want to calculate texture coordinates in vertex shader for independent texture reads, manual depth tests, etc.

Or is there any Unity built in values I can use here?


Solution

  • In the vertex shader, you set

    gl_Position = ...
    

    This must be in clip space, before the perspective divide to normalized device coordinates. This is because OpenGL does a bunch of stuff in the 4D space and is necessary for interpolation.

    Since you just want the value at each vertex and nothing interpolated, you can normalize right away (or can even leave out if using an orthographic projection)...

    vec3 ndc = gl_Position.xyz / gl_Position.w; //perspective divide/normalize
    vec2 viewportCoord = ndc.xy * 0.5 + 0.5; //ndc is -1 to 1 in GL. scale for 0 to 1
    vec2 viewportPixelCoord = viewportCoord * viewportSize;
    

    Here, viewportCoord is the equivalent of your fragmentScreenCoordinates, assuming the viewport covers the window.

    Note: as @derhass points out, this will fail if geometry intersects the w = 0 plane. I.e. a visible triangle's vertex is behind the camera.

    [EDIT]
    The comments discuss using the coordinates for 1 to 1 pixel nearest neighbor lookup. As @AndonM.Coleman says, changing the coordinates will work, but it's easier and faster to use nearest neighbor filtering. You can also use texelFetch, which bypasses filtering altogether:

    1. Snap the coordinates:

      vec2 sampleCoord = (floor(viewportPixelCoord) + 0.5) / textureSize(mySampler);
      vec4 colour = texture(mySampler, sampleCoord);
      
    2. Nearest neighbor filtering (not sure what this is in unity3d):

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); //magnification is the important one here
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      
    3. Use texelFetch:

      vec4 colour = texelFetch(mySampler, ivec2(viewportPixelCoord), 0);