Point sprites intersecting geometry and how to fix that


in a recent project I had to draw lots of LEDs in a scene with other geometry. The go-to solution for this are, of course, point sprites: just render a bunch of 2D billboards where the LEDs should go. To make sure they would get properly clipped by geometry in the foreground, I had to enable depth testing. The problem was that all of them were now intersecting the actual geometry of the light fixture.

It would be great if we could render the point sprites on top of the geometry, while making sure they don’t visually move at all and would still be covered by objects in the foreground.

Instead of doing depth testing in the fragment shader, or foregoing depth testing altogether, I chose to add a little bias to the sprites to shift them slightly towards the camera. My world coordinates are in meters and I think 5 centimeters would do it. So here’s the simplified vertex shader:

#version 330
uniform mat4 ciModelView;
uniform mat4 ciProjectionMatrix;
uniform vec2 uPointSize;        // in view space units (usually the same as world space) 
uniform vec2 uViewportSize;     // size of the window (or viewport) in pixels
in vec4 ciPosition;             // in object space
float calcPointSize( in vec4 viewPosition, in vec2 size, in float viewportWidth )
    vec4 projected = ciProjectionMatrix * vec4( 0.5 * size.xy, viewPosition.z, viewPosition.w );
    return viewportWidth * projected.x / projected.w;
void main(void)
    // Calculate the view space position.
    vec4 viewPosition = ciModelView * ciPosition;
    // Calculate point sprite size.
    gl_PointSize = calcPointSize( viewPosition, uPointSize, uViewportSize.x );
    // Now move the vertex 5cm towards the camera by scaling the w coordinate:
    viewPosition.w *= viewPosition.z / ( viewPosition.z + 0.05 );

    gl_Position = ciProjectionMatrix * viewPosition;

We can simply shift the point sprite along the ray from the vertex position to the camera position by changing the W coordinate. You can think of the W coordinate as a scaling factor. In world and view space, it usually is equal to 1. By setting it to 0.5, you would move the point twice as close to the camera (half the distance).

This is what it looks like with the above code active:

Nice result for such a cheap and neat little trick, eh?