How to prevent floating point errors in camera vectors
On a different question, the user that answered (Ricky) me also said the following (which I still find a little confusing):
When I say they must remain perpendicular, I only mean that if you rotate vectors independently by the same transformation over and over, floating point errors may creep in and they could diverge from being perpendicular. Dot(x,y), dot(y,z) and dot(x,z) should be very close to zero. If not, you need to do some cross products and re-perpendicularize them. It's as simple as overwriting z=cross(x,y), then y=cross(z,x).
There are 3 relevant vectors on my camera system, these vectors are floating point numbers and always normalized. Reference vector points in the direction the camera is looking at, Up开发者_JAVA技巧Vector and RightVector are self-explanatory.
Anyone knowing how to answer, should answer of course, but Ricky, if you're there, please help me out...
1) What exactly does it mean dot(x,y), dot(y,z) and dot(x,z)? Is this the dot product between these vectors? I suppose... But which ones (x, y, z) correspond to my Reference, UpVector and RightVector? I'm a little confused...
2) Then, these dot products should be very close to zero, how exactly should I check this? I've seen code like this to achieve the same thing in a similar context:
const float Math::EPSILON = 1e-6f;
Math::closeEnough(<floating_point_variable_to_check>, 0.0f);
static bool closeEnough(float f1, float f2) {
// Determines whether the two floating-point values f1 and f2 are
// close enough together that they can be considered equal.
return fabsf((f1 - f2) / ((f2 == 0.0f) ? 1.0f : f2)) < EPSILON;
}
I suppose it's good enough?
3) Where I exactly should I perform all these checks and re-perpendicularize the vectors? I have 3 functions that rotate the camera, Pitch, Yaw and Roll (the last one I don't use it that much), only these functions change those unit vectors. Should I perform the checks and fixes every time I call one of these rotate functions or would that be too much? Where and when then?
4) Last but not least, to fix them I need to overwrite the vectors with the cross product, makes sense as I want them perpendicular to each other. But isn't the quote above missing one cross product? And does the order i which I perform those fixes matter?
Hopefully the two current answers are adequate, so I'll just add on a theoretical note that the reason this arises and the reason it's not too important how you handle it is that the nine numbers that form a 3x3 rotation matrix and describe how your camera is oriented are more than necessary.
A rotation matrix is just a matrix with the three vectors (e.g. up, right, forward) as the three columns. Consider a matrix with three vectors e1=right, e2=up, e3=backward. Then to transform a point into the same reference frame as the matrix, you premultiply the column vector by
[ e1_x e2_x e3_x ]
[ e1_y e2_y e3_y ]
[ e1_z e2_z e3_z ]
(Or the inverse... I always get the language confused depending on how you think about it. Also, OpenGL uses 4x4 matrices as a trick to apply translations or even perspective with just one matrix.) Anyway, you can get any of the three vectors by crossing two others. i.e. e1 = e2 x e3
, e2 = e3 x e1
, and e3 = e1 x e2
That means one vector is unnecessary. One less vector leaves six numbers. The length of the two remaining vectors is irrelevant —— only the direction is important -- which leaves four numbers required to describe the transformation. Enter quaternions. They're a mathematical tool that happen to deal with the remaining four numbers in a stable, robust way. If you use all nine numbers though, you just need to make sure that the vectors always have length 1 and that any one is perpendicular to the other two. Otherwise it's not really a rotation matrix. Up might not be 90 degrees from right. That's why I suggested:
e1 = Normalize(e1);
e3 = Normalize(Cross(e1,e2)); // e3 is now perpendicular to e1 and e2, and
// probably hasn't changed much. It will also
// have length 1.
e2 = Normalize(Cross(e3,e1)); // e2 was perpendicular to e3 from the previous
// step, but now it's also perpendicular to e1.
You can do this in any order or combination as long as it accomplishes the same effect. Every iteration or every thousand. If you neglect it completely there's a small chance that your camera will start to distort the image. After a few camera rotations, only the last couple digits will be modified, so think of it as a fudge factor to keep things from drifting over long periods of time.
1) Dot(x,z) means the dot-product between (for example) your reference-vector and your side-vector. Since these vectors should all be perpendicular, the dot-product of any two of them should be zero.
2) Since all your vectors are (theoretically) normalized, you should simply be able to test whether fabs( Dot(a,b) ) < EPSILON.
3) Frankly, if there's only one camera, I'd just renormalize them every frame - why not?
Here's one approach:
Basically, the forward vector is the only one you really care about, and if you know that your camera is never going to be upside-down, you can derive the other two vectors each frame.
Take the cross-product of the forward (reference) vector with an up-vector (0,1,0). This gives you a side-vector that's guaranteed perpendicular to the forward vector. Now take the cross-product of this new vector with the forward vector. This gives you the "real" camera's up-vector - ie. it will be pitched forward by whatever pitch angle the camera is at.
Yes, the vector dot product between your vectors. x . y = 0 if x and y are perpendicular. It doesn't matter which pairs you pick since each needs to be perpendicular to the other two.
Yep, that's the way
Wherever you like really. Once per frame before rendering it is a good spot. The idea is just to keep cumulative errors from getting out of hand.
No, you just need to fix up two of the vectors in relation to the other one (and each other), so only two vectors need to be changed. Whether you pick the forwards vector or the up vector or even the right vector is your choice. Choosing "up" makes sense for cases where you always want the camera upright. Application order does not matter, since the corrections should be tiny.
精彩评论