Perspective Camera, How to Setup + generate a ray?

Started by
20 comments, last by Drusenija 18 years, 7 months ago
.. [Edited by - sjf on August 28, 2005 6:57:04 PM]
Advertisement
Hey, the same problem/concept has be bugg'n me. Sorry, I am unable to be of assistance. Normally the talent here is pretty good. Just keep your fingers crossed!

em i don t know how you want to implement this but in opengl you transfrom as follows

modelspace
|
worldspace
|
viewspace
|
clipspace
|
normalized device coordinates
|
viewport



you could inverse this
by setting up the transformations as you do in opengl
calculate the ray from each pixel and trace back to your scene

here is a article on how to get the picking ray in opengl
http://www.bookofhook.com/Article/GameDevelopment/mousepick.pdf




if course you need to sample more than 1 ray per pixel to get decent results

i hope this was of any help for you

here is the sourcecode for gluUnproject which you can use to calculate your ray's dir vector

/* $Id: project.c,v 1.2 1999/09/14 00:10:31 brianp Exp $ *//* * Mesa 3-D graphics library * Version:  3.1 * Copyright (C) 1995-1999  Brian Paul * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *//* * $Log: project.c,v $ * Revision 1.2  1999/09/14 00:10:31  brianp * added gluUnProject4() * * Revision 1.1.1.1  1999/08/19 00:55:42  jtg * Imported sources * * Revision 1.7  1999/01/03 03:23:15  brianp * now using GLAPIENTRY and GLCALLBACK keywords (Ted Jump) * * Revision 1.6  1998/07/08 01:43:43  brianp * new version of invert_matrix()  (also in src/matrix.c) * * Revision 1.5  1997/07/24 01:28:44  brianp * changed precompiled header symbol from PCH to PC_HEADER * * Revision 1.4  1997/05/28 02:29:38  brianp * added support for precompiled headers (PCH), inserted APIENTRY keyword * * Revision 1.3  1997/04/11 23:22:42  brianp * added divide by zero checks to gluProject() and gluUnproject() * * Revision 1.2  1997/01/29 19:05:29  brianp * faster invert_matrix() function from Stephane Rehel * * Revision 1.1  1996/09/27 01:19:39  brianp * Initial revision * */#ifdef PC_HEADER#include "all.h"#else#include <stdio.h>#include <string.h>#include <math.h>#include "gluP.h"#endif/* * This code was contributed by Marc Buffat (buffat@mecaflu.ec-lyon.fr). * Thanks Marc!!! *//* implementation de gluProject et gluUnproject *//* M. Buffat 17/2/95 *//* * Transform a point (column vector) by a 4x4 matrix.  I.e.  out = m * in * Input:  m - the 4x4 matrix *         in - the 4x1 vector * Output:  out - the resulting 4x1 vector. */static void transform_point( GLdouble out[4], const GLdouble m[16],			     const GLdouble in[4] ){#define M(row,col)  m[col*4+row]   out[0] = M(0,0) * in[0] + M(0,1) * in[1] + M(0,2) * in[2] + M(0,3) * in[3];   out[1] = M(1,0) * in[0] + M(1,1) * in[1] + M(1,2) * in[2] + M(1,3) * in[3];   out[2] = M(2,0) * in[0] + M(2,1) * in[1] + M(2,2) * in[2] + M(2,3) * in[3];   out[3] = M(3,0) * in[0] + M(3,1) * in[1] + M(3,2) * in[2] + M(3,3) * in[3];#undef M}/* * Perform a 4x4 matrix multiplication  (product = a x b). * Input:  a, b - matrices to multiply * Output:  product - product of a and b */static void matmul( GLdouble *product, const GLdouble *a, const GLdouble *b ){   /* This matmul was contributed by Thomas Malik */   GLdouble temp[16];   GLint i;#define A(row,col)  a[(col<<2)+row]#define B(row,col)  b[(col<<2)+row]#define T(row,col)  temp[(col<<2)+row]   /* i-te Zeile */   for (i = 0; i < 4; i++)     {	T(i, 0) = A(i, 0) * B(0, 0) + A(i, 1) * B(1, 0) + A(i, 2) * B(2, 0) + A(i, 3) * B(3, 0);	T(i, 1) = A(i, 0) * B(0, 1) + A(i, 1) * B(1, 1) + A(i, 2) * B(2, 1) + A(i, 3) * B(3, 1);	T(i, 2) = A(i, 0) * B(0, 2) + A(i, 1) * B(1, 2) + A(i, 2) * B(2, 2) + A(i, 3) * B(3, 2);	T(i, 3) = A(i, 0) * B(0, 3) + A(i, 1) * B(1, 3) + A(i, 2) * B(2, 3) + A(i, 3) * B(3, 3);     }#undef A#undef B#undef T   MEMCPY( product, temp, 16*sizeof(GLdouble) );}/* * Compute inverse of 4x4 transformation matrix. * Code contributed by Jacques Leroy jle@star.be * Return GL_TRUE for success, GL_FALSE for failure (singular matrix) */static GLboolean invert_matrix( const GLdouble *m, GLdouble *out ){/* NB. OpenGL Matrices are COLUMN major. */#define SWAP_ROWS(a, b) { GLdouble *_tmp = a; (a)=(b); (b)=_tmp; }#define MAT(m,r,c) (m)[(c)*4+(r)] GLdouble wtmp[4][8]; GLdouble m0, m1, m2, m3, s; GLdouble *r0, *r1, *r2, *r3; r0 = wtmp[0], r1 = wtmp[1], r2 = wtmp[2], r3 = wtmp[3]; r0[0] = MAT(m,0,0), r0[1] = MAT(m,0,1), r0[2] = MAT(m,0,2), r0[3] = MAT(m,0,3), r0[4] = 1.0, r0[5] = r0[6] = r0[7] = 0.0, r1[0] = MAT(m,1,0), r1[1] = MAT(m,1,1), r1[2] = MAT(m,1,2), r1[3] = MAT(m,1,3), r1[5] = 1.0, r1[4] = r1[6] = r1[7] = 0.0, r2[0] = MAT(m,2,0), r2[1] = MAT(m,2,1), r2[2] = MAT(m,2,2), r2[3] = MAT(m,2,3), r2[6] = 1.0, r2[4] = r2[5] = r2[7] = 0.0, r3[0] = MAT(m,3,0), r3[1] = MAT(m,3,1), r3[2] = MAT(m,3,2), r3[3] = MAT(m,3,3), r3[7] = 1.0, r3[4] = r3[5] = r3[6] = 0.0; /* choose pivot - or die */ if (fabs(r3[0])>fabs(r2[0])) SWAP_ROWS(r3, r2); if (fabs(r2[0])>fabs(r1[0])) SWAP_ROWS(r2, r1); if (fabs(r1[0])>fabs(r0[0])) SWAP_ROWS(r1, r0); if (0.0 == r0[0])  return GL_FALSE; /* eliminate first variable     */ m1 = r1[0]/r0[0]; m2 = r2[0]/r0[0]; m3 = r3[0]/r0[0]; s = r0[1]; r1[1] -= m1 * s; r2[1] -= m2 * s; r3[1] -= m3 * s; s = r0[2]; r1[2] -= m1 * s; r2[2] -= m2 * s; r3[2] -= m3 * s; s = r0[3]; r1[3] -= m1 * s; r2[3] -= m2 * s; r3[3] -= m3 * s; s = r0[4]; if (s != 0.0) { r1[4] -= m1 * s; r2[4] -= m2 * s; r3[4] -= m3 * s; } s = r0[5]; if (s != 0.0) { r1[5] -= m1 * s; r2[5] -= m2 * s; r3[5] -= m3 * s; } s = r0[6]; if (s != 0.0) { r1[6] -= m1 * s; r2[6] -= m2 * s; r3[6] -= m3 * s; } s = r0[7]; if (s != 0.0) { r1[7] -= m1 * s; r2[7] -= m2 * s; r3[7] -= m3 * s; } /* choose pivot - or die */ if (fabs(r3[1])>fabs(r2[1])) SWAP_ROWS(r3, r2); if (fabs(r2[1])>fabs(r1[1])) SWAP_ROWS(r2, r1); if (0.0 == r1[1])  return GL_FALSE; /* eliminate second variable */ m2 = r2[1]/r1[1]; m3 = r3[1]/r1[1]; r2[2] -= m2 * r1[2]; r3[2] -= m3 * r1[2]; r2[3] -= m2 * r1[3]; r3[3] -= m3 * r1[3]; s = r1[4]; if (0.0 != s) { r2[4] -= m2 * s; r3[4] -= m3 * s; } s = r1[5]; if (0.0 != s) { r2[5] -= m2 * s; r3[5] -= m3 * s; } s = r1[6]; if (0.0 != s) { r2[6] -= m2 * s; r3[6] -= m3 * s; } s = r1[7]; if (0.0 != s) { r2[7] -= m2 * s; r3[7] -= m3 * s; } /* choose pivot - or die */ if (fabs(r3[2])>fabs(r2[2])) SWAP_ROWS(r3, r2); if (0.0 == r2[2])  return GL_FALSE; /* eliminate third variable */ m3 = r3[2]/r2[2]; r3[3] -= m3 * r2[3], r3[4] -= m3 * r2[4], r3[5] -= m3 * r2[5], r3[6] -= m3 * r2[6], r3[7] -= m3 * r2[7]; /* last check */ if (0.0 == r3[3]) return GL_FALSE; s = 1.0/r3[3];              /* now back substitute row 3 */ r3[4] *= s; r3[5] *= s; r3[6] *= s; r3[7] *= s; m2 = r2[3];                 /* now back substitute row 2 */ s  = 1.0/r2[2]; r2[4] = s * (r2[4] - r3[4] * m2), r2[5] = s * (r2[5] - r3[5] * m2), r2[6] = s * (r2[6] - r3[6] * m2), r2[7] = s * (r2[7] - r3[7] * m2); m1 = r1[3]; r1[4] -= r3[4] * m1, r1[5] -= r3[5] * m1, r1[6] -= r3[6] * m1, r1[7] -= r3[7] * m1; m0 = r0[3]; r0[4] -= r3[4] * m0, r0[5] -= r3[5] * m0, r0[6] -= r3[6] * m0, r0[7] -= r3[7] * m0; m1 = r1[2];                 /* now back substitute row 1 */ s  = 1.0/r1[1]; r1[4] = s * (r1[4] - r2[4] * m1), r1[5] = s * (r1[5] - r2[5] * m1), r1[6] = s * (r1[6] - r2[6] * m1), r1[7] = s * (r1[7] - r2[7] * m1); m0 = r0[2]; r0[4] -= r2[4] * m0, r0[5] -= r2[5] * m0, r0[6] -= r2[6] * m0, r0[7] -= r2[7] * m0; m0 = r0[1];                 /* now back substitute row 0 */ s  = 1.0/r0[0]; r0[4] = s * (r0[4] - r1[4] * m0), r0[5] = s * (r0[5] - r1[5] * m0), r0[6] = s * (r0[6] - r1[6] * m0), r0[7] = s * (r0[7] - r1[7] * m0); MAT(out,0,0) = r0[4]; MAT(out,0,1) = r0[5], MAT(out,0,2) = r0[6]; MAT(out,0,3) = r0[7], MAT(out,1,0) = r1[4]; MAT(out,1,1) = r1[5], MAT(out,1,2) = r1[6]; MAT(out,1,3) = r1[7], MAT(out,2,0) = r2[4]; MAT(out,2,1) = r2[5], MAT(out,2,2) = r2[6]; MAT(out,2,3) = r2[7], MAT(out,3,0) = r3[4]; MAT(out,3,1) = r3[5], MAT(out,3,2) = r3[6]; MAT(out,3,3) = r3[7];  return GL_TRUE;#undef MAT#undef SWAP_ROWS}/* projection du point (objx,objy,obz) sur l'ecran (winx,winy,winz) */GLint GLAPIENTRY gluProject(GLdouble objx,GLdouble objy,GLdouble objz,                          const GLdouble model[16],const GLdouble proj[16],                          const GLint viewport[4],                          GLdouble *winx,GLdouble *winy,GLdouble *winz){    /* matrice de transformation */    GLdouble in[4],out[4];    /* initilise la matrice et le vecteur a transformer */    in[0]=objx; in[1]=objy; in[2]=objz; in[3]=1.0;    transform_point(out,model,in);    transform_point(in,proj,out);    /* d'ou le resultat normalise entre -1 et 1*/    if (in[3]==0.0)       return GL_FALSE;    in[0]/=in[3]; in[1]/=in[3]; in[2]/=in[3];    /* en coordonnees ecran */    *winx = viewport[0]+(1+in[0])*viewport[2]/2;    *winy = viewport[1]+(1+in[1])*viewport[3]/2;    /* entre 0 et 1 suivant z */    *winz = (1+in[2])/2;    return GL_TRUE;}/* transformation du point ecran (winx,winy,winz) en point objet */GLint GLAPIENTRY gluUnProject(GLdouble winx,GLdouble winy,GLdouble winz,                            const GLdouble model[16],const GLdouble proj[16],                            const GLint viewport[4],                            GLdouble *objx,GLdouble *objy,GLdouble *objz){    /* matrice de transformation */    GLdouble m[16], A[16];    GLdouble in[4],out[4];    /* transformation coordonnees normalisees entre -1 et 1 */    in[0]=(winx-viewport[0])*2/viewport[2] - 1.0;    in[1]=(winy-viewport[1])*2/viewport[3] - 1.0;    in[2]=2*winz - 1.0;    in[3]=1.0;    /* calcul transformation inverse */    matmul(A,proj,model);    invert_matrix(A,m);    /* d'ou les coordonnees objets */    transform_point(out,m,in);    if (out[3]==0.0)       return GL_FALSE;    *objx=out[0]/out[3];    *objy=out[1]/out[3];    *objz=out[2]/out[3];    return GL_TRUE;}/* * New in GLU 1.3 * This is like gluUnProject but also takes near and far DepthRange values. */GLint GLAPIENTRYgluUnProject4( GLdouble winx, GLdouble winy, GLdouble winz, GLdouble clipw,               const GLdouble modelMatrix[16],               const GLdouble projMatrix[16],               const GLint viewport[4],               GLclampd nearZ, GLclampd farZ,               GLdouble *objx, GLdouble *objy, GLdouble *objz, GLdouble *objw ){    /* matrice de transformation */    GLdouble m[16], A[16];    GLdouble in[4],out[4];    GLdouble z = nearZ + winz * (farZ - nearZ);    /* transformation coordonnees normalisees entre -1 et 1 */    in[0] = (winx-viewport[0])*2/viewport[2] - 1.0;    in[1] = (winy-viewport[1])*2/viewport[3] - 1.0;    in[2] = 2.0 * z - 1.0;    in[3] = clipw;    /* calcul transformation inverse */    matmul(A,projMatrix,modelMatrix);    invert_matrix(A,m);    /* d'ou les coordonnees objets */    transform_point(out,m,in);    if (out[3]==0.0)       return GL_FALSE;    *objx=out[0]/out[3];    *objy=out[1]/out[3];    *objz=out[2]/out[3];    *objw=out[3];    return GL_TRUE;}
http://www.8ung.at/basiror/theironcross.html
I have seen it done before. And using that exact same class representation. Someone must know... We'll just have to wait sjf.
ok guys, here is the deal. the real problem here is we have a center point and we need to calculate a direction for each pixel. the screen is really a pretend plane in 3-space that satisifies our parameters. fov, up, lookat, etc. all we need now is point on the plane and we're set. how do we calculate that point? well the fov and distance from the eye tells us the width and height of our plane. you're missing a common paramater, that is the distance between the eye and the viewplane, you really can't build the plane without that becasue there are infinate planes that satisify the paramaters up, lookat, fov, center. consider a plane with fov 45 1 meter from the eye if we moved the plane 2 meters from the eye and we increased the width and height of the plane by two, this would actually trace a similiar view of the scene for objects that are further then 2 meters away from the eye. anyway I hope I'm not going to crazy. here is the jist I'm gonna add a paramater dNear that represents the distance from the ray to the eye. I'm also gonna add an aspect ratio paramater

