I have a program in which I am tracking user's position and setting the frustum (setting the camera at user's position) to change the perspective of the scene as per the user's position. Until right now, I had all four corners of the display screen at the same z and I was able to set the asymmetric frustum and change the scene according to the user's perspective.
The current code looks something like the following:
UserCam::begin(){
saveGlobalMatrices();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum(_topLeftNear.x, _bottomRightNear.x, _bottomRightNear.y, _topLeftNear.y, _camZNear, _camZFar);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(_wcUserHead.x, _wcUserHead.y, _topLeftScreen.z, _wcUserHead.x, _wcUserHead.y, _topLeftScreen.z-1, 0, 1, 0);
}
UserCam::end(){
loadGlobalMatrices();
}
UserCam::setupCam(){
this->_topLeftScreen = _wcTopLeftScreen - _wcUserHead; //wcTopLeftScreen, wcBottomRightScreen and wcUserHead are in the same frame of reference
this->_bottomRightScreen = _wcBottomRightScreen - _wcUserHead;
this->_topLeftNear = (_topLeftScreen/ _topLeftScreen.z) * _camZNear;
this->_bottomRightNear = (_bottomRightScreen/_bottomRightScreen.z )) * _camZNear;
}
However, I want to be able to do the same with a display which is kept tilted to the user and/or does not have all its vertices at the same Z.
The above can be imagined as a sort of tilted window, the vertices of which would have the frustum defined from the user's position. How is such a frustum possible where the display does not have all the vertices at the same Z?
EDIT
There are three planes in the setup that I am considering. The middle one give the correct asymmetric frustum since all the vertices are at the same Z, whereas the left and right planes have two vertices each at different Z. The vertices of the same are as follows:
Plane1: TL : (-426.66, 0, 200), TR: (0, 0, 0), BL : (-426.66, 320.79, 200), BR : (0, 320.79, 0)
Plane2: TL : (0, 0, 0), TR: (426.66, 0, 0), BL : (0, 320.79, 0), BR: (426.66, 320.79, 0)
Plane3: TL: (426.66, 0, 0), TR: (853.32, 0, 200), BL : (426.66, 320.79, 0), BR : (853.32, 320.79, 200)
The idea in this setup is to transform it to a case where all corners have the same z-coordinate. Usually this is done with a view matrix and you get:
overall_transform = (projection) * (view * world)
or in the OpenGL wording
overall_transform = projection * modelview
If you don't want to tamper with the original modelview matrix, you should introduce another matrix in between:
overall_transform = (projection * adaption) * (view * world)
where adaption is a rotation matrix that maps the screen's corners to a plane with constant z-coordinate.
In order to find the correct parameters for projection you have to transform the screen with adaption.
Edit
We start with an arbitrary scene where the camera's position, direction and the screen is known. We consider that the model matrices are already there for each object:
We then need the view transformation V that aligns the camera with the origin. This matrix can easily calculated with gluLookAt. The overall matrix is then T = V * M:
Up to this step the matrices are the same for all three screens. So this part should be in the modelview matrix. What we add now goes into the projection matrix because it differs per screen.
We need to apply a rotation R that aligns the screen perpendicular to the z-axis. The position of the camera must not change at this step because it represents the projection center. The overall transformation is now T = R * V * M.
In order to calculate the angle, we can use e.g. atan2:
dx = right.x - left.x
dz = right.z - left.z
angle = atan2(dz, dx)
It might be necessary to adapt this calculation slightly to your actual needs.
Now is the time to apply the actual perspective transform, which can be done with glFrustum.
We need to find the local edges of the screen. You could transform the screen coordinates with the current transform (R * V).
TL' = R * V * TL
BL' = R * V * BL
BR' = R * V * BR
Now all three coordinates should have the same z-coordinate. We can use these as follows:
common_z = TL'.z = BL'.z = BR'.z
glFrustum(TL'.x / common_z * z_near,
BR'.x / common_z * z_near,
BL'.y / common_z * z_near,
TL'.y / common_z * z_near,
z_near, z_far)
So overall T = glFrustum * R * V * M:
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(...);
//any further model transforms
glMatrixMode(GL_PROJECTION);
glFrustum(...);
glRotate(...);
Related
I'm trying to visualize a simple quad made of -1 to 1 vertices along x and y axis. Why opengl clips the object? The code seems correct to me
glm::mat4 m = glm::translate(glm::mat4{1.0f}, toGlmVec3(objectPosition));
glm::mat4 v = glm::lookAtLH(toGlmVec3(cameraPosition), toGlmVec3(objectPosition), glm::vec3(0, 1, 0));
glm::mat4 p = glm::perspective(glm::radians(50.f), float(640.f) / 480.f, 0.0001f, 100.f);
glm::mat4 mvp = /* p* */ v * m; // when I take p back, the object disappears completely
testShader.use();
testShader.setVector4("u_color", math::Vector4f(0.f, 1.f, 0.f, 1.f));
testShader.setMatrix4("u_mMVP", mvp);
in shader's code only a line
gl_Position = u_mMVP * vec4(a_Pos, 1.0);
after moving the camera a bit along z axis
if I comment out v *, then it works fine and object moves along x and y axis on the screen
without view matrix, only model:
move the object along x and y
so it looks like the rendering code is working fine but what is wrong with view and projection matrices?
The object is clipped by the near and far plane of the Orthographic projection. If you don't explicitly set an projection matrix, the projection matrix is the Identity matrix. The near plane far pane are at +/- 1.
Use glm::ortho to define a different projection matrix. e.g.:
glm::mat4 p = glm::ortho(-1, 1, -1, 1, -10, 10);
The orthographic projection matrix defines a cuboid viewing volume around the position of the viewer. All geometry outside of this volume is clipped.
I need help about the matrix Transformation to find the corners of my viewPort in 3d coordinates(World Space).
I have done some test but i cannot find the solution.
Step 1:
I have Projection and Model Matrix avaible (ViewPort size too). To find the center of my "Screen" i have used this:
OpenGL.UnProject(0.0, 0.0, 0.0) <- this function tell me where is the center of my screen in 3D space (Correct!)
Another approach is to multiply the Coordinate (0, 0, 0) * ProjectionMtx.Inverse.
Step 2:
Now i need for example the left top corner of my viewport, how can i find the 3D point in the world space?
Probably i should work with the viewport size but how?
This is my unproject method:
double[] mview = new double[16];
GetDouble(GL_MODELVIEW_MATRIX, mview);
double[] prj = new double[16];
GetDouble(GL_PROJECTION_MATRIX, prj);
int[] vp = new int[4];
GetInteger(GL_VIEWPORT, vp);
double[] r = new double[3];
gluUnProject(winx, winy, winz, mview, prj, vp, ref r[0], ref r[1], ref r[2]);
For Example:
if i have my camera in (-40,0,0) and my vieport [0,0,1258,513] and i unproject my near plane points i have this result:
left_bottom_near =>X=-39.7499881839701,Y=-0.0219584744091603,Z=0.946276352352364
right_bottom_near =>X=-39.7499881839701,Y=-0.0219584744091603,Z=0.946446903614738
left_top_near =>X=-39.7499881839701,Y=-0.0217879231516134,Z=0.946276352352364
right_top_near =>X=-39.7499881839701,Y=-0.0217879231516134,Z=0.946446903614738
I can understand the X value of my points that is ~ to my x world value of my camera position but, what about the Y & Z? I cannot understand.
gluUnProject converts from window coordinates to to world space (or model space).
The view matrix converts from world space to view space.
The projection matrix transforms from view space to normalized device space. The normalized device space is a cube with the left, bottom, near of (-1, -1, -1) and the right, top, far of (1, 1, 1).
Finally the xy coordinates in normalized device space are mapped to the viewport (window coordinates). The viewport is defined by glViewport. The z component is maped to the glDepthRange (defalt [0.0, 1.0]. gluUnProject does the opposite of all this steps.
At orthographic projection the viewing volume is a cuboid. At Perspective projection the viewing volume is a frustum. The projection matrix transforms from view space to normalized device space.
What you can see on the viewport is the projection of the normalized device space to its xy plane.
In general the corners of the viewing volume can be get by un-projecting the 8 corner points of the window coordinates. For that you have to know the viewport rectangle and the depth range.
I assume that the viwport has the size of the window (0, 0, widht, height) and that the depth range is [0.0, 1.0]:
left = 0.0;
right = width;
bottom = 0.0;
top = height;
left_bottom_near = gluUnProject(left, bottom, 0.0)
right_bottom_near = gluUnProject(right, bottom, 0.0)
left_top_near = gluUnProject(left, top, 0.0)
right_top_near = gluUnProject(right, top, 0.0)
left_bottom_far = gluUnProject(left, bottom, 1.0)
right_bottom_far = gluUnProject(right, bottom, 1.0)
left_top_far = gluUnProject(left, top, 1.0)
right_top_far = gluUnProject(right, top, 1.0)
I have fov angle = 60, width = 640 and height = 480 of window, near = 0.01 and far = 100 planes and I get projection matrix using glm::perspective()
glm::perspective(glm::radians(fov),
width / height,
zNear,
zFar);
It works well.
Then I want to change projection type to orthogonal, but I don't knhow how to compute input parameters of glm::ortho() properly.
I've tried many ways, but problem is after switching to orthographic projection size of model object become another.
Let I have a cube with center in (0.5, 0.5, 0.5) and length size 1, and camera with mEye in (0.5, 0.5, 3), mTarget in (0.5, 0.5, 0.5) and mUp (0, 1, 0). View matrix is glm::lookAt(mEye, mTarget, mUp)
With perspective projection it works well. With glm::ortho(-width, width, -height, height, zNear, zFar) my cube became a small pixel in the center of window.
Also I've tried implement this variant How to switch between Perspective and Orthographic cameras keeping size of desired object
but result is (almost) same as before.
So, first question is how to compute ortho parameters for saving original view size of object/position of camera?
Also, zooming with
auto distance = glm::length(mTarget - mEye)
mEye = mTarget - glm::normalize(mTarget - mEye) * distance;
have no effect with ortho. Thus second question is how to implement zooming in case of ortho projection?
P.s.
I assume I understand ortho correctly. Proportions of model doesn't depends on depth, but nevertheless I still can decide where camera is for setting size of model properly and using zoom. Also I assume it is simple and trivial task, for example, when developing a 3D-viewer/editor/etc. Correct me if it is not.
how to compute ortho parameters for saving original view size of object/position of camera?
At orthographic projection the 3 dimensional scene is parallel projection to the 2 dimensional viewport.
This means that the objects, which are projected on the viewport always have the same size, independent of their depth (distance to the camera).
The perspective projection describes the mapping from 3D points in the world as they are seen from of a pinhole camera, to 2D points of the viewport.
This means an object which is projected on the viewport becomes smaller, by its depth.
If you switch form perspective to orthographic projection only the objects in 1 plane, which is planar (parallel) to the viepwort, and keeps its depth. Note, a plane is 2 dimensional and has no "depth". This cause that a 3 dimensional object never can "look" the same, when the projection is switched. But a 2 dimensional billboard can keep it's size.
The ration of depth an size at perspective projection is linear and can be calculated. It depends on the field of view angle only:
float ratio_size_per_depth = atan(glm::radians(fov / 2.0f) * 2.0f;
If you want to set up an orthographic projection, which keeps the size for a certain distance (depth) then you have to define the depth first:
e.g. Distance to the target point:
auto distance = glm::length(mTarget - mEye);
the projection can be set up like this:
float aspect = width / height
float size_y = ratio_size_per_depth * distance;
float size_x = ratio_size_per_depth * distance * aspect;
glm::mat4 orthProject = glm::ortho(-size_x, size_x, -size_y, size_y, 0.0f, 2.0f*distance);
how to implement zooming in case of ortho projection?
Scale the XY components of the orthographic projection:
glm::mat4 orthProject = glm::ortho(-size_x, size_x, -size_y, size_y, 0.0f, 2.0f*distance);
float orthScale = 2.0f;
orthProject = glm::scale(orthProject, glm::vec3(orthScale, orthScale, 1.0f));
Set a value for orthScale which is > 1.0 for zoom in and a value which is < 1.0 for zoom out.
I have a pipeline that uses model, view and projection matrices to render a triangle mesh.
I am trying to implement a ray tracer that will pick out the object I'm clicking on by projecting the ray origin and direction by the inverse of the transformations.
When I just had a model (no view or projection) in the vertex shader I had
Vector4f ray_origin = model.inverse() * Vector4f(xworld, yworld, 0, 1);
Vector4f ray_direction = model.inverse() * Vector4f(0, 0, -1, 0);
and everything worked perfectly. However, I added a view and projection matrix and then changed the code to be
Vector4f ray_origin = model.inverse() * view.inverse() * projection.inverse() * Vector4f(xworld, yworld, 0, 1);
Vector4f ray_direction = model.inverse() * view.inverse() * projection.inverse() * Vector4f(0, 0, -1, 0);
and nothing is working anymore. What am I doing wrong?
If you use perspective projection, then I recommend to define the ray by a point on the near plane and another one on the far plane, in normalized device space. The z coordinate of the near plane is -1 and the z coordinate of the far plane 1. The x and y coordinate have to be the "click" position on the screen in the range [-1, 1] The coordinate of the bottom left is (-1, -1) and the coordinate of the top right is (1, 1). The window or mouse coordinates can be mapped linear to the NDCs x and y coordinates:
float x_ndc = 2.0 * mouse_x/window_width - 1.0;
flaot y_ndc = 1.0 - 2.0 * mouse_y/window_height; // flipped
Vector4f p_near_ndc = Vector4f(x_ndc, y_ndc, -1, 1); // z near = -1
Vector4f p_far_ndc = Vector4f(x_ndc, y_ndc, 1, 1); // z far = 1
A point in normalized device space can be transformed to model space by the inverse projection matrix, then the inverse view matrix and finally the inverse model matrix:
Vector4f p_near_h = model.inverse() * view.inverse() * projection.inverse() * p_near_ndc;
Vector4f p_far_h = model.inverse() * view.inverse() * projection.inverse() * p_far_ndc;
After this the point is a Homogeneous coordinates, which can be transformed by a Perspective divide to a Cartesian coordinate:
Vector3f p0 = p_near_h.head<3>() / p_near_h.w();
Vector3f p1 = p_far_h.head<3>() / p_far_h.w();
The "ray" in model space, defined by point r and a normalized direction d finally is:
Vector3f r = p0;
Vector3f d = (p1 - p0).normalized()
I was trying to display 2D text in using 3D coordinates.
I was following this tutorial (Solution #1: The 2D way). I did everything as shown in this tutorial but something is probably wrong. Here is the code:
void Update()
{
glm::mat4 projectionMatrix = glm::perspective(45.0,640.0/480.0,0.01,500.0);
glm::mat4 viewMatrix = glm::lookAt(glm::vec3(0.0,0.0,0.0),glm::vec3(0.0,0.0,-5.0),glm::vec3(0.0,1.0,0.0));
glm::vec4 worldSpace(0.f,1.0,-5.f,1.0);
glm::vec4 screenSpace = projectionMatrix * viewMatrix * worldSpace;
screenSpace /= screenSpace.w;
ovlay.setPosition(screenSpace.x,screenSpace.y);
}
projectionMatrix is the perspective that I'm using.
viewMatrix is my camera position and direction.
worldSpace is the position in 3D that I want to use to calculate 2D coords.
screenSpace should give me the position in 2D space but I get some weird result:
x = 0, y = 0.358518
I think it should be something like x = 320, y = 100.
If someone knows what I did wrong I'd be thankful.
Well, what this code calculates are normalized device coordinates of thet worldSpace point. The viewing volume is [-1,1] along all axis in this space, so x=0 is exactly at the center and y=0.358518 is somewhere above the center.
If you want the window space position, you need to take the viewport into account. Assuming your viewport fillst the whole window of size w * h pixels, you can get the window position as:
wx = (x + 1.0f) * 0.5f * w;
wy = (y + 1.0f) * 0.5f * h;
Assuming the 640x480 resoluting suggested by your projection matrix, this would give (320, 326). I don't know why you'd expect y as 100. Note the GL uses the bottom left corner as origin. In typical window systems, origin is at the top, so y=326 in the GL would match y'=153 in that other convention.