PDA

View Full Version : ArcBall Camera

TheTrueHelix
05-17-2016, 11:05 PM
Ok, I would like to welcome everyone ! I'm a new member on this OpenGL forum and I hope to find the knowledge I am searching for.

I currently working on an ArcBall camera but I can't seem to be able to make it work properly. It's been a while I can't find everyone who can help me. I don't only want the answers, I want the explanation if possible. So my goal is to make a Camera that rotates around its target. I'm using GLM and I'm trying to stop using it and building my own MATH class. I tried multiple ways of having my camera work.

Before the code, I need to explain that my function receive the Target (PointToFix) position, 2 Rotation Angle between 0 and 359 and the zoom level. Additionnaly, It receive the main and actual View Matrix which I overwrite at the end of the function since its by reference.

void t_Math::ChangeViewLookAt(glm::mat4& InMatrix, glm::vec3 InPointToFix, GLfloat InPitchValue, GLfloat InYawValue, GLfloat InScalingValue)
{
glm::vec3 cameraTarget = glm::vec3(InPointToFix.x, InPointToFix.y, InPointToFix.z);

const GLfloat x_Rotation = InPitchValue / 57.5829;
const GLfloat y_Rotation = InYawValue / 57.5829;
const GLfloat scaling = InScalingValue;

glm::vec4 tempoMatrix = InMatrix * glm::vec4(1,1,1,1);
tempoMatrix = glm::normalize(tempoMatrix);

tempoMatrix = glm::rotate(tempoMatrix, x_Rotation, glm::vec3(1.0, 0.0, 0.0));
tempoMatrix = glm::rotate(tempoMatrix, y_Rotation, glm::vec3(0.0, 1.0, 0.0));

glm::mat4 rot_mat = glm::rotate(glm::mat4(1.f), x_Rotation, glm::vec3(1,0,0));
rot_mat = glm::rotate(glm::mat4(1.f), y_Rotation, glm::vec3(0,1,0)) * rot_mat;

glm::vec4 cameraPosition = (tempoMatrix * glm::vec4(scaling,scaling,scaling,1));

InMatrix = glm::lookAt(glm::vec3(cameraPosition + (rot_mat * glm::vec4(cameraTarget - glm::vec3(cameraPosition),0.f))), cameraTarget, glm::vec3(0.0, 1.0, 0.0));
}

then someone told me that I didn't need to use the LookAt but instead just use a identity matrice and apply the rotation to get my View Matrice. What I understand is that, in my case, you cannot rotate the Entire View matrix because it countain more than just the position and so, I need to rotate the position before creating the view matrix.

So I ended up with this

void t_Math::ChangeViewLookAt(glm::mat4& InMatrix, glm::vec3 InPointToFix, GLfloat InPitchValue, GLfloat InYawValue, GLfloat InScalingValue)
{
const GLfloat x_Rotation = InPitchValue / 57.5829;
const GLfloat y_Rotation = InYawValue / 57.5829;

const glm::vec3 cameraTarget = glm::vec3(InPointToFix.x, InPointToFix.y, InPointToFix.z);
const glm::vec3 scaling = glm::vec3(InScalingValue, InScalingValue, InScalingValue);

glm::mat4 view = glm::mat4(1.0);

view = glm::scale(view, scaling);

view = glm::rotate(view, x_Rotation, glm::vec3(1.0, 0.0, 0.0));
view = glm::rotate(view, y_Rotation, glm::vec3(0.0, 1.0, 0.0));

view = glm::translate(view, cameraTarget);

InMatrix = view;
}

and right now, I have a smooth camera but it doesn't move. The camera itself rotate but it's not around a Target. I don't want the camera itself to rotate on-place but instead I want its position to change around at a static distance from a target creating an ArcBall Camera.

Thank you very much. I really need some answers. I have read a book "Anton's OpenGL Tutorial" and I don't really like the way he explains things. He doesn't explain much and he is using MakeFile and GLFW which doesn't explain how they work. Additionnaly, he doesn't explain but he just do which I don't find to help me. For example, he will show you code and tell you why it's better to do so but he doesn't explain why and what happen in the hardware itself.

