PDA

View Full Version : yet another nooby raytracer


rende
05-18-2007, 07:28 PM
I've been trying to code a raytracer on and off over the past few months. Problem is I jumped into the deep end ( basicly started coding on the thing the first time I touched c/c++).. Anyways, managed to get something out of it finally! :bounce:

http://fluentart.com/gallery/albums/scraps/Screenshot.jpg

Havent coded in image output yet, only drawing to screen using pixeltoaster (http://www.pixeltoaster.com/) as it renders so thats why your seeing a cropped render. At the moment all it can do is load OBJ files (with tris only, and a bit broken at the moment) and spit out the ray/tri intersection data (hit, distance, u and v). I decided to skip sphere raytracing alltogether (duno if that was a good idea in hindsight though, might have learnt faster if i did).

Just so you know what your looking at in the image.. I piped the u/v/distance data to the color channels.

outcol.a = 1;
outcol.r = nearU;
outcol.g = nearV;
outcol.b = nearT/2;

So.. the reason why Im posting. I'd like to help other c/c++ newbies by providing my source as i go along and ask questions when i get stuck. The only problem I have at the moment is with the obj loading.. the code im using is from the web, and as far as i can tell doesnt work 100%.. The only way I can get it to work is by adding a false v 0 0 0 entry at the top of the obj, and that fixes the vertex sequence.. if someone can have a look at the obj loading code and help me to fix this bug please...

sourcecode so far (using anjuta on debian, but compiles and runs with devc++ too )
http://fluentart.com/media/fluentray0.1.zip

obj.cpp

#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include "obj.h"

ObjModel* ObjLoadModel(char* memory, size_t size)
{
char* p = NULL, * e = NULL;
ObjModel* ret = (ObjModel*) calloc(1, sizeof(ObjModel));
memset(ret, 0, sizeof(ObjModel));

p = memory;
e = memory + size;

while (p != e)
{
if (memcmp(p, "vn", 2) == 0) ret->nNormal++;
else if (memcmp(p, "vt", 2) == 0) ret->nTexCoord++;
else if (memcmp(p, "v", 1) == 0) ret->nVertex++;
else if (memcmp(p, "f", 1) == 0) ret->nTriangle++;

while (*p++ != (char) 0x0A);
}

ret->VertexArray = (ObjVertex*) malloc(sizeof(ObjVertex) * ret->nVertex);
ret->NormalArray = (ObjNormal*) malloc(sizeof(ObjNormal) * ret->nNormal);
ret->TexCoordArray = (ObjTexCoord*) malloc(sizeof(ObjTexCoord) * ret->nTexCoord);
ret->TriangleArray = (ObjTriangle*) malloc(sizeof(ObjTriangle) * ret->nTriangle);

p = memory;

int nV = 0, nN = 0, nT = 0, nF = 0;

while (p != e)
{
if (memcmp(p, "vn", 2) == 0)
{
sscanf(p, "vn %f %f %f", &ret->NormalArray[nN].x,
&ret->NormalArray[nN].y,
&ret->NormalArray[nN].z);
nN++;
}
else if (memcmp(p, "vt", 2) == 0)
{
sscanf(p, "vt %f %f", &ret->TexCoordArray[nT].u,
&ret->TexCoordArray[nT].v);
nT++;
}
else if (memcmp(p, "v", 1) == 0) /* or *p == 'v' */
{
sscanf(p, "v %f %f %f", &ret->VertexArray[nV].x,
&ret->VertexArray[nV].y,
&ret->VertexArray[nV].z);
nV++;
}
else if (memcmp(p, "f", 1) == 0) /* or *p == 'f' */
{
sscanf(p, "f %d/%d/%d %d/%d/%d %d/%d/%d", &ret->TriangleArray[nF].Vertex[0],
&ret->TriangleArray[nF].TexCoord[0],
&ret->TriangleArray[nF].Normal[0],
&ret->TriangleArray[nF].Vertex[1],
&ret->TriangleArray[nF].TexCoord[1],
&ret->TriangleArray[nF].Normal[1],
&ret->TriangleArray[nF].Vertex[2],
&ret->TriangleArray[nF].TexCoord[2],
&ret->TriangleArray[nF].Normal[2]);


nF++;
}

while (*p++ != (char) 0x0A);
}

return ret;
}

size_t ObjLoadFile(char* szFileName, char** memory)
{
size_t bytes = 0;
FILE* file = fopen(szFileName, "rt");

if (file != NULL)
{
fseek(file, 0, SEEK_END);
size_t end = ftell(file);
fseek(file, 0, SEEK_SET);

*memory = (char*) malloc(end);
bytes = fread(*memory, sizeof(char), end, file);

fclose(file);
}

return bytes;
}


obj.h
#ifndef OBJ_H
#define OBJ_H

struct ObjVertex
{
float x, y, z;
};

typedef ObjVertex ObjNormal;

struct ObjTexCoord
{
float u, v;
};

struct ObjTriangle
{
int Vertex[3];
int Normal[3];
int TexCoord[3];
};

struct ObjModel
{
int nVertex, nNormal, nTexCoord, nTriangle;

ObjVertex* VertexArray;
ObjNormal* NormalArray;
ObjTexCoord* TexCoordArray;
ObjTriangle* TriangleArray;
};

ObjModel* ObjLoadModel(char*, size_t);
size_t ObjLoadFile(char*, char**);

#endif



and the main.cc parts..

char* memory = NULL;
size_t bytes = ObjLoadFile("cornell.obj", &memory);
ObjModel* model = ObjLoadModel(memory, bytes);

*snip*

for (int i = 0; i < model->nTriangle; i++) {

vert0[0] = (double) model->VertexArray[ model->TriangleArray[i].Vertex[0] ].x;
vert0[1] = (double) model->VertexArray[ model->TriangleArray[i].Vertex[0] ].y;
vert0[2] = (double) model->VertexArray[ model->TriangleArray[i].Vertex[0] ].z;
vert1[0] = (double) model->VertexArray[ model->TriangleArray[i].Vertex[1] ].x;
vert1[1] = (double) model->VertexArray[ model->TriangleArray[i].Vertex[1] ].y;
vert1[2] = (double) model->VertexArray[ model->TriangleArray[i].Vertex[1] ].z;
vert2[0] = (double) model->VertexArray[ model->TriangleArray[i].Vertex[2] ].x;
vert2[1] = (double) model->VertexArray[ model->TriangleArray[i].Vertex[2] ].y;
vert2[2] = (double) model->VertexArray[ model->TriangleArray[i].Vertex[2] ].z;

result = intersect_triangle(orig, dir, vert0, vert1, vert2, &t, &u, &v);

... *snip*

H3ro
05-18-2007, 10:50 PM
Hallo,

I started writing, but the forum did something stupid when I pressed one of the links I was trying to add here.

In short:
Cool to see your project, looks good so far. I am working on a raytracer as well, but looks like you have had more progress than me. If interested, you can see my raytracer a few threads down.
For help with loading object files, see this links:
Link1 (http://local.wasp.uwa.edu.au/%7Epbourke/dataformats/obj/)
Link2 (http://www.ultimategameprogramming.com/demoDownload.php?category=OpenGL&page=6)

Also, the way you are doing ray intersection looks a bit weird I think, so you might want to reconsider that part. (assuming thats what your doing in the main.cc you posted)

Good luck with development

rende
05-19-2007, 05:50 AM
hey, ye i saw your project.. thats why i posted mine here aswell :) thanks for the reply

Also, the way you are doing ray intersection looks a bit weird I think, so you might want to reconsider that part. (assuming thats what your doing in the main.cc you posted)

yeah its because im converting between different variabl e structures... very messy. Well I'll try and recode the obj loading part and see if i can get that silly bug sorted out. Thanks for the links btw.
Next is 3d transforms (rotation/scale etc.. of loaded objects and the camera) image output so i can start rendering animations. :scream:

Duno where to start with lighting though... i've read up a little on matrix transformations and vector math, but I guess i've been away from maths for too long after school.

H3ro
05-19-2007, 06:32 AM
Here is my code where I call the intersection routine. Hope it helps.


// Loop through all the primitives in the scene and return the
// the closest one
for (int currentPrimitive = 0; currentPrimitive <=totalPrimitves;
currentPrimitive++)
{
// Find the primitive we are currently testing against
Primitive* testObject = thisScene.getPrimitive(currentPrimitive);

// Check if the ray hits the current object
intersectionRes = testObject->intersectionTest(currentRay);

// If the ray hits something, check if its the closest object
if (intersectionRes.hit != 0)
{
// Find closest primitive by comparing distance
if (intersectionRes.distance < closestDistance )
{

closestDistance = intersectionRes.distance;
closestPrimitive = currentPrimitive;
}
}
}


Lighting is not that hard, all you have to do is to create a new ray and see it it hits any of the lights in the scene.

rende
05-19-2007, 09:11 AM
Wanted to quickly ask this.. from http://www.flipcode.com/articles/article_raytrace01.shtml i saw he normalizes the ray direction vector
vector3 o( 0, 0, -5 );
vector3 dir = vector3( m_SX, m_SY, 0 ) - o;
NORMALIZE( dir );
Ray r( o, dir );

I had a look at the source code..


#define DOT(A,B) (A.x*B.x+A.y*B.y+A.z*B.z)
#define NORMALIZE(A) {float l=1/sqrtf(A.x*A.x+A.y*A.y+A.z*A.z);A.x*=l;A.y*=l;A.z*=l;}


so what exactly is dot and normalize for?

rende
05-21-2007, 09:39 AM
Small update.. trying to recode some parts so i can implement recursive raytracing/lighting.

http://fluentart.com/gallery/albums/scraps/fr012render.jpg

H3ro
05-21-2007, 02:18 PM
Wow, nice update. Would not call that a small one, looks like you have done a lot of work sins your last post.

I am working on the object loading part of my raytracer. No matter what I do my object loader does not work

rende
05-21-2007, 03:56 PM
Try copying mine.. just load obj.h and obj.cpp and check in my main.cc how to use it. you have to add a v 0 0 0 at the top of you .obj file though, still havent fixed that.

yarniso
06-08-2007, 10:05 AM
That's probably because the indices start at 1 and not 0 for an .obj file (if my memory serves me well). So just subtracting 1 from all the indices you read should solve the "adding a vertex" issue.

rende
06-14-2007, 07:34 AM
Ye, i tried that. Its almost as if the parser skips the first entry. Subtracting one from the index causes the program to crash.

Havent had much time to work on this project since my last update.. :(

Robert Bateman
06-14-2007, 12:52 PM
so what exactly is dot and normalize for?

performs the dot product on 2 vectors, normally used to find the angle between them. Normalise is used to turn a vector into unit length vector. btw, I wouldn't recommend using macros, but if you do, make sure that you always check for a division by zero in the normalize routines. It frequently happens if you have degenerate triangles, then the floating point error stack costs you tonnes of CPU cycles.


#define DOT(A,B) (A.x*B.x+A.y*B.y+A.z*B.z)
#define NORMALIZE(A) {float l=DOT(A,A); l = l>0 ? 1.0f/sqrt(l) : 0;A.x*=l;A.y*=l;A.z*=l;}

Carina
06-21-2007, 05:18 PM
performs the dot product on 2 vectors, normally used to find the angle between them.

Or, as in this case, performing the dot product of a vector and itself gives you the length of that vector, squared. Hence, the square root of the dot product of a vector with itself gives you the length of the vector.

Dividing each component of the vector by its length gives you the normalised vector (i.e. a vector in the same direction as the original, but with length 1).

You see dot used a lot in graphics for this purpose as we are often interested in the squared length of a line connecting two points. While most libraries provide functions to find the length of a vector, they normally first find it's squared length, and returns its square root. Thusly, using "getLength()" to get the length, then multiplying it by itself, would computationally mean "compute the squared length, take it's square root and multiply it by itself".. Pretty pointless and unnecessarily expensive:)

Errr.. Haven't a clue if that last bit makes any sense to anyone else, but I know what I mean :D

UrbanFuturistic
06-22-2007, 12:33 AM
That was the complicated way of putting it, yes :p

The length of lines at an angle in 3D space are calculated using basic trigenometry, same as a triangle. just think of the line as the hypotenuse and dx, dy and dz as adjacent, opposite and.. other opposite. This means that as sqrt(a^2+o^2) = h, sqrt(x^2+y^2+z^2) = h.

I'd also advise dropping macros altogether if at all possible. Even if you're not going to be programming in proper C++, there are advantages to using a C++ compiler.

1) Any libraries you compile will interface without problems with C++ code.
2) String and iostream are just really useful for files and, um, strings.
3) Replace macros with inline functions, you do it like this:

inline int functionName(parameters) {function code}

The compiler takes care of the rest and it makes it much easier to debug as the compiler deals with the code as you see it and actually knows what's inline and what isn't. It's an absolute munter trying to deal with an error reported at the wrong line because half the code's only there after it's gone through the preprocessor.

4) Use consts or typedefs instead of #defines. eg.

