How Does gl_PointCoord Map Textures in GLSL?

In OpenGL and WebGL, gl_PointCoord is a built-in fragment shader variable that provides two-dimensional texture coordinates across the surface of an individual point primitive rendered as a point sprite. When rendering large volumes of particles or billboards, passing single vertices configured with gl_PointSize allows the graphics pipeline to expand each point into a screen-aligned square during rasterization. This article explores how gl_PointCoord operates within this rasterized space, how to use it to sample 2D textures directly onto points, and how it enables procedural shaping and masking for particle systems.

Understanding Point Sprites and Rasterization

Point sprites represent a lightweight alternative to traditional quad meshes when rendering hundreds or thousands of uniform visual elements like sparks, dust, or stars. Instead of submitting four vertices and two triangles per particle, the application submits a single vertex. In the vertex shader, assigning a value to the built-in variable gl_PointSize defines the width and height of the quad (in pixels) that the GPU will generate on the screen around that vertex.

During rasterization, the GPU breaks down each square point into individual fragments. Because a point originates from a single vertex, standard interpolated vertex attributes (like custom UV coordinates) cannot provide a gradient across the point’s generated area. gl_PointCoord solves this by automatically generating a local coordinate for every fragment inside the point square.

The Coordinate Space of gl_PointCoord

The value of gl_PointCoord is a vec2 that ranges from 0.0 to 1.0 along both the X and Y axes.

Because the vertical axis starts at 0.0 at the top and increases downward to 1.0, the coordinate system matches standard image file row storage and window coordinate conventions rather than the standard Cartesian coordinate system (where Y increases upward).

Sampling Textures with gl_PointCoord

To apply an image or sprite texture to each point, pass gl_PointCoord as the UV coordinate parameter to texture sampling functions in the fragment shader.

#version 300 es
precision mediump float;

uniform sampler2D u_particleTexture;
out vec4 fragColor;

void main() {
    // Sample the texture using the generated point coordinates
    vec4 texColor = texture(u_particleTexture, gl_PointCoord);
    
    // Discard transparent fragments to prevent depth write issues
    if (texColor.a < 0.1) {
        discard;
    }
    
    fragColor = texColor;
}

In this setup, each generated point sprite displays the full texture mapped across its rasterized square, regardless of the screen size determined by gl_PointSize.

Procedural Masking and Circular Points

In addition to texture sampling, gl_PointCoord is widely used to create procedural shapes directly in the shader without loading image files. For example, rendering smooth, circular particles requires masking out fragments outside a specific radius from the center.

#version 300 es
precision mediump float;

uniform vec4 u_color;
out vec4 fragColor;

void main() {
    // Calculate distance from the center (0.5, 0.5)
    vec2 centerOffset = gl_PointCoord - vec2(0.5);
    float dist = length(centerOffset);
    
    // Discard fragments outside the circle radius (0.5)
    if (dist > 0.5) {
        discard;
    }
    
    // Optional: anti-alias the edge using smoothstep
    float alpha = smoothstep(0.5, 0.45, dist);
    
    fragColor = vec4(u_color.rgb, u_color.a * alpha);
}

Key Considerations and Limitations

While gl_PointCoord offers high rendering performance, there are specific behaviors to keep in mind: