PDA

View Full Version : Ray generation problem



SuperCraggs
02-17-2009, 05:54 AM
Hi there. I have created a quick application in C# using CSGL as a testbed preview window for a raytracer.

I seem to be having a problem with ray generation.
I am using Molleur-Trombore 1995 as my ray-triangle intersection algorithm, and I am generating rays for picking, and for raytracing.

The raytracer is slow as hell, but i've not hacked in any acclerators yet.

Now, the problem is that when the camera is looking directly at world origin, then the rendering comes out correctly, however, when it shifts, the middle of the frustum renders correctly, while the edges don't.

I'm following the picking FAQ
http://www.opengl.org/resources/faq/technical/selection.htm

and am using exactly this method to generate all my rays.



public Ray CreateRay(int xPixel, int yPixel)
{
Ray Ray;

unsafe
{
Vector3 RayDirection;
Vector3 RayFar = new Vector3();
Vector3 RayNear = new Vector3();

double rx = 0;
double ry = 0;
double rz = 0;

double[] mProjection = new double[16];
double[] mModelView = new double[16];
int[] Viewport = new int[4];

fixed (double* pMView = &mModelView[0])
{
fixed (double* pProj = &mProjection[0])
{
fixed (int* pViewport = &Viewport[0])
{
// populate matricies and viewport data
GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX, mModelView);
GL.glGetDoublev(GL.GL_PROJECTION_MATRIX, mProjection);
GL.glGetIntegerv(GL.GL_VIEWPORT, Viewport);

// calculate ray direction by doing near plane intersection and far plane intersection
GLU.gluUnProject((double)xPixel, (double)(Viewport[3]-1)-yPixel, 0.0, pMView, pProj, pViewport, &rx, &ry, &rz);
RayNear = new Vector3(rx, ry, rz);

GLU.gluUnProject((double)xPixel, (double)(Viewport[3] - 1) - yPixel, 1.0, pMView, pProj, pViewport, &rx, &ry, &rz);
RayFar = new Vector3(rx, ry, rz);

// compute ray direction from far plane intersect - near plane, and remember to normalize!
RayDirection = RayFar - RayNear;
Vector3.Normalize(RayDirection);

// build ray from eye point as origin, raydirection as the ray point on the farplane - the point on the ray near plane.
Ray = new Ray(this.GetEye(), RayDirection);
}
}
}
}

return Ray;
}


Can anyone see any problems with the above ray generation function? If not, then I have some other ideas where there may be problems, but I don't want this post to be of epic length.

This is driving me crazy, please help! I can't put in any secondary rays until this damn problem is sorted :)

Craig.

scratt
02-17-2009, 10:11 AM
FWIW I had similar problems with accuracy using this method.
I fixed it by moving the farz closer to the viewport.

For me it worked as I was picking objects which fell in the front to mid-range.

It certainly improved accuracy.

SuperCraggs
02-17-2009, 04:12 PM
Scratt :

Cheers for the response. Doesn't work however. One quadrant of the output is warped, while the other side tallies perfectly. Changing the length of the frustum makes no difference.

