glm::lookat, perspective clarification in OpenGL - c++

All yalls,
I set up my camera eye on the positive z axis (0, 0, 10), up pointing towards positive y (0, 1, 0), and center towards positive x (2, 0, 0). If, y is up, and the camera is staring down the negative z axis, then x points left in screen coordinates, in right-handed OpenGL world coordinates.
I also have an object centered at the world origin. As the camera looks more to the left (positive x direction), I would expect my origin-centered object to move right in the resulting screen projection. But I see the opposite is the case.
Am I lacking a fundamental understanding? If so, what? If not, can anyone explain how to properly use glm to generate view and projection matrices, in the default OpenGL right-handed world model, which are sent to shaders?
glm::vec3 _eye(0, 0, 10), _center(2, 0, 0), _up(0, 1, 0);
viewMatrix = glm::lookAt(_eye, _center, _up);
projectionMatrix = glm::perspective(glm::radians(45), 6./8., 0.1, 200.);
Another thing I find interesting is the red line in the image points in the positive x-direction. It literally is the [eye -> (forward + eye)] vector of another camera in the scene, which I extract from the inverse of the viewMatrix. What melts my brain about this is, when I use that camera's VP matrices, it points in the direction opposite to the same forward direction that was extracted from the inverse of the viewMatrix. I'd really appreciate any insight into this discrepancy as well.
Also worth noting: I built glm 0.9.9 via cmake. And I verified it uses the right-hand, [-1, 1] variants of lookat and perspective.
resulting image:

I would expect my origin-centered object to move right in the resulting screen projection. But I see the opposite is the case.
glm::lookAt defines a view matrix. The parameters of glm::lookAt are in world space and the center parameter of glm::lookAt defines the position where you look at.
The view space is the local system which is defined by the point of view onto the scene.
The position of the view, the line of sight and the upwards direction of the view, define a coordinate system relative to the world coordinate system. The view matrix transforms from the wolrd space to the view (eye) space.
If the coordiante system of the view space is a Right-handed system, then the X-axis points to the left, the Y-axis up and the Z-axis out of the view (Note in a right hand system the Z-Axis is the cross product of the X-Axis and the Y-Axis).
The line of sight is the vector form the eye position to the center positon:
eye = (0, 0, 10)
center = (2, 0, 0)
up = (0, 1, 0)
los = center - eye = (2, 0, -10)
In this case, if the center of the object is at (0, 0, 0) and you look at (0, 0, 2), the you look at a position at the right of the object, this means that the object is shifted to the left.
This will change, if you change the point of view e.g. (0, 0, -10) or flip the up vector e.g. (0, -1, 0).

Related

Perspective view matrix for Y-down coordinate system

I have this little toy graphics library in rust I tinker with to learn OpenGL. I initially intended it to do only low-res 2D stuff on it, so I set up the coordinate system for pixel-space orthographic rendering, a system with (0, 0) at the top-left corner of the screen that allows sprites to be "snapped" to pixels for an 8/16-bit style.
Now I'm thinking it would be fun to add a perspective camera to render the sprites in perspective as a sort of paper-doll theater thing, sort of inverse 2.5D, world is 3D but characters are 2D.
So I use the excellent tutorial here:
https://learnopengl.com/Getting-started/Camera
Great stuff, but the algorithm calls for deriving from the up-vector [0, 1, 0], so my world renders upside down (since my "up" is [0, -1, 0]). If I do set the "up" as [0, -1, 0] I instead get the X-axis extending from right to left, as if negating "up" rotates 180° around the Z-axis, which again isn't what I want.
Is it at all possible to do a perspective camera view matrix with a typical orthographic world Y-down coordinate system, and how would that be done practically using e.g. glm?
The trick with the reflection matrix works; The orientation and position of all objects on screen works out and camera movement produces the expected results.
This page is what tipped me off on the fact that the only difference between left and right-hand view matrixes is the direction of the X-axis, and that mirroring/reflections changes the handedness:
https://www.realtimerendering.com/blog/left-handed-vs-right-handed-viewing/
That should make sense, if you compare your hands they only differ in what side the thumb is on, not in what direction the fingers are pointing.
The solution:
Create a standard right-handed view matrix, but define the up-vector as [0, -1, 0]
Create a reflection matrix that flips the view around the Y-axis:
[-1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0
0, 0, 0, 1]
multiply the reflection and view matrix:
reflection_matrix * view_matrix;
This works in OpenGL, but allegedly DirectX works with row-major matrices, so you have to take that into account when multiplying.
This is the code I use to produce the view matrix:
let eye: glm::Vec3 = glm::vec3(160.0, 90.0, 300.0);
let target: glm::Vec3 = glm::vec3(160.0, 90.0, 0.0);
let cam_forward = glm::normalize(&(&eye - &target));
let up = glm::Vec3::new(0.0, -1.0, 0.0);
let cam_right = glm::normalize(&glm::cross(&up, &cam_forward));
let cam_up = glm::normalize(&glm::cross(&cam_forward, &cam_right));
let mut mx_view = glm::look_at(&eye, &target, &cam_up);
let mut mirr: glm::Mat4 = glm::Mat4::identity();
mirr[0] = -1.0;
mx_view = mirr * mx_view;
The camera looks down the Z-axis of my 320 by 180 pixel world.
This is of-course in rust, but, based on the excellent nalgebra-glm crate which pretty closely replicates what glm does for C++, so I think anyone using C++ should also be able to follow.
I do not recommend changing the camera matrix.
Instead, try to use glClipControl(GL_UPPRER_LEFT,GL_ZERO_TO_ONE); in this situation, the front face will be CW.
Or simply multiply the projection matrix P[1][1] by -1.