typedef float GLfloat;
const double velocity;
GLfloat distance = 42.5

5) No-one programs in pure C anymore anyway, before the 1999 standard you couldn't even do for (int i = 0; i < j; i++); it had to be int i; for (i = 0; i < j; i++);

rende
06-26-2007, 08:24 AM
thanks for the feedback peeps!

I'm trying to code in shadows now, and im not quite sure if im going about it the right way. As I don't have the xyz position of where the camera rays hit whatever it renders, only the length of that ray. Should I get it by using the ray direction from the camera, using Pythagoras? Then cast rays from that position to each light?

Also, id like to implement some sort of area shadows/lights and bounced light. The only way I can think of that could work is to trace a hemisphere of rays at each bounce, and do away with point lights altogether using objects instead.

Any snippets that could help would be awesome :)

yarniso
06-26-2007, 09:02 AM
As you will know a ray is defined by r = o + t*d
You have the o (origin) and d (direction) as known values. Now you say you have the "length" of the ray. So I guess you mean you have the 't' value. If you put that into the ray equation above, you get the point where the ray hit geometry.
Then from that point you cast a ray in the direction of the light. If it hits anything before reaching the light then it's in shadow (for point-lights).

Hth.

playmesumch00ns
06-26-2007, 10:47 AM
This is probably about the time when you want to start thinking carefully about your interface rather than just coding straight ahead, as choices you make now could restrict you severely further down the line.

At the very least you're going to want:

o Some kind of Hit object that returns information about the hit surface. If you know how far along the ray the intrersection was found, then you know the point of intersection by the point of origin plus the ray direction times the hit distance.

o a Material object that describes the brdf of the surface you've intersected.

o a Light object that defines an interface for sampling and evaluating lights. Subclasses of this will define area lights, point lights and whatever else you want.

Area light sampling itself is fairly simple in the naive case. For example for a rectangular area light, just distribute a bunch of points in a rectangle using e.g. stratified sampling, then transform them to world space based on the transform of the light. Sum the radiance from each of them to trhe shading point multiplied by the visibility function (i.e. trace a shadow ray from the point on the light to the shading point), then divide the result by the number of samples and robert's your mother's brother.

CGTalk Moderation
06-26-2007, 10:47 AM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.