Does anyone have any other ideas? :(

scratt
02-17-2009, 06:29 PM
Just one other thing.. Are you getting any errors back from gluUnProject?

It returns a bool.

From what I can see squeezing my eyes thorough the tiny code box in your post your implementation is right, so IMO it now is either something in the surrounding logic, or a fail in gluUnProject for some reason.

yooyo
02-18-2009, 02:42 AM
Above code should work.. Are you sure that you have correct eye position?

SuperCraggs
02-18-2009, 03:21 PM
Scratt : Cheers again for the response, but again, nope :(
The function returns true every single time.

I am going to post the code for the raytracer and the camera (because of the length)

If someone who knows a bit about raytracing can have a look through and make sure i've not done anything stupid. It might be the camera that is broken, it's always worked in opengl, but that's because I've always coped out and used GLUlookat :D

SuperCraggs
02-18-2009, 03:28 PM
I didn't want to post all my code, as it's quite long, but I've got a silly bug somewhere, and I just can't find it. I've spent a week debugging this badboy, to no avail.

As the image is coming out warped, I get the feeling that the error is somewhere in my camera code. If someone could have a quick looksie, that would be awesome.

You might need to cut and paste into notepad :)

Camera Code :



using System;
using CsGL.OpenGL;
using MathBox;
using System.IO;

namespace OpenGLWindow
{
public class Camera
{
Vector3 Eye = new Vector3();
Vector3 Target = new Vector3();
Vector3 Up = new Vector3();
Vector3 Equator = new Vector3();
Vector3 Centre = new Vector3();

private double Pitch, Yaw, Roll;

// access attributes
public Vector3 GetEye()
{
return new Vector3(this.Eye.x, this.Eye.y, this.Eye.z);
}
public Vector3 GetTarget()
{
return new Vector3(this.Target.x, this.Target.y, this.Target.z);
}

public double GetPitch() { return this.Pitch; }
public double GetYaw() { return this.Yaw; }

public Camera()
{
Yaw = 0.0;
Pitch = 0.0;

Eye = new Vector3(0.0, 1.0, -5.0);
Target = new Vector3();
}

public void Update()
{
const double RadToDeg = 57.2957795;

Target.x = Eye.x + Math.Sin(2 * Yaw / RadToDeg);
Target.z = Eye.z + Math.Cos(2 * Yaw / RadToDeg);
Target.y = Eye.y + Math.Sin(2 * Pitch / RadToDeg);

Up.x = Target.x - Eye.x;

Vector3 Temp = Target - Eye;
double Radius = Temp.Magnitude;

Equator = Vector3.GetCrossProduct(Temp, Up);
Centre = this.Eye + Target;

Up.y = Math.Abs(Eye.y + (Radius * Math.Sin(Pitch + Math.PI / 2)));
Up.z = Target.z - Eye.z;

GL.gluLookAt(Eye.x, Eye.y, Eye.z, Target.x, Target.y, Target.z, 0, 1, 0);
}

/// <summary>
/// Epic Hack! This uses the opengl world transformation pipeline to correctly create the ray. This will be
/// very expensive, but will have to do for now!
/// </summary>
/// <param name="xPixel">The x coordinate of the pixel we are drawing.</param>
/// <param name="yPixel">The y coordinate of the pixel we are drawing.</param>
/// <param name="mProjection">The current opengl projection matrix</param>
/// <param name="mModelView">The current opengl modelview matrix</param>
/// <param name="Viewport">The opengl viewport data</param>
/// <returns>A correctly constructed ray in opengl world coordinates</returns>
public Ray CreateRay(int xPixel, int yPixel)
{
Ray Ray;
//Stream s = File.Open("C:\\RTRACER_RayEqns.txt", FileMode.Append, FileAccess.Write, FileShare.None);
//StreamWriter Sw = new StreamWriter(s);

unsafe
{
Vector3 RayDirection;
Vector3 RayFar = new Vector3();
Vector3 RayNear = new Vector3();

double rx = 0;
double ry = 0;
double rz = 0;

double[] mProjection = new double[16];
double[] mModelView = new double[16];
int[] Viewport = new int[4];

// if anyone knows anything about cleaning this area up so it looks a bit nicer, that would be great :)
fixed (double* pMView = &amp;mModelView[0])
{
fixed (double* pProj = &amp;mProjection[0])
{
fixed (int* pViewport = &amp;Viewport[0])
{
// populate matricies and viewport data
GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX, mModelView);
GL.glGetDoublev(GL.GL_PROJECTION_MATRIX, mProjection);
GL.glGetIntegerv(GL.GL_VIEWPORT, Viewport);

// calculate ray direction by doing near plane intersection and far plane intersection
int Foo = GLU.gluUnProject((double)xPixel, (double)(Viewport[3]-1)-yPixel, 0.0, pMView, pProj, pViewport, &amp;rx, &amp;ry, &amp;rz);
RayNear = new Vector3(rx, ry, rz);

int Bar = GLU.gluUnProject((double)xPixel, (double)(Viewport[3] - 1) - yPixel, 1.0, pMView, pProj, pViewport, &amp;rx, &amp;ry, &amp;rz);
RayFar = new Vector3(rx, ry, rz);

// compute ray direction from far plane intersect - near plane, and remember to normalize!
RayDirection = RayFar - RayNear;
Vector3.Normalize(RayDirection);

// build ray from eye point as origin, raydirection as the ray point on the farplane - the point on the ray near plane.
Ray = new Ray(this.GetEye(), RayDirection);
}
}
}
}

return Ray;
}

public void RotateCamera(double deltax, double deltay)
{
Yaw -= 0.1 * deltax;
Pitch -= 0.1 * deltay;
}

public void DollyCamera(double zAmount)
{
Vector3 LocalZ = this.Target - this.Eye;

this.Target += LocalZ * zAmount;
this.Eye += LocalZ * zAmount;
}

public void StrafeCamera(double xAmount)
{
Vector3 LocalXAxis = Vector3.GetCrossProduct(Target - Eye, Up);

this.Eye += LocalXAxis * xAmount;
this.Target += LocalXAxis * xAmount;
}

/*
public Ray CreateRay(int xPixel, int yPixel, int ViewportWidth, int ViewportHeight)
{
double xp = xPixel * 1.0f / ViewportWidth * 2 - 1;
double yp = (yPixel+1) * 1.0f / (ViewportHeight) * 2 - 1;
yp = -yp;

Vector3 Pos = this.Centre - Up * yp - Equator * xp;
Vector3 Dir = Pos - Eye;

Vector3.Normalize(Dir);
Ray ray = new Ray(Pos, Dir);
return ray;
}
}
*/

}




Raytracer code. I'm going to dbl check i've implemented the ray-triangle intersection correctly, but if someone could have a look through in the meantime, this is driving me nuts.



using System;
using System.Collections.Generic;
using System.Drawing;
using MathBox;

namespace OpenGLWindow
{
public class Ray
{
public Vector3 Origin;
public Vector3 Direction;

public Ray(Vector3 Origin, Vector3 Direction)
{
this.Origin = Origin;
this.Direction = Direction;
}

public override string ToString()
{
return ("{Origin : " + Origin.ToString() + " Direction : " + Direction.ToString() + "}");
}
}

class Raytracer
{
private int ShadedPixels;
private int FrustumPixels;

public Raytracer()
{
ShadedPixels = 0;
FrustumPixels = 0;
}

public Color TraceRay(Ray R, List<Mesh> Scenegraph, List<Light> SceneLights)
{
Color FinalColour = new Color(0.0, 0.0, 0.0);
RayHitInfo Closest = IntersectRayScene(R, Scenegraph);

// no hit, fill with background colour
if (!Closest.Hit)
{
FrustumPixels++;
return new Color(0.5, 0.5, 0.5);
}
else
{
ShadedPixels++;
return Closest.Element.Material.DiffuseColour;
}
// got a hit, so shade-lambert diffuse;
// TODO : Shade ambient, specular

FinalColour = new Color(1.0, 1.0, 1.0);

//foreach (Light L in SceneLights)
//{
// FinalColour += L.DiffuseIntensity * L.DiffuseColour * (Vector3.DotProduct(Closest.Normal, L.Position));
//}

// shade ambient
// shade specular
// shade shadows

// spawn reflection
// spawn refraction

return FinalColour;
}

/// <summary>
/// Tests pixel ray against all meshes in scene. Returns the ray hit info corresponding to the nearest valid hit. Returns frustum colour if no hit.
/// </summary>
/// <param name="R"></param>
/// <param name="Scenegraph"></param>
/// <returns></returns>
private RayHitInfo IntersectRayScene(Ray R, List<Mesh> Scenegraph)
{
RayHitInfo Closest = new RayHitInfo();

foreach (Mesh M in Scenegraph)
{
RayHitInfo Current = this.RayMeshIntersection(R, M);

if ((Current.tDistance < Closest.tDistance) & Current.Hit)
{
Closest = Current;
Closest.Element = M;
}
}

return Closest;
}

/// <summary>
/// Raytracing intersection algorithm based on molleur-trombadore 1995 "Minimum storage ray-triangle intersection"
/// </summary>
/// <param name="R"></param>
/// <param name="M"></param>
/// <returns></returns>
public RayHitInfo RayMeshIntersection(Ray R, Mesh M)
{
// optimizations : BVH
// backfacing sort

// R.Direction.y = -R.Direction.y; // HACK : Something is wrong with the way the y coordinate is being generated.

RayHitInfo Hit = new RayHitInfo();
RayHitInfo Final = new RayHitInfo();

for (int p = 0; p != M.Index.Count; p += 9)
{
Triangle T = new Triangle(M.Vertex[M.Index[p]], M.Vertex[M.Index[p + 3]], M.Vertex[M.Index[p + 6]]);

// TODO : Profile the culling step
Hit = this.IntersectTriangle(R, T, false);

if (Hit.tDistance < Final.tDistance) // HIT.HIT
{
Final = Hit.Clone();
Final.TriangleHit = p;
}
}

return Final;
}

// molleur-trombdore (1995)
RayHitInfo IntersectTriangle(Ray Ray, Triangle T, bool Cull)
{
double Epsilion = 0.000001; // fudge number for extreme glancing ray hits, the ray is practically parallel to the plane

RayHitInfo Hit = new RayHitInfo();

// scalar determinants
double Determinant, InverseDeterminant; // inverse for double sided intersection testing

// triangle edges
Vector3 Edge1 = T.v1 - T.v0;
Vector3 Edge2 = T.v2 - T.v0;

// hit vectors
Vector3 tVec = new Vector3();
Vector3 pVec = new Vector3();
Vector3 qVec = new Vector3();

pVec = Vector3.GetCrossProduct(Ray.Direction, Edge2); // begin calculating determinant for u
Determinant = Vector3.DotProduct(Edge1, pVec); // if det approaches 0, ray lies in plane of triangle.

if (Cull)
{
if (Determinant < Epsilion)
return new RayHitInfo(); // ray glances triangle, return no hit;

// calculate distance from vertex 0 to the ray origin.
tVec = Ray.Origin - T.v0;

Hit.HitPoint.u = Vector3.DotProduct(tVec, pVec);

if (Hit.HitPoint.u < 0.0 || Hit.HitPoint.u > Determinant) // hit point is outside triangle;
return new RayHitInfo(); // return no hit.

// u parameter seems valid, now lets move onto v;
qVec = Vector3.GetCrossProduct(tVec, Edge1);
Hit.HitPoint.v = Vector3.DotProduct(Ray.Direction, qVec);

// test v parameter for acceptable barycentric intersection
if (Hit.HitPoint.v < 0.0 || (Hit.HitPoint.u + Hit.HitPoint.v) > Determinant)
return new RayHitInfo();

// ray must have hit triangle, so get the hit distance
Hit.tDistance = Vector3.DotProduct(Edge2, qVec);

InverseDeterminant = 1.0 / Determinant; // get inverse of determinant

Hit.Hit = true;
Hit.tDistance *= InverseDeterminant;
Hit.HitPoint.u *= InverseDeterminant;
Hit.HitPoint.v *= InverseDeterminant;
}
else
{
if (Determinant > -Epsilion &amp;&amp; Determinant < Epsilion)
return new RayHitInfo();

InverseDeterminant = 1.0 / Determinant;

tVec = Ray.Origin - T.v0;

// get U parameter and test bounds
Hit.HitPoint.u = Vector3.DotProduct(tVec, pVec) * InverseDeterminant;

// we are out of the bounds of the triangle, so no hit.
if (Hit.HitPoint.u < 0.0 || Hit.HitPoint.u > 1.0)
return new RayHitInfo();

// now we are ready to test the v parameter.
qVec = Vector3.GetCrossProduct(tVec, Edge1);

// get the v parameter
Hit.HitPoint.v = Vector3.DotProduct(Ray.Direction, qVec) * InverseDeterminant;

// if both hit points are in the acceptable range then ray intersects triangle
if (Hit.HitPoint.v < 0.0 || (Hit.HitPoint.u + Hit.HitPoint.v) > 1.0)
return new RayHitInfo();

// definite intersection, get distance to hit
Hit.Hit = true;
Hit.tDistance = Vector3.DotProduct(Edge2, qVec) * InverseDeterminant;
}

return Hit;
}

/// <summary>
/// HACK. Correctly renders an opengl scene. Ultimately, it will either need to transform all meshes, OR use a matrix based projection
/// system.
/// </summary>
/// <param name="Scenegraph"></param>
/// <param name="xRes"></param>
/// <param name="yRes"></param>
/// <param name="FrameBuffer"></param>
/// <param name="CurrentCamera"></param>
/// <param name="Scenelights"></param>
/// <param name="mProjection"></param>
/// <param name="mModelView"></param>
/// <param name="Viewport"></param>
public void RenderOpenGLScene(List<Mesh> Scenegraph, Color[,] Buffer, int Width, int Height, Camera CurrentCamera, List<Light> Scenelights)
{
int DEBUG_PixelsShaded = 0;
Decimal Percentage = 0;

for (int y = Height-1; y != -1; y--)
{
for (int x = 0; x != Width; x++)
{
if (Scenegraph.Count > 0)
{
Ray R = CurrentCamera.CreateRay(x, y);
//Ray DEBUGONLY = CurrentCamera.CreateRay(x, y, Width, Height);
Buffer[x,y] = this.TraceRay(R, Scenegraph, Scenelights);
}

DEBUG_PixelsShaded++;
decimal Det = ((decimal)Width * (decimal)Height) / DEBUG_PixelsShaded;
Percentage = 100 / Det;
Percentage = Decimal.Truncate(Percentage);
}
}
}
}
}

SuperCraggs
02-19-2009, 12:50 PM
I still can't fix it. I'm sure the problems are in the camera code. I use gluLookAt in the update function. That would be the only thing that would account for the projection and modelview mats being messed up, and therefore the ray calculations being done incorrectly.

Can anyone who knows what they are doing take a look at the camera code and see what I am doing wrong.

Why opengl can't just supply a [censored] camera class is beyond me. So we have hundreds of people who don't know what they are doing writing their own version, which seems to work, until they use it for something serious, just like what has happened to me :)

yooyo
02-19-2009, 04:39 PM
Uhm.. from youor code I see that you are calculate two points:
- On near plane
- on far plane

then you calc a vector between thos two points
farp - nearp

and then normalize. On the end you use that normalised vector as direction and eye pos as origin for ray.

But.. eye, near point and far point are colinear (on same ray), so you can calculate just near or just far point and use vector from eye to that point as ray direction.