About view matrix and frustum culling

I was trying to determine if an object (sphere) is inside a view frustum. My strategy was first to get the view matrix:
glm::lookAt(cameraPosition, lookAtPosition, cameraUp);
Where cameraPosition is the position of the camera, lookAtPosition is calculate by cameraPosition + cameraDirection and cameraUp is pretty much self-explanatory.
After obtaining the view matrix, I calculate the 8 vertices of the frustum in the camera view, then multiply the inverse of the view matrix by each point to get the coordinate of them in the world space.
Using these 8 points in the world space, I can construct the 6 planes by using cross products (so their normals will point inward). Then I take the dot product of the object vector (I get the vector by subtracting the object position with an arbitrary point on that plane) with the normal vector of each plane, if it is positive for all 6, I know it's inside the frustum.
So my questions are:
Is the camera view is always set at (0,0,0) and looking at the positive z direction?
The view matrix transform the world view into the camera view, if I use the inverse of it to transform the camera view to the world view, is it correct?
My frustum is opposite with my cameraDirection which causes nothing to be seen on the screen (but it still drawing stuff in the back of my camera).
Is the camera view is always set at (0,0,0) and looking at the positive z direction?
The view space is the space which defines the look at the scene.
Which part is "seen" (is projected onto the viewport) is depends on the projection matrix. Generally (in OpenGL) the origin is (0,0,0) and the view space z axis points out of the viewport. However, the projection matrix inverts the z axis. It turns from a the Right-handed system of the view space to the left handed system of the normalized device space.
the inverse of it to transform the camera view to the world view
Yes.
The view matrix transforms from world space to view space. The projection matrix transform the Cartesian coordinates of the view space to the Homogeneous coordinates of the clipspace. Clipspace coordinates are transformed to normalized device space by dividing the xyz components by the w component (Perspective divide).
The normalized device space is a cube with a minimum of (-1, -1, -1) and a maximum of (1, 1, 1). So the 8 corner points of the cube are the corner of the viewing volume in normalized device space.
(-1, -1, -1) ( 1, -1, -1) ( 1, 1, -1) (-1, 1, -1) // near plane
(-1, -1, 1) ( 1, -1, 1) ( 1, 1, 1) (-1, 1, 1) // far plane
If you want to transform a point from normalized device space to view space, you've to:
transform by the inverse projection matrix
transform by the inverse view matrix
divide the xyz component of the result by its w component
glm::mat4 view; // view matrix
glm::mat4 projetion; // projection matrix
glm::vec3 p_ndc; // a point in normalized device space
glm::mat4 inv_projetion = glm::inverse( projetion );
glm::mat4 inv_view = glm::inverse( view );
glm::vec4 pth = inv_view * inv_projetion * glm::vec4( p_ndc, 1.0f );
glm::vec3 pt_world = glm::vec3( pth ) / pth.w;

Display recursively rendered scene into a plane

