www.pudn.com > raytracer4.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, int a_ID ) :  
	m_Origin( a_Origin ),  
	m_Direction( a_Dir ), 
	m_ID( a_ID ) 
{ 
} 
 
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; 
	// precalculate 1 / size of a cell (for x, y and z) 
	m_SR.x = GRIDSIZE / m_Scene->GetExtends().GetSize().x; 
	m_SR.y = GRIDSIZE / m_Scene->GetExtends().GetSize().y; 
	m_SR.z = GRIDSIZE / m_Scene->GetExtends().GetSize().z; 
	// precalculate size of a cell (for x, y, and z) 
	m_CW = m_Scene->GetExtends().GetSize() * (1.0f / GRIDSIZE); 
} 
 
// ----------------------------------------------------------- 
// Engine::FindNearest 
// Finds the nearest intersection in a regular gird for a ray 
// ----------------------------------------------------------- 
int Engine::FindNearest( Ray& a_Ray, float& a_Dist, Primitive*& a_Prim ) 
{ 
	int retval = MISS; 
	vector3 raydir, curpos; 
	Box e = m_Scene->GetExtends(); 
	curpos = a_Ray.GetOrigin(); 
	raydir = a_Ray.GetDirection(); 
	// setup 3DDDA (double check reusability of primary ray data) 
	vector3 cb, tmax, tdelta, cell; 
	cell = (curpos - e.GetPos()) * m_SR; 
	int stepX, outX, X = (int)cell.x; 
	int stepY, outY, Y = (int)cell.y; 
	int stepZ, outZ, Z = (int)cell.z; 
	if ((X < 0) || (X >= GRIDSIZE) || (Y < 0) || (Y >= GRIDSIZE) || (Z < 0) || (Z >= GRIDSIZE)) return 0; 
	if (raydir.x > 0) 
	{ 
		stepX = 1, outX = GRIDSIZE; 
		cb.x = e.GetPos().x + (X + 1) * m_CW.x; 
	} 
	else  
	{ 
		stepX = -1, outX = -1; 
		cb.x = e.GetPos().x + X * m_CW.x; 
	} 
	if (raydir.y > 0.0f) 
	{ 
		stepY = 1, outY = GRIDSIZE; 
		cb.y = e.GetPos().y + (Y + 1) * m_CW.y;  
	} 
	else  
	{ 
		stepY = -1, outY = -1; 
		cb.y = e.GetPos().y + Y * m_CW.y; 
	} 
	if (raydir.z > 0.0f) 
	{ 
		stepZ = 1, outZ = GRIDSIZE; 
		cb.z = e.GetPos().z + (Z + 1) * m_CW.z; 
	} 
	else  
	{ 
		stepZ = -1, outZ = -1; 
		cb.z = e.GetPos().z + Z * m_CW.z; 
	} 
	float rxr, ryr, rzr; 
	if (raydir.x != 0) 
	{ 
		rxr = 1.0f / raydir.x; 
		tmax.x = (cb.x - curpos.x) * rxr;  
		tdelta.x = m_CW.x * stepX * rxr; 
	} 
	else tmax.x = 1000000; 
	if (raydir.y != 0) 
	{ 
		ryr = 1.0f / raydir.y; 
		tmax.y = (cb.y - curpos.y) * ryr;  
		tdelta.y = m_CW.y * stepY * ryr; 
	} 
	else tmax.y = 1000000; 
	if (raydir.z != 0) 
	{ 
		rzr = 1.0f / raydir.z; 
		tmax.z = (cb.z - curpos.z) * rzr;  
		tdelta.z = m_CW.z * stepZ * rzr; 
	} 
	else tmax.z = 1000000; 
	// start stepping 
	ObjectList* list = 0; 
	ObjectList** grid = m_Scene->GetGrid(); 
	a_Prim = 0; 
	// trace primary ray 
	while (1) 
	{ 
		list = grid[X + (Y << GRIDSHFT) + (Z << (GRIDSHFT * 2))]; 
		while (list) 
		{ 
			Primitive* pr = list->GetPrimitive(); 
			int result; 
			if (pr->GetLastRayID() != a_Ray.GetID()) if (result = pr->Intersect( a_Ray, a_Dist ))  
			{ 
				retval = result; 
				a_Prim = pr; 
				goto testloop; 
			} 
			list = list->GetNext(); 
		} 
		if (tmax.x < tmax.y) 
		{ 
			if (tmax.x < tmax.z) 
			{ 
				X = X + stepX; 
				if (X == outX) return MISS; 
				tmax.x += tdelta.x; 
			} 
			else 
			{ 
				Z = Z + stepZ; 
				if (Z == outZ) return MISS; 
				tmax.z += tdelta.z; 
			} 
		} 
		else 
		{ 
			if (tmax.y < tmax.z) 
			{ 
				Y = Y + stepY; 
				if (Y == outY) return MISS; 
				tmax.y += tdelta.y; 
			} 
			else 
			{ 
				Z = Z + stepZ; 
				if (Z == outZ) return MISS; 
				tmax.z += tdelta.z; 
			} 
		} 
	} 
testloop: 
	while (1) 
	{ 
		list = grid[X + (Y << GRIDSHFT) + (Z << (GRIDSHFT * 2))]; 
		while (list) 
		{ 
			Primitive* pr = list->GetPrimitive(); 
			int result; 
			if (pr->GetLastRayID() != a_Ray.GetID()) if (result = pr->Intersect( a_Ray, a_Dist ))  
			{ 
				a_Prim = pr; 
				retval = result; 
			} 
			list = list->GetNext(); 
		} 
		if (tmax.x < tmax.y) 
		{ 
			if (tmax.x < tmax.z) 
			{ 
				if (a_Dist < tmax.x) break; 
				X = X + stepX; 
				if (X == outX) break; 
				tmax.x += tdelta.x; 
			} 
			else 
			{ 
				if (a_Dist < tmax.z) break; 
				Z = Z + stepZ; 
				if (Z == outZ) break; 
				tmax.z += tdelta.z; 
			} 
		} 
		else 
		{ 
			if (tmax.y < tmax.z) 
			{ 
				if (a_Dist < tmax.y) break; 
				Y = Y + stepY; 
				if (Y == outY) break; 
				tmax.y += tdelta.y; 
			} 
			else 
			{ 
				if (a_Dist < tmax.z) break; 
				Z = Z + stepZ; 
				if (Z == outZ) break; 
				tmax.z += tdelta.z; 
			} 
		} 
	} 
	return retval; 
} 
 
