I want to create a oblique (cavalier) projection in OpenGL. I know this operation is not default supported and instead I need a Shear Matrix and then make an Orthogonal Projection.
Can you tell me what are the OpenGl steps / functions that I have to make?
I've not used a oblique/cavalier projection before, but the following should give you an idea of how to proceed:
Create a 4x4 shear matrix,
H(θ, Φ) = | 1, 0, -cot(θ), 0 |
| 0, 1, -cot(Φ), 0 |
| 0, 0, 1, 0 |
| 0, 0, 0, 1 |
θ being the shear in X, Φ being the shear in Y, and Z being left alone.
(ref: slide 11 of http://www.cs.unm.edu/~angel/CS433/LECTURES/CS433_17.pdf)
Multiply that by your orthographic projection,
| 2/(r-l), 0, 0, -(r+l)/(r-l) |
| 0, 2/(t-b), 0, -(t+b)/(t-b) |
| 0, 0, 2/(f-n), -(f+n)/(f-n) |
| 0, 0, 0, 1 |
(described by, left, right, bottom, top, near and far)
(ref: http://en.wikipedia.org/wiki/Orthographic_projection_%28geometry%29)
OpenGL then allows you to upload this matrix directly (as an array of 16 floats) via the function glLoadMatrixf():
GLfloat proj[16] = { ... };
glMatrixMode(GL_PROJECTION); // Make sure we're modifying the *projection* matrix
glLoadMatrixf(proj); // Load the projection
For a more in depth look at how viewing and transformations work in OpenGL, I'd refer you to Chapter 3 of the OpenGL "Red Book". There they use glOrtho() to create and apply an orthographic projection.
Edit:
As datenwolf points out, bear in mind that the matrix elements in OpenGL are specified in column major order.
OpenGL allows you to specify arbitrary projection matrices. Construct the desired projection matrix yourself to map the incoming vertices into the range -1 to 1 in each dimension, then load it using
GLfloat custrom_projection[16] = {
...
};
glMatrixMode(GL_PROJECTION);
glLoadMatrix(custom_projection);
OpenGL indexes the matrix elements in colum major order, i.e.
0 4 8 12
1 5 9 13
2 6 10 14
3 7 11 15
Since the so-called oblique projection is obtained by rotating the projection plain by a certain angle away from the right one, which produces nothing but a lengthened image along the rotation axis, I think it suffices to just scale the normal orthogonal projection along that axis, by a factor of \csc\theta. This claim can be proven by trigonometry equalities, e.g., \sin\theta+\cos\theta \cot\theta=\csc\theta. If your oblique projection is specified by the \theta and \phi like in luke's answer, the axis angle can be computed as a trigonometry exercise based on this two angles, say, \arctan(\tan\theta\sqrt(1+\cot^2\phi)).
Related
I'm trying to draw a point at every pixel on the screen but I'm getting a weird anomaly: two red lines, one vertical, one horizontal:
My setup is like this:
I've got an ortho projection matrix like this:
glm::mat4 projection=glm::ortho(0.f, screen_width-1.f, 0.f, screen_height-1.f, 1.f, -1.f);
It's just setting the opengl coordinate system to be the same size as the screen's pixel dimensions.
Then, I'm creating filling the data into a vector like this:
for (int i=0; i<screen_height; ++i){
for (int j=0; j<screen_width; ++j){
idata.push_back(j);
idata.push_back(i);
idata.push_back(1);
}
}
I'm uploading integers into a VBO with this:
glBufferData(GL_ARRAY_BUFFER, sizeof(GLint)*idata.size(), &idata[0], GL_DYNAMIC_DRAW);
Setting up the attributes like so:
glVertexAttribIPointer(0, 3, GL_INT, 3*sizeof(GLint), (void *)(0*sizeof(GLint)));
glEnableVertexAttribArray(0);
This is the layout of my shader:
layout (location=0) in ivec3 position;
And this is my draw call:
glDrawArrays(GL_POINTS, 0, idata.size()/3);
That's a rounding issue, because you did set up the projection in a very weird way:
glm::ortho(0.f,screen_width-1.f,0.f,screen_height-1.f,1.f,-1.f)
In OpenGL's window space, pixels are an actual area (of squares of size 1x1).
So, If your screen is for example 4 pixel wide, this is what you get in terms of pixels
+--+--+--+--+
|0 |1 |2 |3 | integer pixel coors
...
However, the space you work in is continuous (in principle, at least), adn OpenGL's window space will go from 0 to 4 (not 3), which makes perfect sense as there are four pixels, and each pixel being one unit wide:
0 1 2 3 4 OpenGL window space
+--+--+--+--+
|0 |1 |2 |3 | integer pixel coords
....
Using OpenGL's window space conventions, the pixel centers lie at half-integer coordinates.
Now you're not drawing in OpenGL window space directly, but you can set up transformation to basically undo the viewport transform (which goes from NDC to window space) to get a pixel-exact mapping. But you didn't do that. what you did instead is:
3/4 9/4
0 6/4 3 your coordinate system
+--+--+--+--+
|0 |1 |2 |3 | integer pixel coords
....
And you're draw at full integers in that weird system, which just does not map very well to the actual pixels.
So you should do
glm::ortho(0.f,screen_width,0,screen_height,1.f,-1.f)
However, since you seem to be drawing at integer positions, you end up drawing exactly at the pixel corners, and might still see some rounding issues (and the rounding direction will be up to the implementation, as per the spec).
So, you better shift it so that integer coordinates lie at pixel centers
glm::ortho(-0.5f, screen_width-0.5f, -0.5f, screen_height-0.5f, 1.f, -1.f)
0.5 2.5
-0.5 1.5 3.5 your coordinate system
+--+--+--+--+
|0 |1 |2 |3 | integer pixel coords
....
I'm trying to create a 3D manipulation program using C++ and openGL. I'm relatively new to openGL so I often have to look up the documentation to find the right function to do what I want. I thought I had a good understanding of orthogonal vs perspective projections (in that glOrtho creates an orthogonal projection where different z-values don't look different and glFrustum creates a perspective projection where z-values that are closer look bigger). However, when I swap out glOrtho and glFrustum in my program, I don't see any difference. I replicated a small program below that shows the effects. For reference, I'm using openGL with freeglut.
#include "GL/freeglut.h"
void initFunc()
{
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1, 1, -1, 1, -1, 1);
}
void displayFunc()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glColor3f(1.0f, 1.0f, 0.0f);
glLineWidth(1.0f);
glutWireTeapot(0.3);
glTranslatef(0, -0.5, -0.5);
glutWireTeapot(0.3);
glutSwapBuffers();
}
int main(int argc, char ** argv)
{
glutInit(&argc, argv);
glutInitWindowSize(600, 600);
glutInitWindowPosition(0, 0);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
glutCreateWindow("Teapot Perspective");
initFunc();
glutDisplayFunc(displayFunc);
glutMainLoop();
}
I'm drawing two teapots slightly offset in both the y and z axes. From what I understand, the glOrtho should render the two teapots as identical with only a y offset, whereas the glFrustum should render one of them bigger than the other. However, both of them render the teapots identically.
Am I missing something here? Are there other steps I have to take to properly set up a perspective projection? Or am I misunderstanding how glFrustum works? I've also tried using gluPerspective instead of glFrustum but I can't seem to find the right values to use. I experimented with a FOV of 90, aspect of 1, and various z values but they all either produce no teapot, or a teapot distorted beyond recognition. Furthermore, the gluPerspective appears to have different behavior than a glFrustum call with corresponding parameters. I'm not sure what I'm missing here.
At Orthographic Projection the coordinates in the view space are linearly mapped to clip space coordinates and the clip space coordinates are equal to the normalized device coordinates, because the w component is 1 (for a cartesian input coordinate).
The values for left, right, bottom, top, near and far define a box. All the geometry which is inside the volume of the box is "visible" on the viewport.
The Orthographic Projection Matrix, defined by glOrtho is:
r = right, l = left, b = bottom, t = top, n = near, f = far
x: 2/(r-l) 0 0 0
y: 0 2/(t-b) 0 0
z: 0 0 -2/(f-n) 0
t: -(r+l)/(r-l) -(t+b)/(t-b) -(f+n)/(f-n) 1
At Perspective Projection the projection matrix describes the mapping from 3D points in the world as they are seen from of a pinhole camera, to 2D points of the viewport.
The eye space coordinates in the camera frustum (a truncated pyramid) are mapped to a cube (the normalized device coordinates).
A perspective projection matrix can be defined by a frustum (glFrustum).
The distances left, right, bottom and top, are the distances from the center of the view to the side faces of the frustum, on the near plane. near and far specify the distances to the near and far plane on the frustum.
r = right, l = left, b = bottom, t = top, n = near, f = far
x: 2*n/(r-l) 0 0 0
y: 0 2*n/(t-b) 0 0
z: (r+l)/(r-l) (t+b)/(t-b) -(f+n)/(f-n) -1
t: 0 0 -2*f*n/(f-n) 0
A specification like this from you question:
glFrustum(-1, 1, -1, 1, -1, 1);
does not define a proper frustum, because the value for the near plane is negative and the value for the far plane is positive.
If you would check for OpenGL errors (by glGetError), then you would get an INVALID_OPERATION error.
OpenGL 4.6 API Compatibility Profile Specification; 12.1. FIXED-FUNCTION VERTEX TRANSFORMATIONS; page 501:
>
void Frustum( double l, double r, double b, double t, double n, double f );
the coordinates (l b −n)T and (r t −n)T specify the points on the near clipping plane that are mapped to the lower left and upper right corners of the window, respectively (assuming that the eye is located at (0 0 0)T). f gives the distance from the eye to the far clipping plane.
Errors
An INVALID_VALUE error is generated if n <= 0, f <= 0, l == r, b == t, or n == f.
I'm new to OpenGL and I'm trying to understand how the projection matrix works in it.
To create a simple case, I define a triangle in the world space and its coordinates are:
(0,1,0), (1,0,0), (-1,0,0)
I set the modelview matrix and projection matrix as below:
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(
0, 0, 2,
0, 0, 0,
0, 1, 0);
glMatrixMode(GL_PROJECTION);
glOrtho(-2, 2, -2, 2, -0.1, -2.0); // does not work
// glOrtho(-2, 2, -2, 2, 0.1, 2.0); // works
From my understanding, gluLookAt() is used to set the viewing matrix. Since OpenGL does not have a concept of "camera", and thus it transforms the entire world to reach the effect of a camera. In the above code, I assume the "camera" is at (0,0,2), looking at (0,0,0). So OpenGL internally moves the triangle backwards along z axis to z=-2.
To define a view frustum, glOrtho() get 6 parameters. To make the triangle visible in the frustum, I set the near and far value to -0.1 and -2.0 respectively and this should indicate that the frustum include [-0.1, -2.0] on z axis.
I searched for similar questions and found out someone states that the last two parameters of glOrtho() is in fact -near and -far. But if this is correct, the following code should work(but it doesn't):
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(
0, 0, -2, // changed 2 to -2, thus the triangle should be transformed to z=2?
0, 0, 0,
0, 1, 0);
glMatrixMode(GL_PROJECTION);
glOrtho(-2, 2, -2, 2, -0.1, -2.0); // -near=-0.1, -far=-2.0, thus the frustum should include [0.1, 2.0], thus include the triangle
If I'm correct, the triangle should be drawn on the screen, so there must be something wrong with my code. Can anyone help?
First of all note, that the fixed function pipeline matrix stack and drawing by glBegin/glEnd sequences is deprecated since more than 10 years.
Read about Fixed Function Pipeline and see Vertex Specification for a state of the art way of rendering.
If you use a view matrix like this:
gluLookAt(0, 0, 2, 0, 0, 0, 0, 1, 0);
Then the values for the near and the far plane have to be positive when you set up the the projection matrix,
glOrtho(-2, 2, -2, 2, 0.1, 2.0);
because, gluLookAt transforms the vertices to view space (in view space the z axis points out of the viewport), but the projection matrix inverts the z-axis.
But be careful, since the triangle is at z=0
(0,1,0), (1,0,0), (-1,0,0)
and the distance from the camera to the triangle is 2, because of the view matrix, the triangle is placed exactly on the far plane (which is 2.0 too). I recommend to increase the distance to the far plane from 2.0 to (e.g.) 3.0:
glOrtho(-2, 2, -2, 2, 0.1, 3.0);
If you change the view matrix,
gluLookAt(0, 0, -2, 0, 0, 0, 0, 1, 0);
then still the (view space) z-axis points out of the viewport, but you look at the "back" side of the triangle. The triangle is still in the center of the view (0, 0, 0), but the camera position has changed. The triangle is still in front of the camera.
If you would do
gluLookAt(0, 0, 2, 0, 0, 4, 0, 1, 0);
then you would look away from the triangle. You would have to project the backside of the view to the viewport to "see" the triangle (glOrtho(-2, 2, -2, 2, -0.1, -3.0);).
Further note, that glOrtho multiplies the current matrix by the orthographic projection matrix. This means you should set the identity matrix, before you use glOrtho, as you do it with the model view matrix:
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-2, 2, -2, 2, 0.1, 2.0);
Explanation
The projection, view and model matrix interact together to present the objects (meshes) of a scene on the viewport.
The model matrix defines the position orientation and scale of a single object (mesh) in the worldspace of the scene.
The view matrix defines the position and viewing direction of the observer (viewer) within the scene.
The projection matrix defines the area (volume) with respect to the observer (viewer) projected onto the viewport.
At orthographic projection, this area (volume) is defined by 6 distances (left, right, bottom, top, near and far) to the viewer's position.
View matrix
The view coordinates system describes the direction and position from which the scene is looked at. 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).
Projection matrix
The projection matrix describes the mapping from 3D points of the view on a scene, to 2D points on the viewport. It transforms from eye space to the clip space, and the coordinates in the clip space are transformed to the normalized device coordinates (NDC) by dividing with the w component of the clip coordinates. The NDC are in range (-1,-1,-1) to (1,1,1). Every geometry which is out of the clippspace is clipped.
At Orthographic Projection the coordinates in the view space are linearly mapped to clip space coordinates and the clip space coordinates are equal to the normalized device coordinates, because the w component is 1 (for a cartesian input coordinate).
The values for left, right, bottom, top, near and far define a box. All the geometry which is inside the volume of the box is "visible" on the viewport.
The Orthographic Projection Matrix looks like this:
r = right, l = left, b = bottom, t = top, n = near, f = far
2/(r-l) 0 0 0
0 2/(t-b) 0 0
0 0 -2/(f-n) 0
-(r+l)/(r-l) -(t+b)/(t-b) -(f+n)/(f-n) 1
The z-axis is inverted by the projection matrix.
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
I'm trying to do a simple perspective projection in the process of rasterizing a 3D point. Here are all the matrices and other info. All Matrices are row major. The coordinate system is Right Handed.
The Camera is at [0,0,-1] and the point is at [0,0,0] (w=1 for matrix operations)
Model View Matrix(Inverse of Cam Matrix i.e, tx = 0;ty = 0; tz = 1):
[1 0 0 tx]
[0 1 0 ty]
[0 0 1 tz]
[0 0 0 1 ]
Perspective Matrix:
[f/aspect,0,0,0]
0,f,0,0
0,0,-(near+far)/(near-far),2*far*near/(near-far)
0,0,1,0]
aspect is equal to 1 since the viewport is square. Far = 100 and Near = 0.1
f = 1/tan(fovDegress*M_PI/360);
The resultant Matrix is:
1.94445, 0, 0, 0
0, 1.944445, 0, 0
0, 0, 1.020202, -2.020202
0, 0, 1, 0
Now I apply the Model View Matrix and then the Projection Matrix to the point vector and then I get a new point Pv = {x,y,z,w}
Then I get the normalized co-ordinates x' = x/w ; y' = y/w; and z' = z/w;
x' and y' always lie in between [-1,1] as long as the point is in the frustum. But the same isn't the case for z'. As the point approaches near the camera, the z' values increases exponentially. When the point is at [0,0,0] z' value equals -1.
Now, I need to clip some lines, so I need z' value to be in between [1,-1]. I am wondering what's wrong with my procedure. Thank you.
What you experiencing is the nonlinearity of perspectivic depth mapping. With a frustum projection matrix this follows a 1/x law with distance from the viewpoint.
EDIT:
Just double checked your matrices: You got your frustum matrix wrong. The correct frustum matrix is
f/aspect, 0, 0, 0
0, f, 0, 0
0, 0, -(far+near)/(far-near), 2*far*near/(far-near)
0, 0, 1, 0
Still you'll run into a division by zero if you approach the origin. Just put the vector (0,0,0,1) through that thing, which results in (x=0,y=0,z=2*far*near/(far-near),w=0) in clip space. And then the homogenous division {x,y,z}/(w=0) ← blows up.