I have to render 2 scenes separately and embed one of them into another scene as a plane. The sub scene that is rendered as a plane will use a view matrix calculated from relative camera position and perspective matrix considering distance and calculated skew to render sub scene as if that scene is placed actually on the point.
For describing more detail, this is a figure to describe the simpler case.
(In this case, we have the sub scene on the center line of the main frustum)
It is easy to calculate perspective matrix visualized as red frustum by using these parameters.
However, it is very difficult for me to solve the other case. If there were the sub scene outside of the center line, I should skew the projection matrix to correspond with scene outside.
I think this is kind of oblique perspective projection. And also this is very similar to render mirror. How do I calculate this perspective matrix?
As #Rabbid76 already pointed out this is just a standard asymmetric frustum. For that, you just need to know the coordinates of the rectangle on the near plane you are going to use, in eye-space.
However, there is also another option: You can also modify the existing projection matrix. That approach will be easier if you know the position of your rectangle in window coordinates or normalized devices coordinates. You can simply pre-multiply scale and translation matrices to select any sub-region of your original frustum.
Let's assume that your viewport is w * h pixels wide, and starts at (0,0) in the window. And you want to create a frustum which just renders a sub-rectangle which starts at the lower left corner of pixel (x,y), and which is a pixels wide and b pixels tall.
Convert to NDC:
x_ndc = (x / w) * 2 - 1 and y_ndc = (y / h) * 2 - 1
a_ndc = (a / w) * 2 and b_ndc = (b / h) * 2
Create a scale and translation transform which maps the range [x_ndc, x_ndc+a_ndc] to [-1,1], and similiar for y:
( 2/a_ndc 0 0 -2*x_ndc/a_ndc-1 )
M = ( 0 2/b_ndc 0 -2*y_ndc/b_ndc-1 )
( 0 0 1 0 )
( 0 0 0 1 )
(note that the factor 2 is going to be cancled out. Instead of going to [-1,1] NDC space in step 1, we could also just have used the normalized [0,1], I just wanted to use the standard spaces.)
Pre-Multiply M to the original projection matrix P:
P' = M * P
Note that even though we defined the transformation in NDC space, and P works in clip space before the division, the math still will work out. By using the homogenous coordinates, the translation part of M will be scaled by w accordingly. The resulting matrix will just be a general asymmetric projection matrix.
Now this does not adjust the near and far clipping planes of the original projection. But you can adjust them in the very same way by adding appropriate scale and translation to the z coordinate.
Also note that using this approach, you are not even restricted to selecting an axis-parallel rectangle, you can also rotate or skew it arbitrarily, so basically, you can select an arbitrary parallelogram in window space.
How do I calculate this perspective matrix?
An asymmetric perspective (column major order) projection matrix is set up like this:
m[16] = [
2*n/(r-l), 0, 0, 0,
0, 2*n/(t-b), 0, 0,
(r+l)/(r-l), (t+b)/(t-b), -(f+n)/(f-n), -1,
0, 0, -2*f*n/(f-n), 0];
Where r, l, b, and t are the left, right, bottom and top distances to the frustum planes on the near plane. n and f are the distances to the near and far plane.
In common, in a framework or a library a projection matrix like this is set up by a function called frustum.
e.g.
OpenGL Mathematics: glm::frustum
OpenGL fixed function pipeline: glFrustum

OpenGL simultaneous translate and rotate around local axis

