Sage
Posts: 1,199
Joined: 2004.10
Post: #1
I've implemented billboarding in my vertex shader and it works.

Now here's the issue: I've been experimenting with individual per-particle rotation in my vertex shader. So each particle has a vertex attribute "Rotation" which is the amount to rotate that particle in radians. The effect looks awesome... smoke appears to sort of "billow" and flames "roil" and so on.

That being said, my implementation only sort-of works. The particles do rotate, but they lose billboarding as they rotate and the camera moves about.

To give you an idea, here are two screenshots. In the first, the camera's looking pretty much straight down +y ( I use z-up ) and the rotating particles look great:

In the second, I'm looking diagonally, and you can see some particles are sheared:

The approach I'm taking is to get "right" and "up" vectors from the modelview matrix and to then rotate them by the particle's rotation about the camera look vector. Then I do the standard pushing of gl_Vertex in the right direction for that vertex.

Note: If I remove the rotation math, or set the rotation to zero, it looks great.

Now, my guess is that one or both of two things could be happening:

1) My principle is fundamentally flawed! I should be doing it some non-boneheaded way. If this is the case, please let me know!

2) My math for making a matrix that rotates a vector about some other vector is flawed. This strikes me as being a likely culprit. I got this matrix after googling... I've never used it before, since I usually use quaternions for this kind of rotation.

Here's the relevant bits of my vertex shader. The fragment shader is irrelevant here.

Code:
```mat4 rotationAboutAxis( in vec3 axis, in float rads ) {     mat4 R;                  float s = sin( rads );     float c = cos( rads );     float rc = 1.0 - c;          // [column][row]     R[0][0]  = c + rc * axis.x;     R[0][1]  = rc * axis.x*axis.y + s*axis.z;     R[0][2]  = rc * axis.x*axis.z - s*axis.y;     R[0][3]  = 0.0;                  R[1][0]  = rc * axis.y*axis.x - s*axis.z;     R[1][1]  = c + rc * (axis.y * axis.y);     R[1][2]  = rc * axis.y*axis.z + s*axis.x;     R[1][3]  = 0.0;          R[2][0]  = rc * axis.z*axis.x + s*axis.y;     R[2][1]  = rc * axis.z*axis.z - s*axis.x;     R[2][2] = c + rc * (axis.z * axis.z);     R[2][3] = 0.0;          R[3][0] = 0.0;     R[3][1] = 0.0;     R[3][2] = 0.0;     R[3][3] = 1.0;          return R; } attribute float Rotation; attribute vec3 VertexSize;      void main() {       // snip: color, texture, other junk          //     // Get our billboard vectors from modelview, and then rotate them     //       vec4 right = vec4( gl_ModelViewMatrix[0][0],                        gl_ModelViewMatrix[1][0],                        gl_ModelViewMatrix[2][0],                        0.0 );     vec4 up    = vec4( gl_ModelViewMatrix[0][1],                        gl_ModelViewMatrix[1][1],                        gl_ModelViewMatrix[2][1],                        0.0 );     vec3 look  = vec3( gl_ModelViewMatrix[0][2],                        gl_ModelViewMatrix[1][2],                        gl_ModelViewMatrix[2][2] );              mat4 R = rotationAboutAxis( look, Rotation );     right = R * right;     up = R * up;                   //     // Construct vertex direction     //     vec3 vR = VertexSize.xxx * right.xyz;     vec3 vU = VertexSize.yyy * up.xyz;       vec4 dir = vec4( vR + vU, 0.0 );          gl_Position = gl_ModelViewProjectionMatrix * (gl_Vertex + dir );     }```

Any help is greatly appreciated. That being said, I'm going to see if implementing a quaternion in GLSL isn't too slow for real time stuff.
Sage
Posts: 1,199
Joined: 2004.10
Post: #2
OK, I fixed it by implementing it with a quaternion. Here's the new rotationAboutAxis() method:

Code:
```mat4 rotationAboutAxis( in vec3 axis, in float rads ) {     //     // Construct a quaternion (x,y,z,w), and then convert it to a matrix     // Note: We're not error checking for a non-normalized axis     //     rads = rads * 0.5;     float s = sin( rads );     float x = axis.x * s,           y = axis.y * s,           z = axis.z * s,           w = cos( rads );     //     // Construct the matrix     //     float x2,y2,z2,xx,yy,zz,xy,yz,xz,wx,wy,wz;     x2 = x + x;     y2 = y + y;     z2 = z + z;     xx = x * x2;     yy = y * y2;     zz = z * z2;     xy = x * y2;     yz = y * z2;     xz = z * x2;     wx = w * x2;     wy = w * y2;     wz = w * z2;          mat4 R;     // [column][row]     R[0][0] = 1.0 - (yy + zz);      R[1][0] = xy - wz;          R[2][0] = xz + wy;          R[3][0] = 0.0;     R[0][1] = xy + wz;              R[1][1] = 1.0 - (xx + zz);  R[2][1] = yz - wx;          R[3][1] = 0.0;     R[0][2] = xz - wy;              R[1][2] = yz + wx;          R[2][2] = 1.0 - (xx + yy);  R[3][2] = 0.0;     R[0][3] = 0.0;                  R[1][3] = 0.0;              R[2][3] = 0.0;              R[3][3] = 1.0;          return R; }```

It works! And I'm still getting 60fps.
Member
Posts: 114
Joined: 2005.03
Post: #3
Just a note: When doing something for which there is already an OpenGL function, then it's always a good idea to look at the code of Mesa3D. The license is liberal enough. By the way, their implementation of glRotatef is nearly identical to your solution.
Sage
Posts: 1,199
Joined: 2004.10
Post: #4
You know, that's funny. It's been so long since I used a direct call to glRotate that I forgot it takes an axis and produces a matrix representing a rotation about it. Also, I agree with using Mesa code for reference... I used it to figure out gluUnproject and friends a while back.