How Is Point Size Controlled in GLSL?
The point size of rendered vertices in OpenGL is controlled using the
built-in vertex shader output variable gl_PointSize. When
rendering primitives using GL_POINTS, assigning a
floating-point value to gl_PointSize defines the diameter
of each point rasterized on screen in pixels. This article covers
enabling shader-controlled sizing in the OpenGL host application,
writing assignment logic in the vertex shader, calculating
distance-based attenuation for 3D scenes, and utilizing fragment
coordinates to shape point sprites.
Enabling Program Point Size in OpenGL
Before GLSL shaders can directly control point dimensions, the OpenGL context must be configured to respect shader-defined sizes rather than the fixed global state.
In standard OpenGL desktop profiles, you must call:
glEnable(GL_PROGRAM_POINT_SIZE);If this capability is not enabled, the GPU ignores assignments to
gl_PointSize in your shader and falls back to the default
size or the value set by glPointSize(size). In OpenGL ES
and WebGL contexts, gl_PointSize is enabled by default and
must be explicitly written in the vertex shader whenever rendering
GL_POINTS.
Basic Vertex Shader Implementation
In GLSL, gl_PointSize is a built-in output variable of
type float. You assign a pixel diameter directly within the
main() function of the vertex shader.
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
gl_PointSize = 20.0; // Sets the point size to 20x20 pixels
}When the rasterizer processes vertices emitted with
GL_POINTS, it generates a square raster grid centered on
the vertex position corresponding to the assigned pixel dimension.
Implementing Distance Attenuation
In 3D scenes, fixed pixel sizes make distant points appear
unnaturally large. To simulate perspective, you can dynamically scale
gl_PointSize based on the distance between the camera and
the vertex.
A standard linear attenuation model calculates the camera-space distance using the view matrix:
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform float uBaseSize;
void main()
{
vec4 viewPos = view * model * vec4(aPos, 1.0);
gl_Position = projection * viewPos;
// Calculate distance from the camera along the Z axis
float distance = length(viewPos.xyz);
// Attenuate size based on distance
gl_PointSize = uBaseSize / distance;
}This adjustment ensures that points scale realistically as they move toward or away from the camera, functioning effectively for particle systems, starfields, and point cloud visualizations.
Hardware Limits and Clamping
GPU drivers enforce minimum and maximum limits on point rasterization sizes. Assigning a value outside these bounds results in clamping by the hardware.
You can query the supported range on the host side using:
GLfloat range[2];
glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, range);
// range[0] contains the minimum size, range[1] contains the maximum sizeTo prevent unexpected rendering artifacts across different GPU vendors, clamp the computed size within GLSL:
gl_PointSize = clamp(calculatedSize, 1.0, 64.0);Styling Points with gl_PointCoord
Controlling gl_PointSize produces a square billboard
over the vertex. To render circular particles or textured sprites, GLSL
provides the gl_PointCoord input variable inside the
fragment shader, which provides normalized UV coordinates ranging from
\((0.0, 0.0)\) to \((1.0, 1.0)\) across the point quad.
#version 330 core
out vec4 FragColor;
void main()
{
// Calculate distance from the center of the point
vec2 coord = gl_PointCoord - vec2(0.5);
if (length(coord) > 0.5)
{
discard; // Discard fragments outside the circle
}
FragColor = vec4(1.0, 0.5, 0.2, 1.0);
}Combining vertex-stage gl_PointSize assignments with
fragment-stage coordinate masking enables lightweight, high-performance
rendering of complex 2D and 3D point effects.