I am working on an application that has similar functionality to MotionBuilder in its viewport interactions. It has three buttons:
Button 1 rotates the viewport around X and Y depending on X/Y mouse drags.
Button 2 translates the viewport around X and Y depending on X/Y mouse drags.
Button 3 "zooms" the viewport by translating along Z.
The code is simple:
glTranslatef(posX,posY,posZ);
glRotatef(rotX, 1, 0, 0);
glRotatef(rotY, 0, 1, 0);
Now, the problem is that if I translate first, the translation will be correct but the rotation then follows the world axis. I've also tried rotating first:
glRotatef(rotX, 1, 0, 0);
glRotatef(rotY, 0, 1, 0);
glTranslatef(posX,posY,posZ);
^ the rotation works, but the translation works according to world axis.
My question is, how can I do both so I achieve the translation from code snippet one and the rotation from code snippet 2.
EDIT
I drew this rather crude image to illustrate what I mean by world and local rotations/translations. I need the camera to rotate and translate around its local axis.
http://i45.tinypic.com/2lnu3rs.jpg
Ok, the image makes things a bit clearer.
If you were just talking about an object, then your first code snippet would be fine, but for the camera it's quite different.
Since there's technically no object as a 'camera' in opengl, what you're doing when building a camera is just moving everything by the inverse of how you're moving the camera. I.e. you don't move the camera up by +1 on the Y axis, you just move the world by -1 on the y axis, which achieves the same visual effect of having a camera.
Imagine you have a camera at position (Cx, Cy, Cz), and it has x/y rotation angles (CRx, CRy). If this were just a regular object, and not the camera, you would transform this by:
glTranslate(Cx, Cy, Cz);
glRotate(CRx, 1, 0, 0);
glRotate(CRy, 0, 1, 0);
But because this is the camera, we need to do the inverse of this operation instead (we just want to move the world by (-Cx, -Cy, and -Cz) to emulate the moving of a 'camera'. To invert the matrix, you just have to do the opposite of each individual transform, and do them in reverse order.
glRotate(-CRy, 0, 1, 0);
glRotate(-CRx, 1, 0, 0);
glTranslate(-Cx, -Cy, -Cz);
I think this will give you the kind of camera you're mentioning in your image.
I suggest that you bite the apple and implement a camera class that stores the current state of the camera (position, view direction, up vector, right vector) and manipulate that state according to your control scheme. Then you can set up the projection matrix using gluLookAt(). Then, the order of operations becomes unimportant. Here is an example:
Let camPos be the current position of the camera, camView its view direction, camUp the up vector and camRight the right vector.
To translate the camera by moveDelta, simply add moveDelta to camPos. Rotation is a bit more difficult, but if you understand quaternions you'll be able to understand it quickly.
First you need to create a quaternion for each of your two rotations. I assume that your horizontal rotation is always about the positive Z axis (which points at the "ceiling" if you will). Let hQuat be the quaternion representing the horizontal rotation. I further assume that you want to rotate the camera about its right axis for your vertical rotation (creating a pitch effect). For this, you must apply the horizontal rotation to the camera's current angle. The result is the rotation axis for your vertical rotation hQuat. The total rotation quaternion is then rQuat = hQuat * vQuat. Then you apply rQuat to the camera's view direction, up, and right vectors.
Quat hRot(rotX, 0, 0, 1); // creates a quaternion that rotates by angle rotX about the positive Z axis
Vec3f vAxis = hRot * camRight; // applies hRot to the camera's right vector
Quat vRot(rotY, vAxis); // creates a quaternion that rotates by angle rotY about the rotated camera's right vector
Quat rQuat = hRot * vRot; // creates the total rotation
camUp = rQuat * camUp;
camRight = rQuat * camRight;
camView = rQuat * camView;
Hope this helps you solve your problem.
glRotate always works around the origin. If you do:
glPushMatrix();
glTranslated(x,y,z);
glRotated(theta,1,0,0);
glTranslated(-x,-y,-z);
drawObject();
glPopMatrix();
Then the 'object' is rotate around (x,y,z) instead of the origin, because you moved (x,y,z) to the origin, did the rotation, and then pushed (x,y,z) back where it started.
However, I don't think that's going to be enough to get the effect you're describing. If you always want transformations to be done with respect to the current frame of reference, then you need to keep track of the transformation matrix yourself. This why people use Quaternion based cameras.

GLSL coordinate space?

Do I have to map gl_Position to be within (-1,-1),(1,1) for it to appear on screen, or what? i.e., is (-1,-1) the top left, and (1,1) the bottom right?
Before I was just using my library's CreatePerspectiveFieldOfView and it took care of all the conversions for me, but I'm writing my own now and I'm not quite sure what I need to map the coords to...
gl_Position is a 4-vector, so it has more than just those two elements, usually named x, y, z and w. When clipping, the coordinates are clipped to [-w, w], or in other words, after normalization (dividing each coordinate by w), to [-1, 1]. So normalized coordinates are clipped by the cube (-1, -1, -1) - (1, 1, 1) (unless you have defined more clip planes).
For more information on clipping, read
http://www.opengl.org/documentation/specs/version1.1/glspec1.1/node28.html