// ----------------------------------------------------------- 
// 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 
	if (!(result = FindNearest( a_Ray, a_Dist, prim ))) return 0; 
	// handle intersection 
	if (prim->IsLight()) 
	{ 
		// we hit a light, stop tracing 
		a_Acc = prim->GetMaterial()->GetColor(); 
	} 
	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->GetNrLights(); l++ ) 
		{ 
			Primitive* light = m_Scene->GetLight( l ); 
			// 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); 
				Primitive* pr = 0; 
				FindNearest( Ray( pi + L * EPSILON, L, ++m_CurID ), tdist, pr ); 
				if (pr != light) shade = 0; 
			} 
			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, ++m_CurID ), 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, ++m_CurID ), 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 ); 
	// reset ray id counter 
	m_CurID = 0; 
} 
 
// ----------------------------------------------------------- 
// Engine::RenderRay 
// Helper function, fires one ray in the regular grid 
// ----------------------------------------------------------- 
Primitive* Engine::RenderRay( vector3 a_ScreenPos, Color& a_Acc ) 
{ 
	Box e = m_Scene->GetExtends(); 
	vector3 o( 0, 0, -5 ); 
	vector3 dir = a_ScreenPos - o; 
	NORMALIZE( dir ); 
	Color acc( 0, 0, 0 ); 
	Ray r( o, dir, ++m_CurID ); 
	// advance ray to scene bounding box boundary 
	if (!e.Contains( o )) 
	{ 
		float bdist = 10000.0f; 
		if (e.Intersect( r, bdist )) r.SetOrigin( o + (bdist + EPSILON) * dir ); 
	} 
	float dist; 
	return Raytrace( r, a_Acc, 1, 1.0f, dist ); 
} 
 
// ----------------------------------------------------------- 
// 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 ); 
	Box e = m_Scene->GetExtends(); 
	// 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 ); 
			Primitive* prim = RenderRay( vector3( m_SX, m_SY, 0 ), acc ); 
			int red, green, blue; 
			if (prim != lastprim) 
			{ 
				lastprim = prim; 
				RenderRay( vector3( m_SX - m_DX / 2, m_SY, 0 ), acc ); 
				RenderRay( vector3( m_SX, m_SY - m_DY / 2, 0 ), acc ); 
				RenderRay( vector3( m_SX - m_DX / 2, m_SY - m_DY / 2, 0 ), acc ); 
				red = (int)(acc.r * 64); 
				green = (int)(acc.g * 64); 
				blue = (int)(acc.b * 64); 
				if (red > 255) red = 255; 
				if (green > 255) green = 255; 
				if (blue > 255) blue = 255; 
			} 
			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