2
\$\begingroup\$

Behavior:

With the following texture

particle texture

I get a weird behavior when I try to rotate my particles in the direction they are moving. Essentially, the quad seems to rotate, but just barely so that when they bounce on the ground or when they spawn we can clearly see that they are not aligned upward when the particle is going upward.

better camera position weird rotation

Shader code and process explanation:

particle_billboard.geom:

//This geometry shader takes points as a 
//primitive input and return quads built with
//triangle strips

#version 430

layout (points) in;
layout (triangle_strip, max_vertices = 4) out;

layout (std140, binding = 0) uniform CameraInfo {
  mat4  ProjectionView; // Projection * View
  vec3  eye;    // camera position
};

uniform float particle_size;

in  vec4 ex_Color[];
in  vec3 ex_Direction[]; // Only one direction per particle so index is always direction[0]
out vec2 TexCoord;
out vec4 FragColor;

float angle_between_vector(vec3 v1, vec3 v2)
{
  return acos(
    dot(v1, v2)/ length(v1)*length(v2)
  );
}

vec3 project_vector_on_plane(vec3 v, vec3 normalized_plane_normal)
{
  return v - dot(v, normalized_plane_normal) * normalized_plane_normal;
}

vec4 quat_from_axis_angle(vec3 axis, float rad_angle)
{ 
  vec4 qr;
  float half_angle = 0.5*rad_angle;
  qr.x = axis.x * sin(half_angle);
  qr.y = axis.y * sin(half_angle);
  qr.z = axis.z * sin(half_angle);
  qr.w = cos(half_angle);
  return qr;
}

vec3 rotate_vertex_position(vec3 position, vec3 axis, float rad_angle)
{ 
  vec4 q = quat_from_axis_angle(axis, rad_angle);
  vec3 v = position.xyz;
  return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);
}

void main(void) {
  // Built-in variable "gl_in" of the geometry shader
  // gives us the index in the primitive. Points only 
  // have one index, so gl_in[0] it is.

  vec3 wPos = gl_in[0].gl_Position.xyz;
  vec3 wRotatedPos = wPos;
  vec3 originalPos = wPos;
  vec3 wPosToCamera = normalize(eye - wPos);
  vec3 wUp = vec3(0.0, 1.0, 0.0); 
  vec3 wRight = cross(wPosToCamera, wUp);

  FragColor = ex_Color[0]; // Points only have one vertex

  // 0.1 - Project ex_Direction on 

  // 1- Compute angle between right and direction
  float wDirectionAngle = angle_between_vector(project_vector_on_plane(ex_Direction[0], wPosToCamera), wRight);

  // Lower left corner
  wPos -= (wRight * particle_size/2);
  wPos.y -= particle_size/2;
  wRotatedPos = rotate_vertex_position(wPos - originalPos, wPosToCamera, wDirectionAngle) + originalPos;
  gl_Position = ProjectionView * vec4(wRotatedPos, 1.0);
  TexCoord = vec2(0.0, 0.0);
  EmitVertex();

  // Upper left corner
  wPos.y += particle_size;
  wRotatedPos = rotate_vertex_position(wPos - originalPos, wPosToCamera, wDirectionAngle) + originalPos;
  gl_Position = ProjectionView * vec4(wRotatedPos, 1.0);
  TexCoord = vec2(0.0, 1.0);
  EmitVertex();

  // lower right corner
  wPos.y -= particle_size;
  wPos += wRight * particle_size;
  wRotatedPos = rotate_vertex_position(wPos - originalPos, wPosToCamera, wDirectionAngle) + originalPos;
  gl_Position = ProjectionView * vec4(wRotatedPos, 1.0);
  TexCoord = vec2(1.0, 0.0);
  EmitVertex();

  // Upper right corner
  wPos.y += particle_size;
  wRotatedPos = rotate_vertex_position(wPos - originalPos, wPosToCamera, wDirectionAngle) + originalPos;
  gl_Position = ProjectionView * vec4(wRotatedPos, 1.0);
  TexCoord = vec2(1.0, 1.0);
  EmitVertex();

  EndPrimitive();
}

