Point sprite size attenuation with modern OpenGL

So it turns out my problem was due to my misunderstanding of gl_PointSize. As was noted in the comments and clearly stated in the documentation, gl_PointSize contains size of rasterized points, in pixels. Hence the point sprites look larger once we move away from them, but not because they are being scaled, but because they still occupy the same screen space while the rest of the 3D scene is being scaled-down according to the perspective projection.

I fixed the problem with a few adjustments to the vertex shader to actually scale the point size according to the distance from the viewer:

uniform mat4 u_MVPMatrix;
uniform vec3 u_cameraPos;

// Constants (tweakable):
const float minPointScale = 0.1;
const float maxPointScale = 0.7;
const float maxDistance   = 100.0;

void main()
{
    // Calculate point scale based on distance from the viewer
    // to compensate for the fact that gl_PointSize is the point
    // size in rasterized points / pixels.
    float cameraDist = distance(a_position_size.xyz, u_cameraPos);
    float pointScale = 1.0 - (cameraDist / maxDistance);
    pointScale = max(pointScale, minPointScale);
    pointScale = min(pointScale, maxPointScale);

    // Set GL globals and forward the color:
    gl_Position  = u_MVPMatrix * vec4(a_position_size.xyz, 1.0);
    gl_PointSize = a_position_size.w * pointScale;
    v_color      = a_color;
}

Had the same problem while using GLSL with three.js. Solved it based on glampert's answer, but first of all three.js requires usage of certain predefined variable names:

uniform vec4 origin;

void main() {
    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
    float cameraDist = distance( mvPosition, origin );
    gl_PointSize = 200.0 / cameraDist;
    gl_Position = projectionMatrix * mvPosition;
}

Secondly note that the modelViewMatrix is applied first to the particle position and then the distance is calculated to that position. Because if you apply transformations to the particle system object your particles lie in object coordinates that are not equal to global coordinates. So I calculate the distance in view coordinates (where the camera is always in the origin).