ok lets start by calculating the true up and right vectors
note: the lookat vector is just the view direction

right = cross(lookat, up).noramalize();
trueUp = cross(right, lookat).normalize();

//now we calculate the width and height of our plane we have
height = 2*dNear*tan(fov*PI/180);
width = height*aspectRatio;

ok so this is the width and height of that imaginary plane we talked about, now suppose I want to know the distance(in world space) between pixels, can you guess how?

widthPixel = width/horozontalResolution;
heightPixel = height/heightResolution;

ok now we got all the junk out the way, now we can calculate the direction vector for a pixel i, j (not necessarily integers) now what you do here varies slightly depending on if you want 0,0 on the upper left corner of the screen or the lower left corner. I'm gonna make 0,0 on the bottommost left most pixel of the image, the lower left corner of the pic that is.

note: I'm assuming lookat is normalized and thus lookat*dNear has a magnitue of dNear. also I'm gonna calculate the position of the lower left corner of the screen first, this is not efficent, but it's easy to understand I think.

//calculate the position of the lower leftmost point on the viewplane
lowerleft = center + lookat*dNear - (width/2)*right - (height/2)*trueUp;

//now that we have the lower left point of the screen we can calculate any other
very simply (note the addition of .5, this is to send rays at whole number(i,j) trou the CENTER of the pixel) this is just my convention

