www.pudn.com > raytracer3.zip > raytracer.cpp
// -----------------------------------------------------------
// raytracer.cpp
// 2004 - Jacco Bikker - jacco@bik5.com - www.bik5.com - <><
// -----------------------------------------------------------
#include "raytracer.h"
#include "scene.h"
#include "common.h"
#include "windows.h"
#include "winbase.h"
namespace Raytracer {
Ray::Ray( vector3& a_Origin, vector3& a_Dir ) :
m_Origin( a_Origin ),
m_Direction( a_Dir )
{
}
Engine::Engine()
{
m_Scene = new Scene();
}
Engine::~Engine()
{
delete m_Scene;
}
// -----------------------------------------------------------
// Engine::SetTarget
// Sets the render target canvas
// -----------------------------------------------------------
void Engine::SetTarget( Pixel* a_Dest, int a_Width, int a_Height )
{
// set pixel buffer address & size
m_Dest = a_Dest;
m_Width = a_Width;
m_Height = a_Height;
}
// -----------------------------------------------------------
// Engine::Raytrace
// Naive ray tracing: Intersects the ray with every primitive
// in the scene to determine the closest intersection
// -----------------------------------------------------------
Primitive* Engine::Raytrace( Ray& a_Ray, Color& a_Acc, int a_Depth, float a_RIndex, float& a_Dist )
{
if (a_Depth > TRACEDEPTH) return 0;
// trace primary ray
a_Dist = 1000000.0f;
vector3 pi;
Primitive* prim = 0;
int result;
// find the nearest intersection
for ( int s = 0; s < m_Scene->GetNrPrimitives(); s++ )
{
Primitive* pr = m_Scene->GetPrimitive( s );
int res;
if (res = pr->Intersect( a_Ray, a_Dist ))
{
prim = pr;
result = res; // 0 = miss, 1 = hit, -1 = hit from inside primitive
}
}
// no hit, terminate ray
if (!prim) return 0;
// handle intersection
if (prim->IsLight())
{
// we hit a light, stop tracing
a_Acc = Color( 1, 1, 1 );
}
else
{
// determine color at point of intersection
pi = a_Ray.GetOrigin() + a_Ray.GetDirection() * a_Dist;
// trace lights
for ( int l = 0; l < m_Scene->GetNrPrimitives(); l++ )
{
Primitive* p = m_Scene->GetPrimitive( l );
if (p->IsLight())
{
Primitive* light = p;
// handle point light source
float shade = 1.0f;
if (light->GetType() == Primitive::SPHERE)
{
vector3 L = ((Sphere*)light)->GetCentre() - pi;
float tdist = LENGTH( L );
L *= (1.0f / tdist);
Ray r = Ray( pi + L * EPSILON, L );
for ( int s = 0; s < m_Scene->GetNrPrimitives(); s++ )
{
Primitive* pr = m_Scene->GetPrimitive( s );
if ((pr != light) && (pr->Intersect( r, tdist )))
{
shade = 0;
break;
}
}
}
if (shade > 0)
{
// calculate diffuse shading
vector3 L = ((Sphere*)light)->GetCentre() - pi;
NORMALIZE( L );
vector3 N = prim->GetNormal( pi );
if (prim->GetMaterial()->GetDiffuse() > 0)
{
float dot = DOT( L, N );
if (dot > 0)
{
float diff = dot * prim->GetMaterial()->GetDiffuse() * shade;
// add diffuse component to ray color
a_Acc += diff * prim->GetMaterial()->GetColor() * light->GetMaterial()->GetColor();
}
}
// determine specular component
if (prim->GetMaterial()->GetSpecular() > 0)
{
// point light source: sample once for specular highlight
vector3 V = a_Ray.GetDirection();
vector3 R = L - 2.0f * DOT( L, N ) * N;
float dot = DOT( V, R );
if (dot > 0)
{
float spec = powf( dot, 20 ) * prim->GetMaterial()->GetSpecular() * shade;
// add specular component to ray color
a_Acc += spec * light->GetMaterial()->GetColor();
}
}
}
}
}
// calculate reflection
float refl = prim->GetMaterial()->GetReflection();
if ((refl > 0.0f) && (a_Depth < TRACEDEPTH))
{
vector3 N = prim->GetNormal( pi );
vector3 R = a_Ray.GetDirection() - 2.0f * DOT( a_Ray.GetDirection(), N ) * N;
Color rcol( 0, 0, 0 );
float dist;
Raytrace( Ray( pi + R * EPSILON, R ), rcol, a_Depth + 1, a_RIndex, dist );
a_Acc += refl * rcol * prim->GetMaterial()->GetColor();
}
// calculate refraction
float refr = prim->GetMaterial()->GetRefraction();
if ((refr > 0) && (a_Depth < TRACEDEPTH))
{
float rindex = prim->GetMaterial()->GetRefrIndex();
float n = a_RIndex / rindex;
vector3 N = prim->GetNormal( pi ) * (float)result;
float cosI = -DOT( N, a_Ray.GetDirection() );
float cosT2 = 1.0f - n * n * (1.0f - cosI * cosI);
if (cosT2 > 0.0f)
{
vector3 T = (n * a_Ray.GetDirection()) + (n * cosI - sqrtf( cosT2 )) * N;
Color rcol( 0, 0, 0 );
float dist;
Raytrace( Ray( pi + T * EPSILON, T ), rcol, a_Depth + 1, rindex, dist );
// apply Beer's law
Color absorbance = prim->GetMaterial()->GetColor() * 0.15f * -dist;
Color transparency = Color( expf( absorbance.r ), expf( absorbance.g ), expf( absorbance.b ) );
a_Acc += rcol * transparency;
}
}
}
// return pointer to primitive hit by primary ray
return prim;
}
// -----------------------------------------------------------
// Engine::InitRender
// Initializes the renderer, by resetting the line / tile
// counters and precalculating some values
// -----------------------------------------------------------
void Engine::InitRender()
{
// set firts line to draw to
m_CurrLine = 20;
// set pixel buffer address of first pixel
m_PPos = m_CurrLine * m_Width;
// screen plane in world space coordinates
m_WX1 = -4, m_WX2 = 4, m_WY1 = m_SY = 3, m_WY2 = -3;
// calculate deltas for interpolation
m_DX = (m_WX2 - m_WX1) / m_Width;
m_DY = (m_WY2 - m_WY1) / m_Height;
m_SY += m_CurrLine * m_DY;
// allocate space to store pointers to primitives for previous line
m_LastRow = new Primitive*[m_Width];
memset( m_LastRow, 0, m_Width * 4 );
}
// -----------------------------------------------------------
// Engine::Render
// Fires rays in the scene one scanline at a time, from left
// to right
// -----------------------------------------------------------
bool Engine::Render()
{
// render scene
vector3 o( 0, 0, -5 );
// initialize timer
int msecs = GetTickCount();
// reset last found primitive pointer
Primitive* lastprim = 0;
// render remaining lines
for ( int y = m_CurrLine; y < (m_Height - 20); y++ )
{
m_SX = m_WX1;
// render pixels for current line
for ( int x = 0; x < m_Width; x++ )
{
// fire primary rays
Color acc( 0, 0, 0 );
vector3 dir = vector3( m_SX, m_SY, 0 ) - o;
NORMALIZE( dir );
Ray r( o, dir );
float dist;
Primitive* prim = Raytrace( r, acc, 1, 1.0f, dist );
int red, green, blue;
if (prim != lastprim)
{
lastprim = prim;
Color acc( 0, 0, 0 );
for ( int tx = -1; tx < 2; tx++ ) for ( int ty = -1; ty < 2; ty++ )
{
vector3 dir = vector3( m_SX + m_DX * tx / 2.0f, m_SY + m_DY * ty / 2.0f, 0 ) - o;
NORMALIZE( dir );
Ray r( o, dir );
float dist;
Primitive* prim = Raytrace( r, acc, 1, 1.0f, dist );
}
red = (int)(acc.r * (256 / 9));
green = (int)(acc.g * (256 / 9));
blue = (int)(acc.b * (256 / 9));
}
else
{
red = (int)(acc.r * 256);
green = (int)(acc.g * 256);
blue = (int)(acc.b * 256);
}
if (red > 255) red = 255;
if (green > 255) green = 255;
if (blue > 255) blue = 255;
m_Dest[m_PPos++] = (red << 16) + (green << 8) + blue;
m_SX += m_DX;
}
m_SY += m_DY;
// see if we've been working to long already
if ((GetTickCount() - msecs) > 100)
{
// return control to windows so the screen gets updated
m_CurrLine = y + 1;
return false;
}
}
// all done
return true;
}
}; // namespace Raytracer