That is pretty much it for the code, I don't really do much else in the vertex and frag shaders... But I can provide them if necessary.

First of all, I draw the particles with quads that I build in the geometry shader from a single point. The quads are built with a right and up vector a little bit like this tutorial does, mainly for making the quads always face the camera.

Now, I visualize the texture as being aligned with the right vector, so I try to compute the angle (wDirectionAngle) between this vector and the direction vector of the particle projected on the particle quad. The particle quad being the plane with the normal in the same direction as the particle_position-to-camera-vector "wPosToCamera". Finally, I use the wPosToCamera as a quaternion vector to rotate my generated quads coordinate around the center of the quad. The rotation around the quaternion and the parameters I use are also heavily inspired on this tutorial.

I've debugged the direction angle and it appears to be correct, although I'm not 100% sure. What could possibly cause this to happen, is there a flaw in the logical process? I couldn't really get any inspiration of similar posts, since they are all about 2D.

EDIT:

tldr;

ErnieDingo's answer works wonderfully IMO. In my case, I had to swap the parameters for the cross product in order to see the quads. I also used as suggested the up vector coming from the camera and multiplied the vectors by a particle size factor registered as an uniform.

detailed edit

Some things I want to say for anyone coming accross this post.

1- Don't use vec3 in an uniform block which can only have std140 definition which rounds up its alignment to vec4 as mentioned here https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)

Warning: Implementations sometimes get the std140 layout wrong for vec3 components. You are advised to manually pad your structures/arrays out and avoid using vec3 at all. std430: This layout works like std140, except with a few optimizations in the alignment and strides for arrays and structs of scalars and vector elements. Specifically, they are no longer rounded up to a multiple of 16 bytes. So an array of floats will match with a C++ array of floats. Note that this layout can only be used with shader storage blocks, not uniform blocks.

With a workaround I was able to use the proper upvector in my shader.

2- My workflow was overcomplicated, in fact as mentioned by ErnieDingo the crossproduct of the particle direction and the camera direction is good enough for the quads to always face the camera.

3- Resulting gifs and code

final small

final big

//This geometry shader takes points as a 
//primitive input and return quads built with
//of triangle strips

#version 430

layout (points) in;
layout (triangle_strip, max_vertices = 4) out;

layout (std140, binding = 0) uniform CameraInfo {
  mat4  ProjectionView; // Projection * View
  vec3  eye;            // camera position
  vec3  target;
  vec3  up_vector;
};

uniform float particle_size;

in  vec4 ex_Color[];
in  vec3 ex_Direction[]; // Only one direction per particle so index is always direction[0]
out vec2 TexCoord;
out vec4 FragColor;