position = lowerleft + (i + .5)*widthPixel*right + (j + .5)*heightPixel*trueUp;

//ok so now we have the position of the pixel on the viewscreen, calculating the direction should now be obvious.

direction = position - center;


and the origin of this ray is obviously, center.

if you got questions, feel free

[Edited by - timw on August 24, 2005 7:58:45 AM]
Well, that's how I did it:
-I generated the four corner points coordinate from my camera setup parameters (size of the window, focal lenght and so on...) in camera space.
-Using the inverse of the look-at-point transformation matrix I transform the four points.
-I interpolate trought them to get nxm points, that I use to set up nxm rays with the position of the camera as origin and the Normalize(generated_point-position_of_camera) as direction.

If you want to have anti aliasing or DOF then you must modify a bit the algo, but the main concept is always the same.
that works to, seems kinda weird tho, I mean when you think of ray tracing you don't think of camera space, you just think of everything being in global space. the matrix inversion and multiplicaiton seems like going roundabout and is definitly less efficent then the vector method. not that generating primary rays needs to be efficent lol. I guess it depends on the app, obviously doesn't mean jack if you're doing any form of distributed tracing.

Tim
gluUnProject is definitely what you want. The only downside is that you either have to have stored the modelview matrix, projection matrix, and viewport, or you have to query them from OpenGL.

Here's a quick sample.

glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
glGetIntegerv(GL_VIEWPORT, viewport);
GLdouble p1[3];
GLdouble p2[3];

gluUnProject(point.x, viewport[3] - point.y, 0.0, modelMatrix, projMatrix, viewport, p1, p1+1, p1+2);

gluUnProject(point.x, viewport[3] - point.y, 0.5, modelMatrix, projMatrix, viewport, p2, p2+1, p2+2);

vector3 mouseRay(p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]);
or you could understand the problem and do the math yourself. instead of relying on open gl to do the same thing for you. if you can't see that the methods are similir I suggest doing the vector one because you can see exactly how the calculation is done.
Ok, a just knew I had come across the same problem before. sjf was right (I think) we only need up and dir vectors, a center point and a fov angle.

Sorry, I don't have any proof to back it up, but check out:

http://groups.csail.mit.edu/graphics/classes/6.837/F04/assignments/assignment2/

Their p/camera only needs those same 4 params.

So how would they do it? Thus, it can be done!

This topic is closed to new replies.

Advertisement