How do I determine what transforms I need to make a square fill an entire window in modern OpenGL. Say for example I have an 800 x 600 window and the coordinates with the vertices of two triangles extending from -1 and 1. Without any type of transformation, these coordinated would fill an 800 x 600 window because OpenGL's coordinates extend from -1 to 1. What if I want to use a standard MVP transformation, though? How do I determine what needs to be done in order to fill an entire window. Consider this code:
glm::mat4 projectionMatrix = glm::perspective(60.0f, 4.f/3.f, 0.1f, 100.0f); // gluPerspective equivalent for those who may not know about glm
glm::mat4 viewMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -5.0f));;
glm::mat4 modelMatrix = glm::mat4(1.0f);
with the same coordinates. I would now get a square somewhere in the middle of the window. Assuming I do not change the projection matrix, what changes would need to be made to the View and Model matrices? I understand the matrix math, but not how it relates to window coordinates themselves.
Could anyone help me understand this?
For 2D elements (like a HUD) I generally set an ortographic matrix with left = 0, right = width, bottom = 0, top = height where width and height are the size of the window. znear = -1 and zfar = 1.
Then your rectangle vertices would be at 0, 0, width and height.
Is that what you want? I'm not sure why you want to use a perspective matrix for a rectangle that fills the screen.
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 am trying to rotate the texture image using glm,but the output looks stretched or not in the properly rotated along z axis. What might be the possible solution for this one.
float imgAspectRatio = imageWidth / (float) imageHeight;
float viewAspectRatio = viewWidth / (float) viewHeight;
if (imgAspectRatio > viewAspectRatio) {
yScale = viewAspectRatio / imgAspectRatio;
} else {
xScale = imgAspectRatio / viewAspectRatio;
}
glm::mat4 model(1.0f);
model = glm::translate(model, glm::vec3(0, 0, 0));
model = glm::rotate(model, glm::radians(angle),
glm::vec3(0, 0, 1));
model = glm::scale(model, glm::vec3(xScale, yScale, 1.0f));
Some suggested multiply with glm::ortho with but its giving square shape
glm::mat4 projection(1.0f);
projection = glm::ortho(
-imgAspectRatio,
imgAspectRatio,
-1.0f,
1.0f,
-1.0f,
1.0f
);
The code that you currently have would actually work if you had a square window.
By combining the image aspect ratio scaling and the view aspect ratio scaling into a single step, the order of operations you currently have is essentially as follows:
Scale image plane by image aspect ratio.
Scale all xy coordinates in your scene by the view aspect ratio.
Rotate image plane.
Translate image plane.
However, you really want to scale by the view aspect ratio at the very end. An easy exercise to help visualize why: imagine you have a perfectly square window in which you render a triangle with xy coordinates (-1, -1), (1, -1), (0, 1). Now suppose your window doubles in height, but you want the shape to stay the same: obviously, you just multiply each y-coordinate by 1/2. Now suppose you want to rotate the triangle by 90 degrees ccw, but again still keep the shape the same. Do you rotate first and then scale by the view aspect ratio? Or vice versa? By running through both options, it becomes clear that you have to scale by the view aspect ratio very last.
In other words, you need the following order:
Scale image plane by image aspect ratio.
Rotate image plane.
Translate image plane.
Scale all xy coordinates in your scene by the view aspect ratio.
Which is achieved by code that looks something like this:
float xScaleImg = 1.0f;
float yScaleImg = xScaleImg / imgAspectRatio;
float xScaleView = 1.0f;
float yScaleView = viewAspectRatio;
glm::mat4 model(1.0f);
model = glm::scale(model, glm::vec3(xScaleView, yScaleView, 1.0f));
model = glm::translate(model, glm::vec3(0, 0, 0));
model = glm::rotate(model, glm::radians(angle), glm::vec3(0, 0, 1));
model = glm::scale(model, glm::vec3(xScaleImg, yScaleImg, 1.0f));
The first four lines assume, as is the case in your example, that both the view and image widths are greater than the respective heights. You'll probably want to add some logic to change this around in the event that the reverse is true.
Update:
Depending on the effect you want to achieve for translation, you may want to move the glm::translate(...) command up one line. The order I gave in my original answer keeps the units of translation equal in pixels. E.g. if you pass in glm::vec3(1.0f, 1.0f, 0.0f), and the width of the window is, say, 1280 pixels, then the image will be translated 640 pixels to the left and 640 pixels upwards.
However, you may want to keep the OpenGL [-1, 1] range on both axes. That is, when you pass in glm::vec3(1.0f, 1.0f, 0.0f) for the translation, you may want the image to be translated right by half of the window's width and up by half of the window's height. In that case, you need to make the translation the last operation performed on the image, and this is done by making the glm::translate(...) the first line of code. This is what Nile Qor wanted. In this case, the last lines of the code become something like:
glm::mat4 model(1.0f);
model = glm::translate(model, glm::vec3(1.0f, 1.0f, 0));
model = glm::scale(model, glm::vec3(xScaleView, yScaleView, 1.0f));
model = glm::rotate(model, glm::radians(angle), glm::vec3(0, 0, 1));
model = glm::scale(model, glm::vec3(xScaleImg, yScaleImg, 1.0f));
For more context, you can see the discussion in the comments section of this answer.
I'm trying to teach myself the ways for 3D programming with OpenGL, however I am struggling with some things, especially projection matrices.
I defined some vertices for a cube and successfully handed them to my graphics processor. The cube goes from xyz -0.5 to xyz 0.5 respectively, which gets rendered fine.
To move it into my world coordinate system, I am using this model matrix:
auto model = glm::mat4(
glm::vec4(1, 0, 0, 0),
glm::vec4(0, 1, 0, 0),
glm::vec4(0, 0, 1, 0),
glm::vec4(0, 0, 0, 1)
);
model = glm::translate(model, glm::vec3(0.f, 0.f, 495.f));
model = glm::scale(model, glm::vec3(100.f, 100.f, 100.f));
This successfully moves my cube to (-50, -50, 445) -> (50, 50, 545) so its now centered in the 200x200x1000 world coordinates I defined for myself.
My camera / view matrix is
auto view = glm::lookAt(
glm::vec3(0.f, 0.f, 5.f),
glm::vec3(0.f, 0.f, 0.f),
glm::vec3(0.f, 1.f, 0.f)
);
which moves the cube slightly closer, changing the z coordinate to 440 and 540 respectively. I don't understand why this is happening but I guess it has something to do with glm expecting a right hand coordinate system while I am working with a left handed one? While this is not why I am posting this question, I would be happy if someone would clear it up for me.
Now to my actual problem: I am trying to make use of glm::perspective. I call it like this:
auto perspective = glm::perspective(glm::radians(55.f), 1.f, 0.f, 1000.f);
If I'm not mistaken, at a z value of 440 I can expect the clipping area to go from roughly -229 to 229, so I would expect that bottom right cube vertex at (-50,-50) is visible. I calculated this by drawing the frustum in 2D, when I noticed that I should be able to calculate the height of any distance to the camera using tan(alpha / 2) * distToCamera = maxVisibleCoordinate (working with a 1:1 aspect ratio). Is this a correct assumption? Here is my terrible drawing, maybe you can tell that I have a wrong understanding of something with it:
In the final step I am trying to get all this together in my vertex shader using
gl_Position = projection * view * model * vec4(pos.x, pos.y, pos.z, 1.0);
which yields a perfectly reasonable result for the x and y value but the z value is always -1 which is, as far as I know, just right for not being displayed.
For my front-bottom-left vertex of the cube (-0.5, -0.5, -0.5) the result is (-96.04, -96.04, -440, -440), normalized to (-0.218, -0.218, -1).
For my back-top-right vertex of the cube (0.5, 0.5, 0.5) the result is (96.04, 96.04, -550, -550), normalized to (0.218, 0.218, -1).
What am I getting wrong, that my z value is lost and just set to -1 instead? When playing around with the camera position, the best I can get is getting it to 1, which also results in an empty window and is definitely not what I would expect.
A projection matrix is like this:
In the picture, f is for zfar and n is for znear.
As you can see, if you put znear = 0, the term at the 4th column become zero, which is incorrect. Also, -(f+n)/(f-n) = -1, which is incorrect too.
So, the conclusion is, znear cannot be zero. It is usually a small value, for example, 0.1
Since Amadeus already answered the question correctly, I'm going to just use this space to add some clarifying information about why it's correct.
We can refer back to the diagram you provided to explain what the problem is: You have two planes, the near plane and the far plane, representing the range at which you may view objects. What the Perspective Matrix does is it takes everything in between those two planes, within the Frustrum that you've defined (mathematically a cone, but our monitors are rectangular, so...) and maps them onto the flat Near-plane to create the final image. In a sense, you can think of the Near Plane as representing the monitor.
So given this context, if you were to set the Near Plane's distance to 0, meaning it was identical to the camera, what would happen? Well, in a cone it would set the plane to a single point, and in a frustrum, it's the same. You cannot view objects drawn onto a single point. You need a surface with actual surface area to draw onto.
That is why it is inappropriate to set the near value to 0. It would turn the drawing surface into a single point, and you cannot mathematically render any objects on a single point. Hence why the essential mathematical formulas backing the matrix will break down and result in bad outcomes if you try to do so anyways.
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.
The game is a top-down 2D space ship game -- think of "Asteroids."
Box2Dx is the physics engine and I extended the included DebugDraw, based on OpenTK, to draw additional game objects. Moving the camera so it's always centered on the player's ship and zooming in and out work perfectly. However, I really need the camera to rotate along with the ship so it's always facing in the same direction. That is, the ship will appear to be frozen in the center of the screen and the rest of the game world rotates around it as it turns.
I've tried adapting code samples, but nothing works. The best I've been able to achieve is a skewed and cut-off rendering.
Render loop:
// Clear.
Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
// other rendering omitted (planets, ships, etc.)
this.OpenGlControl.Draw();
Update view -- centers on ship and should rotate to match its angle. For now, I'm just trying to rotate it by an arbitrary angle for a proof of concept, but no dice:
public void RefreshView()
{
int width = this.OpenGlControl.Width;
int height = this.OpenGlControl.Height;
Gl.glViewport(0, 0, width, height);
Gl.glMatrixMode(Gl.GL_PROJECTION);
Gl.glLoadIdentity();
float ratio = (float)width / (float)height;
Vec2 extents = new Vec2(ratio * 25.0f, 25.0f);
extents *= viewZoom;
// rotate the view
var shipAngle = 180.0f; // just a test angle for proof of concept
Gl.glRotatef(shipAngle, 0, 0, 0);
Vec2 lower = this.viewCenter - extents;
Vec2 upper = this.viewCenter + extents;
// L/R/B/T
Glu.gluOrtho2D(lower.X, upper.X, lower.Y, upper.Y);
Gl.glMatrixMode(Gl.GL_MODELVIEW);
}
Now, I'm obviously doing this wrong. Degrees of 0 and 180 will keep it right-side-up or flip it, but any other degree will actually zoom it in/out or result in only blackness, nothing rendered. Below are examples:
If ship angle is 0.0f, then game world is as expected:
Degree of 180.0f flips it vertically... seems promising:
Degree of 45 zooms out and doesn't rotate at all... that's odd:
Degree of 90 returns all black. In case you've never seen black:
Please help!
Firstly the 2-4 arguments are the axis, so please state them correctly as stated by #pingul.
More importantly the rotation is applied to the projection matrix.
// L/R/B/T
Glu.gluOrtho2D(lower.X, upper.X, lower.Y, upper.Y);
In this line your Orthogonal 2D projection matrix is being multiplied with the previous rotation and applied to your projection matrix. Which I believe is not what you want.
The solution would be move your rotation call to a place after the model view matrix mode is selected, as below
// L/R/B/T
Glu.gluOrtho2D(lower.X, upper.X, lower.Y, upper.Y);
Gl.glMatrixMode(Gl.GL_MODELVIEW);
// rotate the view
var shipAngle = 180.0f; // just a test angle for proof of concept
Gl.glRotatef(shipAngle, 0.0f, 0.0f, 1.0f);
And now your rotations will be applied to the model-view matrix stack. (I believe this is the effect you want). Keep in mind that glRotatef() creates a rotation matrix and multiplies it with the matrix at the top of the selected stack stack.
I would also strongly suggest you move away from fixed function pipeline if possible as suggested by #BDL.