Then I tried LearnOpenGL.com which is a nice website, except it goes too fast. Literally, you are there working with Matrice and next Tutorial you are creating a Phong Lighting. Do you guys have any tips on book I should buy ? A good one which explain everything. Even more, In both Anton's Book or LearnOpenGL.com, Camera are explained but they do not explain camera movement, they just show you how to make a Bird-Eye Camera (the one who is super free and can move around and rotate). Thank you, I'm starting to get deseperated. :sorrow:

BBeck1
05-18-2016, 06:36 AM
Unfortunately, I do not know of a good book that teaches exactly what you are trying to figure out here, especially for OGL.

I wrote an example program (http://virtuallyprogramming.com/XNATutorials/ThreeDTutorials/MatricesInMotion/MatricesInMotion.html) for XNA that does a 3rd person camera that circles the target without using LookAt, but I'm not sure that helps you in OGL. I'm actually hoping to switch to OGL soon as I stopped doing XNA years ago and want to teach OGL now. I've been working on rewriting my DirectX11 very basic "game engine" in OGL and I hope to do a tutorial on that once I get it finished and put it on my YouTube channel, VirtuallyProgramming.com.

Speaking of that, you might want to check out my vector and especially matrices videos (https://www.youtube.com/watch?v=56v9BgwSzsg). That's just math stuff and so it's not specific to OGL, XNA, or DX. I do go straight into HLSL and Phong shading after that though. LOL :whistle: But, hopefully I'll start a fairly basic OGL series of videos by this fall. (Possibly even in the next couple of months if the stars align just right.)

I've been pretty impressed with this book (http://www.amazon.com/Math-Primer-Graphics-Game-Development/dp/1568817231/ref=sr_1_2?s=books&ie=UTF8&qid=1463572448&sr=1-2&keywords=game+math+in+books) when it comes to game math. I haven't read it cover to cover, but I own it and have flipped through it a bit. I don't know that it covers exactly what you are doing here though.

So, what you're trying to do is not all that difficult after you've done it a couple times. There are several principles regarding matrices that you need to know.

First of all, I would use GLM. I'm going to myself and I am extremely comfortable with the math. There's nothing wrong with writing your own, but you need to understand the math first. If you want to write your own, what I would suggest is using both your own and GLM. Use GLM to make sure it works and that your logic is correct and then switch out the function/method with your own.

I did that with LookAt and built my own LookAt function a couple of years ago. I confirmed mine was correct by swapping it out with the built in LookAt function. The program worked exactly the same whether it used mine or theirs. And I did get a better understanding of the math and how it all works under the hood that way. Now I just use GLM. :)

So, first of all, with the View matrix. Yes, you could in theory start with an identity matrix, but you have to get the view matrix initially loaded. An identity matrix is an empty matrix and does not at all contain what should be inside a view matrix. My matrix video (https://www.youtube.com/watch?v=T7sb4yKKzFg) covers what should be in a view matrix. (Unless you are super solid on Vector algebra, watch the vector video first. I know it's a few hours, but - as biased as I am - I think it's worth the time.)

You need the look at function to initially build the view matrix. It needs to contain 3 mutually perpendicular vectors and a position value. I would probably initialize to an identity matrix and then use LookAt in my initialization code to set my starting view matrix. Now that I think about it, you could probably use an identity matrix if you take into account the inverse thing and are willing to start out with a zero'ed position and orientation.

Now what you often see is the view matrix rebuilt every frame using LookAt. I try to avoid that. After I build it once, I move it using matrix math.

The key trick here is that the view matrix is inversed. It's exactly like a world/object matrix of an object except it's inversed. (Also, scale probably doesn't make sense with a view matrix.) The whole point of the view matrix is it does the exact opposite of whatever you tell it to do. This gets into how a view matrix works and why which I cover in my video.

So, I use the inverse function to change it back to normal. Or I inverse any operation, such as a rotation I apply against it.

Get that working first before trying to make a chase camera. Make a first person camera that way, so that you know that you understand that part.

But you can use the rotation and translation functions to build rotation and translation matrices and then apply those to your view matrix to move your camera.

Just multiply the view matrix by a rotation matrix and it will rotate the view matrix, but you are probably going to have to use an inverse rotation matrix or inverse the view matrix, apply the rotation, than inverse it again. I would have to do it in order to tell you the exact math, but it is pretty straight forward if you understand the view matrix is an inversed world matrix for the camera and that it only works if it is inversed. And that means you have to take that into consideration any time you work with it directly.

(The reason it is inversed is that the camera never actually moves; it's job is to move the entire game world around the "camera" - a the origin - to trick the viewer into believing the camera moved. So, to rotate right you rotate the entire game world left. To move forward, you move the entire game world backwards. That is why it's inversed.)

The cheat is to build a brand new view matrix every frame with LookAt and then you don't need to know any of this. But once you stop doing that and just let the view matrix store the camera info from frame to frame, you need to know that it is inversed.

Also, the order of multiplication matters when working with matrices. MyMatrix = RotationMatrix * MyMatrix is totally different than MyMatrix = MyMatrix * RotationMatrix. One should rotate around the local axis and the other rotates around the global axis.

Also, when rotating or scaling, you usually have to move the object to the origin (world center), apply the math, then move it back. You just subtract the position of the object to bring it to the origin, apply the operation, then add the same position you subtracted to put it back where it was after rotating or scaling.

With a rotation, if you don't do this it will orbit the origin rather than rotating around it's own center.

And it turns out that is how you make a 3rd person orbiting camera: just don't move the camera to the origin when rotating it.

So, probably the final "secret" here is that you can link matrices together.

Maybe the most obvious way to see this is to do a chase camera. My game object has it's own world matrix, right. That world matrix has the position and orientation of my game object.

My camera has a view matrix, which is the same thing, but inverted.

Wait. Let me explain this using two world matrices before we confuse it by throwing in a view matrix.

So, you need to understand rigid animation.

When I make a car model, I am going to make the body of the car as one model/mesh and then make the four wheels as separate models/meshes. In Blender, I will parent them together. This tells my program when it imports the model that there is a parent child relationship between the meshes. My code needs to take that into account.

Each mesh has it's own world matrix allowing the wheels and car body to be controlled separately. The parent-child attachment/relationship is done by multiplying the wheel matrix by the car body matrix.

When you multiply/combine the wheel matrix with the body matrix, you will get the wheel position, orientation, (and scale) relative to the car body. So when drawing the car body you use it's world matrix. But when drawing the wheels, you use the wheel's matrix times the body matrix. When you do this, the wheel is relative to the car body. So, if you move the car body using its world matrix the wheels will remain attached. But when you move the wheels by changing the wheel matrix, it does not affect the car body. So you can rotate the wheels without affecting the car body. The "secret" is keeping the matrices separate except when you want the wheel position and orientation you use both the parent matrix and the child matrix by multiplying them together.

You can chain them as deep as you like. You can have grandchildren, great-grandchildren, or great-great-great-great-great-great-great-great-grandchildren. Each just gets its own matrix and you have to multiply them all together to find out how to place the great-great-great-great-great-great-great-great-grandchild into the scene.

You can apply this same concept to the view matrix to make a 3rd person chase camera that orbits the object. Just use the object's matrix and multiply the view matrix times that in order to make the view matrix a child of the parent object. Also, don't move the view matrix to the origin when rotating it so that it orbits the object rather than rotating on it's own axis.

Probably start by doing a view matrix on its own and get it to orbit the origin. (Remember you need at least 3 objects in your scene to see motion.) Once you get the view matrix camera to orbit the origin, you can than multiply it time the object's matrix to make it a child of the object.

You also may need to rotate the view matrix camera on it's own local axis to make it look at the object after the rotation. Using the LookAt function to rebuild it is the easy way to do that because you just always have it look at the (local) origin which as a child is actually the center of its parent. You can probably de-construct the view matrix to extract the position.

Or, you could do the math to make it look at the local origin which is a bit more complicated. This (http://www.euclideanspace.com/maths/algebra/vectors/lookat/) looks like it kind of gets into that, but I need to go get ready for work now.

TheTrueHelix
05-18-2016, 01:08 PM
Ok, I understand the part of the 3 perpendicular vector. Those are the Up, Right and Foward. I know how to calculate the

Forward Vector by using (Target Position - Camera Position)
Up Vector by using the cross product of the Foward and right.

What I'm having difficulty is understanding how to get the right vector .
Additionally, can the Up Vector be different from (0.0, 1.0, 0.0) ? I understand we are using 1Y because its still a vector but can It be someone like this (0.3, 0.3, 0.4) if the camera is oriented in an odd way ?

BBeck1
05-18-2016, 09:04 PM
It's been awhile since I coded a LookAt function. But as I recall, the position, target, and up vector are given. As you said, you can get the forward vector pretty easy. The up is a given. And in reality it should almost never be 0,1,0. What it means is the area "above" the camera, which would point to the ground if the camera is upside down.

We often use the cheat of assigning it 0,1,0 because that works until you start rolling the camera around like an airplane or space ship. For a first person camera where it never does a roll upside down, 0,1,0 is just as good as any accurate answer. It just works... until you roll upside down and then it comes apart. (The camera will make a weird flip that people mistake for gimbal lock when in reality they just lied about which direction is up - above the camera - and it finally caught up with them.)

I start the camera from a neutral position usually where 0,1,0 actually is up (over the camera) and then rotate from there so that I never again have to specify where up is because it's already part of the view matrix.

So, the input parameters include what's needed to calculate the forward vector and has the up vector. With those two vectors you can do a vector cross product ... or is it dot product... I always get the two confused. I think cross product will give you a vector that points straight out from the plane formed by two vectors. The two vectors live on their own unique plane. And the cross product will point directly away from that plane, or give you the direction the plane faces. In this case, the up vector and forward vector live on their own plane and their cross product will give you the right vector that points straight out of that plane, which is exactly what you want.

So, even if up isn't actually up, it still defines a plane between itself and the forward vector. And that plane will give you a perfect right vector for the forward vector.

If I recall correctly, you could do another dot product out of the plane formed by the right vector and forward vector to get an accurate and mutually perpendicular up vector.

So, yes the up vector should be pointing above the camera and almost never at 0,1,0 unless the camera is perfectly horizontal (which you can start from that position to make things easy). And also, it doesn't matter if it's right or not because you throw it away after calculating the right vector with the dot product and recalculate it as I recall. If you google for the code, you should see what I mean. The only time 0,1,0 is truly wrong for the "up" vector is when the camera rolls over and up should be down. Until that moment, it's close enough to calculate a valid right vector because it's really only used to define a plane to use to calculate the right vector. If it were truly pointing up, you could just use that for the up vector, I think, but I think they usually throw it way and recalculate it to insure that's it's perfectly mutually perpendicular. Any other vector that lives on that plane within 180 degrees should work. If it gets too far over though, up is pointing down. Then the right vector becomes a left vector and messes the whole thing up. But that's 180 degrees from where the forward vector points, so as the forward vector rotates, that cut off point changes as well. That's why I say that in theory it should be perpendicular to the forward vector and point to the area you want to be above the camera and never 0,1,0 unless the camera is perfectly horizontal. But it basically works pointed there, give or take about 90 degrees as long as it's on the same plane. You throw it away and recalculate it anyway.

Then the position offset is stored in the matrix as well. So, it's three mutually perpendicular vectors describing an orientation and an offset/position (which is why it's 4by4 instead of 3by3). I "think" the length of the three mutually perpendicular vectors defines the scale along that axis, but I hardly ever mess with scale preferring to scale all the models before importing them and always using a scale of 1,1,1.

Here (https://keithmaggio.wordpress.com/2011/01/19/math-magician-lookat-algorithm/)'s some code someone wrote of it. Notice there's two cross products.

Any time you see a cross product, you are seeing two vectors that are defining the plane they live on together and the result is a vector that tells you what direction to go to get our of that 2D plane. It's the direction that their plane faces. It's like going from 2D space to 3D space and the cross product provides the map.

Since the "up" vector is just the second vector with the forward vector defining that plane, 0,1,0 pretty much always works until you are supposed to be upside down. And the main reason it fails then is because it will give you a left vector instead of a right vector at that point and then when you calculate the actual up vector from there it's going to be wrong.

Where you really get in trouble with the up = 0,1,0 cheat is when you define your view matrix every frame using a LookAt function and store the camera facing separate from the view matrix so that you can construct a new view matrix every frame.

I wrote a space ship program and every time I rolled the ship it would freak out. Everyone was telling me it was gimbal lock, but I finally did the math on the LookAt function and realized it was that I was defining up as 0,1,0 even when the camera was supposed to be upside down.

TheTrueHelix
05-18-2016, 09:59 PM
Alright, I tried something.

I'm pretty happy to have made it this far. I understand abit more matrices and vector now.

In my video, my arcball is half working. Is my problem cause by "Gimbal Lock" ?

I really want to have an ArcBall working with WASD, and I want to be able to rotate around completly without being force onto the invinsible corner.

BBeck1
05-19-2016, 05:39 AM
Just looking at the video, it looks more like the LookAt problem where the up vector is invalid. Glancing at the code, both problems may be there.

The up vector problem happens when you build the view matrix each frame with an up vector that doesn't point above the camera. When you rotate the camera so that the area above the camera is down instead of up you get that sudden "snap" of the camera to some really odd place. Even 0.000000000000000000001 degrees down is no longer up. The nanosecond it goes there it violently flips the camera over. Everyone always assumes that's gimbal lock, and I myself thought it might be the first time I saw it until I worked out the up vector problem.

As long as the camera is horizontal and right-side up, hardcoding an up vector of 0,1,0 works. But even the smallest amount of going upside down breaks it. What appears to happen in the video is that it is horizontal at 0 degrees and then it gets to the top at 90 degrees and then it hits 90.0000000000000000000001 degrees and snap! It's upside down and that hard coded up vector turns it right side up in a single frame totally disorienting everything and violently flipping the camera in a totally new orientation.

Gimbal lock is much more subtle and gentle. There's no point where it snaps all of a sudden. It's more just you know something is off about the way the rotation controls are working. And eventually if you get the "gimbals" aligned just right you can completely lose an entire axis of rotation. But with gimbal lock you can adjust the code to set which of the three axes is the bad one. Even then, the camera will move oddly the second it gets off of it's best plane and starts to enter the plane of that bad axis.

I did a video (https://www.youtube.com/watch?v=4ebJXGlUDbA) on gimbal lock to demonstrate the problem with code examples. The code is in XNA, but it should apply anyway, because it's 90% math that is the same for any game programming environment. (Up until about 20:17 in the video I'm explaining the concept of gimbal lock, the history, and the code that calls the gimbal lock code. About 20:17 is where I start getting into the actual gimbal lock code. It's kind of a long video.)

The gimbal lock problem usually manifests because people are uncomfortable storing orientation data in matrices because matrices are new and pitch, yaw, and roll are familiar. By storing the data as pitch, yaw, and roll, they create gimbal lock. Pitch, yaw, and roll are not mutually exclusive and you cannot represent an orientation with a 3D vector. Three mutually perpendicular vectors inside a matrix = yes. One 3D vector, or three non-mutually perpendicular vectors = no. A quaternion = yes. But that's because a quaternion exists in 4D space. That 4th dimension allows even a quaternion by itself to store an orientation. (Quaternions cannot, however, store scale or position like a matrix can and your video card/shader needs you to feed it a matrix which is the reason I favor matrices over quaternions except where quaternions have some distinct advantage which is probably SLERP in skinned animations if ever. I've gotten 100% comfortable with quaternions and I rarely use them.)

But you can create gimbal lock with quaternions, matrices, or without them. It's all in how you store the data. If you try and store it as pitch, yaw, and roll a matrix or quaternion won't help you. The data actually has to be stored in the matrix or the quaternion. In other words, you have to store it there, not build the matrix or quaternion at the last minute. My video (https://www.youtube.com/watch?v=4ebJXGlUDbA) demonstrates the whole thing and explains the code/math.

BBeck1
05-22-2016, 12:09 PM
I ended up writing some code and implemented a fairly straight forward 3rd person chase camera in this (https://www.opengl.org/discussion_boards/showthread.php/198513-HELP-Calculating-position-of-a-third-person-camera?p=1282603#post1282603) thread. You might want to check it out.