I am reading the depthbuffer of a scene, however as I rotate the camera I notice that towards the edges of the screen the depth is returned closer to camera. I think the angle of impact has an effect on the depthbuffer, however as I am drawing a quad to the framebuffer, I do not want this to happen (this is not actually the case ofcourse but this sums up my what I need).
I linearize the depth with the following:
float linearize(float depth) {
float zNear = 0.1;
float zFar = 40.0;
return (2.0 * zNear) / (zFar + zNear - depth * (zFar - zNear));
}
I figured the following to correct for this, but it's not quite right yet. 45.0 is the angle of the camera vertically / 2. side is the space from the center of the screen.
const float angleVert = 45.0 / 180.0 * 3.17;
float sideAdjust(vec2 coord, float depth) {
float angA = cos(angleVert);
float side = (coord.y - 0.5);
if (side < 0.0) side = -side;
side *= 2.0;
float depthAdj = angA * side;
return depth / depthAdj;
}
To show my problem with a drawing with results of depth of a flat surface in front of the camera:
c
/ | \
/ | \
/ | \
closer further closer
is what I have, what I need:
c
| | |
| | |
| | |
even even even
An idea of how to do it, would be to find the position P in eye-space. Consider P a vector from origin to the point. Project the P onto the the eye direction vector (which in eye-space always is (0,0,-1)). The length of the projected vector is what you need.
Related
I have the following glm perspective projection
world.cameraProjection = glm::perspective(glm::radians(45.0f), app.aspectRatio, 0.0f, 100.0f);
world.cameraProjection = glm::scale(world.cameraProjection, world.scale);
world.cameraView = glm::translate(world.cameraProjection, world.camera);
And I will like to guess the value I will have to use to draw a line that has pixel perfect width.
I know the width of my screen, and I'm trying now to translate the percentage of the viewport that represents 1 pixel into a distance for the glm cameraView. So even if the zoom changes the line I'm drawing will appear always the same size in the screen.
Is there a function in glm to do this?
When using Perspective projection, the x- and y-distance that represents one pixel depends on the depth (z-distance).
A projected size in normalized device space can be transformed to a size in view space with:
aspect = width / height
tanFov = tan(fov_y / 2.0) * 2.0;
dist_x = ndc_dist_x * z_eye * tanFov * aspect;
dist_y = ndc_dist_y * z_eye * tanFov;
What you want is that:
height * (ndc_dist_y + 1) / 2 == 1
So if you know the z-distance of an object, the formula is:
dist = z_eye * (2.0 / height - 1.0) * tan(fov_y / 2.0) * 2.0
where fov_y is the field of view in radiant, height is the height of the field of view in pixels, and z_eye is the absolute distance from the object to the camera.
Consider using parallel projection (Orthographic projection), where the size of the projection does not depend on the distance from the camera.
I am working on some really simple ray-tracer.
For now I am trying to make the perspective camera works properly.
I use such loop to render the scene (with just two, hard-coded spheres - I cast ray for each pixel from its center, no AA applied):
Camera * camera = new PerspectiveCamera({ 0.0f, 0.0f, 0.0f }/*pos*/,
{ 0.0f, 0.0f, 1.0f }/*direction*/, { 0.0f, 1.0f, 0.0f }/*up*/,
buffer->getSize() /*projectionPlaneSize*/);
Sphere * sphere1 = new Sphere({ 300.0f, 50.0f, 1000.0f }, 100.0f); //center, radius
Sphere * sphere2 = new Sphere({ 100.0f, 50.0f, 1000.0f }, 50.0f);
for(int i = 0; i < buffer->getSize().getX(); i++) {
for(int j = 0; j < buffer->getSize().getY(); j++) {
//for each pixel of buffer (image)
double centerX = i + 0.5;
double centerY = j + 0.5;
Geometries::Ray ray = camera->generateRay(centerX, centerY);
Collision * collision = ray.testCollision(sphere1, sphere2);
if(collision){
//output red
}else{
//output blue
}
}
}
The Camera::generateRay(float x, float y) is:
Camera::generateRay(float x, float y) {
//position = camera position, direction = camera direction etc.
Point2D xy = fromImageToPlaneSpace({ x, y });
Vector3D imagePoint = right * xy.getX() + up * xy.getY() + position + direction;
Vector3D rayDirection = imagePoint - position;
rayDirection.normalizeIt();
return Geometries::Ray(position, rayDirection);
}
Point2D fromImageToPlaneSpace(Point2D uv) {
float width = projectionPlaneSize.getX();
float height = projectionPlaneSize.getY();
float x = ((2 * uv.getX() - width) / width) * tan(fovX);
float y = ((2 * uv.getY() - height) / height) * tan(fovY);
return Point2D(x, y);
}
The fovs:
double fovX = 3.14159265359 / 4.0;
double fovY = projectionPlaneSize.getY() / projectionPlaneSize.getX() * fovX;
I get good result for 1:1 width:height aspect (e.g. 400x400):
But I get errors for e.g. 800x400:
Which is even slightly worse for bigger aspect ratios (like 1200x400):
What did I do wrong or which step did I omit?
Can it be a problem with precision or rather something with fromImageToPlaneSpace(...)?
Caveat: I spent 5 years at a video company, but I'm a little rusty.
Note: after writing this, I realized that pixel aspect ratio may not be your problem as the screen aspect ratio also appears to be wrong, so you can skip down a bit.
But, in video we were concerned with two different video sources: standard definition with a screen aspect ratio of 4:3 and high definition with a screen aspect ratio of 16:9.
But, there's also another variable/parameter: pixel aspect ratio. In standard definition, pixels are square and in hidef pixels are rectangular (or vice-versa--I can't remember).
Assuming your current calculations are correct for screen ratio, you may have to account for the pixel aspect ratio being different, either from camera source or the display you're using.
Both screen aspect ratio and pixel aspect ratio can be stored a .mp4, .jpeg, etc.
I downloaded your 1200x400 jpeg. I used ImageMagick on it to change only the pixel aspect ratio:
convert orig.jpg -resize 125x100%\! new.jpg
This says change the pixel aspect ratio (increase the width by 125% and leave the height the same). The \! means pixel vs screen ratio. The 125 is because I remember the rectangular pixel as 8x10. Anyway, you need to increase the horizontal width by 10/8 which is 1.25 or 125%
Needless to say this gave me circles instead of ovals.
Actually, I was able to get the same effect with adjusting the screen aspect ratio.
So, somewhere in your calculations, you're introducing a distortion of that factor. Where are you applying the scaling? How are the function calls different?
Where do you set the screen size/ratio? I don't think that's shown (e.g. I don't see anything like 1200 or 400 anywhere).
If I had to hazard a guess, you must account for aspect ratio in fromImageToPlaneSpace. Either width/height needs to be prescaled or the x = and/or y = lines need scaling factors. AFAICT, what you've got will only work for square geometry at present. To test, using the 1200x400 case, multiply the x by 125% [a kludge] and I bet you get something.
From the images, it looks like you have incorrectly defined the mapping from pixel coordinates to world coordinates and are introducing some stretch in the Y axis.
Skimming your code it looks like you are defining the camera's view frustum from the dimensions of the frame buffer. Therefore if you have a non-1:1 aspect ratio frame buffer, you have a camera whose view frustum is not 1:1. You will want to separate the model of the camera's view frustum from the image space dimension of the final frame buffer.
In other words, the frame buffer is the portion of the plane projected by the camera that we are viewing. The camera defines how the 3D space of the world is projected onto the camera plane.
Any basic book on 3D graphics will discuss viewing and projection.
I have a program that reads a 360 mono panorama and reads an IMU, drawing the correct part of the panorama based on the head location.
I am creating two windows, one per display, and do not want to rely on GLUT_STEREO. The draw() calls for each display are therefore independent, but right now they render the same thing, which is a gluSphere to represent the panorama. To draw the correct part of the sphere, IMU data (quaternion) becomes a rotation matrix, and that matrix is multiplied with the projection.
I wish to create a little bit of overlap with the two images, as shown with the following image:
For example, the red rectangle is my left display and the blue rectangle is my right display, but there is some overlap in the middle.
I was reading some article about stereo rendering, and I thought the solution would be to replace the call from gluPerspective() to glFrustum(), and simply modify both the left and right parameter at the same time. I thought subtracting some value to left/right parameter of glFrustum() on the display and adding some value to the left/right parameter of glFrustum() would do the trick. I modified the glutReshapeFunc() callback's projection matrix to do just that:
void resize(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLdouble near = 0.1;
GLdouble far = 100.0;
GLdouble aspect = (double) width / (double) height;
GLdouble top = tan(FOVY / 360 * M_PI) * near;
GLdouble bottom = -top;
GLdouble right = top * aspect;
GLdouble left = -right;
// TODO: Canned value for testing
left += 0.5;
right += 0.5;
glFrustum(left, right, bottom, top, near, far);
// gluPerspective(FOVY, aspect, near, far);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
Unfortunately, this does not do what I expect (and I am really not sure why). I would think modifying both left and right parameter of glFrustum() would keep the same horizontal FOV but move it to the left or right. It seems to either stretch the image in or out.
I have played around with glTranslatef() on the ModelView or glLookAt(), but there place are not clear to me. Why is glFrustum() not having the right behavior please, and what am I missing?
Modify the frustum and the camera.
You need need two different camera matrices to simulate the eye separation and slightly different frustums to eliminate toe-in.
3D Stereo Rendering
Using OpenGL (and GLUT):
/* Misc stuff */
ratio = camera.screenwidth / (double)camera.screenheight;
radians = DTOR * camera.aperture / 2;
wd2 = near * tan(radians);
ndfl = near / camera.focallength;
/* Derive the two eye positions */
CROSSPROD(camera.vd,camera.vu,r);
Normalise(&r);
r.x *= camera.eyesep / 2.0;
r.y *= camera.eyesep / 2.0;
r.z *= camera.eyesep / 2.0;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
left = - ratio * wd2 - 0.5 * camera.eyesep * ndfl;
right = ratio * wd2 - 0.5 * camera.eyesep * ndfl;
top = wd2;
bottom = - wd2;
glFrustum(left,right,bottom,top,near,far);
glMatrixMode(GL_MODELVIEW);
glDrawBuffer(GL_BACK_RIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
gluLookAt(camera.vp.x + r.x,camera.vp.y + r.y,camera.vp.z + r.z,
camera.vp.x + r.x + camera.vd.x,
camera.vp.y + r.y + camera.vd.y,
camera.vp.z + r.z + camera.vd.z,
camera.vu.x,camera.vu.y,camera.vu.z);
MakeLighting();
MakeGeometry();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
left = - ratio * wd2 + 0.5 * camera.eyesep * ndfl;
right = ratio * wd2 + 0.5 * camera.eyesep * ndfl;
top = wd2;
bottom = - wd2;
glFrustum(left,right,bottom,top,near,far);
glMatrixMode(GL_MODELVIEW);
glDrawBuffer(GL_BACK_LEFT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
gluLookAt(camera.vp.x - r.x,camera.vp.y - r.y,camera.vp.z - r.z,
camera.vp.x - r.x + camera.vd.x,
camera.vp.y - r.y + camera.vd.y,
camera.vp.z - r.z + camera.vd.z,
camera.vu.x,camera.vu.y,camera.vu.z);
MakeLighting();
MakeGeometry();
glutSwapBuffers();
Replace the glDrawBuffer() calls with appropriate FBO binds.
This function is supposed to give me the exact size of my near clipping plane.
public Vector2 NearplaneSize
{
get
{
float w = 2 * Mathf.Tan(Mathf.Deg2Rad(Fov) / 2) * ZNear;
return new Vector2(w, w / AspectRatio);
}
}
I'm creating a plane like this:
Vector2 s = cam.NearplaneSize;
Mesh = PrimitiveFactory.CreatePlane(s.X / -2, s.Y / -2, -(cam.ZNear + 0.1f), s.X, s.Y, 1, 1, Quaternion.FromAxisAngle(Vector3.UnitX, Mathf.Deg2Rad(90)));
in front of the camera, but its slightly larger than half the screen. So obviously the calculation is wrong. I can't seem to find a better formula though.
Any ideas? Thanks
I don't know OpenTK, but due to old gluPerspective call, "Fov" is generally understood as fov y, not fovx.
So I assume that
float h = 2 * Mathf.Tan(Mathf.Deg2Rad(Fov) / 2) * ZNear;
return new Vector2(h * AspectRatio, h);
should do the trick.
My application is a vector drawing application. It works with OpenGL. I will be modifying it to instead use the Cairo 2D graphics library. The issue is with zooming. With openGL camera and scale factor sort of work like this:
float scalediv = Current_Scene().camera.ScaleFactor / 2.0f;
float cameraX = GetCameraX();
float cameraY = GetCameraY();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
float left = cameraX - ((float)controls.MainGlFrame.Dimensions.x) * scalediv;
float right = cameraX + ((float)controls.MainGlFrame.Dimensions.x) * scalediv;
float bottom = cameraY - ((float)controls.MainGlFrame.Dimensions.y) * scalediv;
float top = cameraY + ((float)controls.MainGlFrame.Dimensions.y) * scalediv;
glOrtho(left,
right,
bottom,
top,
-0.01f,0.01f);
// Set the model matrix as the current matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
hdc = BeginPaint(controls.MainGlContext.mhWnd,&ps);
Mouse position is obtained like this:
POINT _mouse = controls.MainGlFrame.GetMousePos();
vector2f mouse = functions.ScreenToWorld(_mouse.x,_mouse.y,GetCameraX(),GetCameraY(),
Current_Scene().camera.ScaleFactor,
controls.MainGlFrame.Dimensions.x,
controls.MainGlFrame.Dimensions.y );
vector2f CGlEngineFunctions::ScreenToWorld(int x, int y, float camx, float camy, float scale, int width, int height)
{
// Move the given point to the origin, multiply by the zoom factor and
// add the model coordinates of the center point (camera position)
vector2f p;
p.x = (float)(x - width / 2.0f) * scale +
camx;
p.y = -(float)(y - height / 2.0f) * scale +
camy;
return p;
}
From there I draw the VBO's of triangles. This allows me to pan and zoom in. Given that Cairo only can draw based on coordinates, how can I make it so that a vertex is properly scaled and panned without using transformations. Basically GlOrtho sets the viewport usually but I dont think I could do this with Cairo.
Well GlOrtho is able to change the viewport matrix instead of modifying the verticies but how could I instead modify the verticies to get the same result?
Thanks
*Given vertex P, which was obtained from ScreenToWorld, how could I modify it so that it is scaled and panned accordng to the camera and scale factor? Because usually OpenGL would essentially do this
I think Cairo can do what you want ... see http://cairographics.org/matrix_transform/ . Does that solve your problem, and if not, why ?