void main(void) {
  vec3 wPos = gl_in[0].gl_Position.xyz;
  vec3 wDirection = ex_Direction[0].xyz;
  vec3 wCameraPlaneNormal = normalize(eye - target);
  vec3 wUp = up_vector; 

  float dot = dot(wDirection, wCameraPlaneNormal);
  if (abs(dot) < 1.0f)
  {
    wUp = cross(wCameraPlaneNormal, wDirection);
  }

  wUp = normalize(wUp) * particle_size;
  wDirection *= particle_size;

  FragColor = ex_Color[0]; // Points only have one vertex
  // Lower left corner
  gl_Position = ProjectionView * vec4(wPos + wUp - wDirection, 1.0);
  TexCoord = vec2(0.0, 0.0);
  EmitVertex();

  // Upper left corner
  gl_Position = ProjectionView * vec4(wPos - wUp - wDirection, 1.0);
  TexCoord = vec2(0.0, 1.0);
  EmitVertex();

  // lower right corner
  gl_Position = ProjectionView * vec4(wPos + wUp + wDirection, 1.0);
  TexCoord = vec2(1.0, 0.0);
  EmitVertex();

  // Upper right corner
  gl_Position = ProjectionView * vec4(wPos - wUp + wDirection, 1.0);
  TexCoord = vec2(1.0, 1.0);
  EmitVertex();

  EndPrimitive();
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Holy moly I almost created a perfect gif. \$\endgroup\$ Feb 1, 2018 at 17:16
  • \$\begingroup\$ Looks good now! I've linked my game page and where you can download my game to see my various particle billboarding techniques. I haven't done ribbon, but that will be done...one day.... facebook.com/InsaneSoftware.com.au \$\endgroup\$
    – ErnieDingo
    Feb 1, 2018 at 20:33

1 Answer 1

2
+100
\$\begingroup\$

I've done something similar in my game. So forgive me if my explanation is hazy without diagrams. I take a point and create a quad which is aligned in the direction of the its travel.

But basically as you say, you want to orientate the particle pretty much to the best representation in the direction they are moving. I'm going off my recollections, I may miss a step, but hopefully this just gets your thinking about your solution.

I will list my assumptions here.

  • Firstly, you have the camera direction vector available.
  • Per particle, you have the direction vector.

So, in 3d world, you need to create a quad aligned along the plane of direction.
So, it can be considered in my example, the left and right of the quad. What you need then is an upvector (could be a downvector depending on cross, but we will use this name). Cross the camera direction Vector with the direction Vector or particle. This gives you a perpendicular vector for both planes, most importantly the direction vector of you particle.

You might need to play with the winding that I have done below.

Edit. Cameradirection is the particle position minus camera position other wise it will not orientate the particle correctly. Sorry!

the pseudo code.

direction.normalise()
cameradirection.normalise();

vector3 leftright = direction;

float dot = dot(direction, cameradirection);

vector3 updown = vector3(0,1,0); // just in case the particle is coming at the camera

if (abs(dot) < 1.0f)
{
   updown = cross(direction, cameradirection);
}
Updown.normalise();
// you should multiplye both vectors by 0.5f to ensure you dont have a double size particle.

quadtopleftcorner = position - updown - leftright;
quadtoprightcorner = position - updown + leftright;
quadbottomrighttcorner = position + updown + leftright;
quadbottomleftcorner = position + updown - leftright;

This is a variation on something I have done, I hope its a fit for your issue. Good luck.

edit: This is all in world space, so mult by your WVp matrix also.

\$\endgroup\$
4
  • \$\begingroup\$ forgot to add, that by rights the up vector should be the upvector of the camera, which you can derive from your camera matrix. If you need help, let me know. Should use that rather than 0,1,0 vector. \$\endgroup\$
    – ErnieDingo
    Feb 1, 2018 at 0:22
  • 1
    \$\begingroup\$ Your answer made me realize so much things I was doing wrong, I'll try to recap everything in an edit to the post. I'd double upvote if possible but sadly now that my rep is gone I can't upvote :(. Never realized that just the cross product with the direction and the cameradirection would be good enough for that. Much more efficient also than what I was doing! I thought that the quads still had to face the camera all the time, hence why I came up with an overcomplicated workflow. Do you know if this could also apply to quad strips orientations when creating ribbons/particle trails? \$\endgroup\$ Feb 1, 2018 at 16:35
  • 1
    \$\begingroup\$ Also for some odd reasons I have to wait 5 hours before awarding the bounty. \$\endgroup\$ Feb 1, 2018 at 16:35
  • \$\begingroup\$ There is definitely ways of doing strips. The best way though i could think of though does require you to embed the next vertice in your chain with the current one. The reason is that strips have elbows or bend points. So you should average the up vector of both to get a smoother angle. On my list of to do things for strips. I also have done the matrix version of this as well. It's better for point effects but i build the geometry in a reference plane and orientate them to the camera using the same matrix. Benefit of this is you can randomise upvector to differentiate the same frame. \$\endgroup\$
    – ErnieDingo
    Feb 1, 2018 at 19:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .