Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Point sprite size attenuation with modern OpenGL

I'm trying to render some particles with OpenGL 3+ using point sprites. I've just realized that I have a major issue with the points. They increase in size automatically with respect to the camera distance. Resulting in the following:

Close to the particle emitter:

colse

And when far away, everything looks blurry and bloated:

far

In older versions it seems one could adjust the point size scales with glPointParameter. The function is still available in new 3+ OpenGL, but it only supports two parameters. GL_POINT_FADE_THRESHOLD_SIZE seemed like what I need, but I've tried it with no results. I'm also using glEnable(GL_PROGRAM_POINT_SIZE);

Any other way I could avoid this automatic scaling of the points based on camera distance? I would rather not have to change to code to use standard billboards made of triangles.

Not sure if relevant at the moment, but this is vertex shader I'm using:

layout(location = 0) in vec4 a_position_size; // Point size in w coord
layout(location = 1) in vec4 a_color;         // RGBA color modulated with texture

layout(location = 0) out vec4 v_color;

uniform mat4 u_MVPMatrix;

void main()
{
    gl_Position  = u_MVPMatrix * vec4(a_position_size.xyz, 1.0);
    gl_PointSize = a_position_size.w;
    v_color      = a_color;
}
like image 269
glampert Avatar asked Oct 15 '14 16:10

glampert


2 Answers

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;
}
like image 178
glampert Avatar answered Nov 09 '22 04:11

glampert


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).

like image 34
daign Avatar answered Nov 09 '22 05:11

daign