Quaternion based camera

I am trying to implement a FPS camera based on quaternion math. I save the rotation quaternion variable called _quat and multiply it by another quaternion when necessary. Here is the code:

 void Camera::SetOrientation(float rightAngle, float upAngle)//in degrees { glm::quat q = glm::angleAxis(glm::radians(-upAngle), glm::vec3(1,0,0)); q*= glm::angleAxis(glm::radians(rightAngle), glm::vec3(0,1,0)); _quat = q; } void Camera::OffsetOrientation(float rightAngle, float upAngle)//in degrees { glm::quat q = glm::angleAxis(glm::radians(-upAngle), glm::vec3(1,0,0)); q*= glm::angleAxis(glm::radians(rightAngle), glm::vec3(0,1,0)); _quat *= q; } 

An application can request an orientation matrix through GetOrientation , which simply translates the quaternion into a matrix.

 glm::mat4 Camera::GetOrientation() const { return glm::mat4_cast(_quat); } 

The application changes orientation as follows:

 int diffX = ...;//some computations based on mouse movement int diffY = ...; camera.OffsetOrientation(g_mouseSensitivity * diffX, g_mouseSensitivity * diffY); 

This results in poor, mixed rotations around almost all axes. What am I doing wrong?

+6
source share
5 answers

The problem is how you accumulate rotation. That would be the same if you used quaternions or matrices. The combination of rotation representing pitch and yaw with another will result in a roll.

In contrast, the easiest way to implement an FPS camera is to simply copy the changes to the title and step, and then convert to a square (or matrix) when you need to. I would change the methods in the camera class:

 void Camera::SetOrientation(float rightAngle, float upAngle)//in degrees { _rightAngle = rightAngle; _upAngle = upAngle; } void Camera::OffsetOrientation(float rightAngle, float upAngle)//in degrees { _rightAngle += rightAngle; _upAngle += upAngle; } glm::mat4 Camera::GetOrientation() const { glm::quat q = glm::angleAxis(glm::radians(-_upAngle), glm::vec3(1,0,0)); q*= glm::angleAxis(glm::radians(_rightAngle), glm::vec3(0,1,0)); return glm::mat4_cast(q); } 
+3
source

Problem

As GuyRT has already pointed out, the way you do accumulation is not very good. Theoretically, this will work that way. However, floating point math is far from perfectly accurate, and errors accumulate more operations that you do. The combination of two quaternion rotations - 28 operations compared to one operation that adds value to the corner (plus, each of the operations in multiplying quaternions affects the resulting rotation in three-dimensional space in a very non-obvious way). In addition, the quaternions used for rotation are quite reasonable for normalization, and their rotation de-normalizes them a little (rotating them many times de-normalizes them a lot, and their rotation with another, already de-normalized quaternion enhances the effect).

Reflection

Why do we use quaternions in the first place?

Quaternions are commonly used for the following reasons:

  • Avoiding a dangerous cardan lock (although many people do not understand the issue, replacing three angles with three quaternions, does not magically eliminate the fact that one combines three rotations around unit vectors - quaternions should be used correctly to avoid this problem)
  • An effective combination of many turns, such as skinning (28 operations versus 45 operations using matrices), preservation of ALU.
  • Fewer values ​​(and therefore fewer degrees of freedom), fewer ops, therefore fewer opportunities for unwanted effects compared to using matrices when combining many transformations.
  • Less values ​​to load, for example, if the skin model has a couple of hundred bones or when drawing ten thousand instances of the object. Smaller vertex flows or uniform blocks.
  • Quaternions are cool, and people using them are cool.

None of them really affect your problem.

Decision

Accumulate two rotations in the form of angles (usually undesirable, but quite acceptable for this case) and create a rotation matrix when you need it. This can be done either by combining the two quaternions, or transforming it into a matrix, as in GuyRT's answer, or by directly creating a rotation matrix (which is probably more efficient, and all that OpenGL wants to see is one matrix).

As far as I know, glm::rotate only rotates around an arbitrary axis. Of course you can use (but then you would rather combine the two quaternions!). Fortunately, the formula for the matrix, which combines rotations around x, then y, then z, is well known and simple, you will find it, for example, in the second paragraph (3) here .
You do not want to rotate around z, therefore cos(gamma) = 1 and sin(gamma) = 0 , which greatly simplifies the formula (write it on a piece of paper).

Using rotation angles is what makes many people scream at you (often not completely undeservedly).
A cleaner alternative tracks the direction you are looking, either with a vector pointing away from your eye in the direction you want to look, or by remembering the point in the space you are looking at (this is what works well with physics in third party game too). To do this, you also need an up vector if you want to allow arbitrary rotations - since then up is not always a world up space - so you may need two vectors. It is much nicer and more flexible, but also more difficult. For the desired FPS in your example, where your only options are to look left-right and up-down, I believe that the rotation angles - only for the camera - are quite acceptable.

+6
source

I have not used GLM, so you may not like this answer. However, performing a quaternion rotation is not bad.

Say your camera has the initial saved orientation of 'vecOriginalDirection' (normalized vec3). Let's say you want it to follow another "vecDirection" (also normalized). Thus, we can adapt an approach similar to a trackball and consider vecDirection as a deviation from what is the default focus of the camera.

Usually the preferred way to rotate a quaternion in the real world is to use NLERP. Let's see if I remember: in the pseudocode (assuming floating point), I think this is:

 quat = normalize([ cross(vecDirection, vecOriginalDirection), 1. + dot(vecDirection, vecOriginalDirection)]); 

(Don’t forget that β€œ1. +”, I forgot why this happened, but it made sense at the same time. I think I pulled my hair out for a few days until I found it. It's basically a block quaternion, IIRC, which is averaged, thereby making the double-angle action similar to a corner ... maybe :))

The renormalization shown above as "normalize ()" is significant (this is "N" in NLERP). Of course, the normalization of quat (x, y, z, w) is true:

 quat /= sqrt(x*x+y*y+z*z+w*w); 

Then, if you want to use your own function to create a 3x3 orientation matrix from quat:

 xx=2.*x*x, yy=2.*y*y, zz=2.*z*z, xy=2.*x*y, xz=2.*x*z, yz=2.*y*z, wx=2.*w*x, wy=2.*w*y, wz=2.*w*z; m[0]=1.-(yy+zz); m[1]=xy+wz; m[2]=xz-wy; m[3]=xy-wz; m[4]=1.-(xx+zz); m[5]=yz+wx; m[6]=xz+wy; m[7]=yz-wx; m[8]=1.-(xx+yy); 

To actually implement the trackball, you will need to calculate vecDirection when the finger is held, and save it on vecOriginalDirection when it is first pressed (subject to the touch interface).

You will also probably want to calculate these values ​​based on the piecewise function of the hemisphere / hyperboloid, if you have not already done so. I think that @minorlogic was trying to save some time, since it looks like you could just use a virtual trackball with a drop down list.

+2
source

Rotation of the rotation angle must be pre-multiplied, multiplication by the send will rotate the world around the origin through (1,0,0), multiplication will rotate the camera.

 glm::quat q_up = glm::angleAxis(glm::radians(-upAngle), glm::vec3(1,0,0)); q_right = glm::angleAxis(glm::radians(rightAngle), glm::vec3(0,1,0)); _quat *= q_right; _quat = q_up * _quat; 
0
source

The best way to start with mouse control is to see Arcball. Trackball manages implementations.

e.g. tutorial http://subokita.com/2014/03/12/trackball-arcball-in-opengl-4/

-2
source

Source: https://habr.com/ru/post/982529/


All Articles