Braiden Grant Braiden Grant - 2 months ago 25
Java Question

Casting Rays in 3D space with rotation components

I am currently working on a CPU based simple ray tracer to render few triangles as a project. I'm okay with every aspect of it except generating the actual rays. I do not wish to project world coordinates into screen space, but actually create the rays according to where the camera is located in 3D coordinates.

Right now, I have a fairly alright algorithm which allows me to generate a ray for each pixel on the screen for any rotation around the Y-Axis, and it attempts to incorporate the X-Axis as well, giving some up and down looking capability, however when the user looks up or down, the image becomes distorted.

This is what I have worked out so far:

Ray ray = new Ray(Camera.position,
new Vector3(
Math.sin(Camera.rotation.y+(x*2/Main.renderSize.width)/2) * Math.cos(Camera.rotation.x+(y*2/Main.renderSize.height)/2),
Math.sin(Camera.rotation.x+(-y*2/Main.renderSize.height)/2),
Math.cos(Camera.rotation.y+(x*2/Main.renderSize.width)/2) * Math.cos(Camera.rotation.x-(y*2/Main.renderSize.height)/2)
));


This gives me a good viewing projection when the camera is not facing in any upwards or downwards direction.

Image of projection when camera is forwards:

img.

Image of projection when camera is looking slightly upwards

img.

It would be greatly appreciated if anyone can help me with the algorithm or point me towards a new one or a good source. Speed is a necessity as it is all real-time. Thanks.

Answer

there may be more going on then just wrong ray direction. So to be more clear here is what I meant by my comment in more detail:

ray

So let assume all the stuff is in camera space coordinate system. so the screen is on plane z=0 and the middle pixel is (0,0,0). The focal point is at focus=(0,0,-f) where f is the focal length of your projection. Now You need to cast ray (or more) for each pixel so its position and direction are:

pos = (x,y,0);
dir = pos-focus = (x,y,f);
// most likely you should also normalize it so
dir = dir / |dir|;

When your x,y coordinates are not in world units but pixels instead you should rescale them accordingly. So let have resolution of the screen xs,ys and we want to have field of view in x axis FOVx = 60.0deg then you need to change this all a bit:

// pixel size
sz = f*tan(0.5*FOVx)/(0.5*xs);
// x,y screen position [pixels] -> xx,yy [world units]
xx=(x-(0.5*xs))*sz;
yy=(y-(0.5*ys))*sz;
// ray
pos = (xx,yy,0);
dir = pos-focus = (xx,yy,f);
dir = dir / |dir|;

Now comes in the transformation matrix. Let M be the transformation matrix representing your screen. The origin is set to middle of screen and the x,y axis vectors correspond to screen axises. You also need M0 matrix which is the copy of M but with origin set to (0,0,0)

As you want to make all the stuff in world global coordinate system GCS then to transform from screen space to world do:

world_position  = M *screen_position;
world_direction = M0*screen_direction;

But this could be slightly different if different matrix elements and operand order convention is used then in the link above.

So now the ray in world GCS will be:

// pixel size
sz = f*tan(0.5*FOVx)/(0.5*xs);
// x,y screen position [pixels] -> xx,yy [world units]
xx=(x-(0.5*xs))*sz;
yy=(y-(0.5*ys))*sz;
// ray
pos = (xx,yy,0);
dir = pos-focus = (xx,yy,f);
// convert to world GCS
pos = M *pos;
dir = M0*dir;
// normalize
dir = dir / |dir|;

Usually you can start with M as unit matrix and from that just multiply by rotations and translations on key strokes to handle view movement and rotations. Before each render you can compute M0=M and then just set the 3 elements holding origin to zero.