luaplayer.org
January 27, 2012, 11:00:10 AM *
Welcome, Guest. Please login or register.

Login with username, password and session length
News: IMPORTANT: Please read the following post http://luaplayer.org/forums/index.php?topic=693
 
   Home   Help Search Login Register  
Pages: [1] 2
  Print  
Author Topic: Ray Tracer  (Read 3119 times)
Sean.K.Brennan
Full Member
***
Posts: 137


View Profile Email
« on: January 15, 2010, 05:20:31 PM »

Saturday, January 16, 2010  2.00PM  -  The first screen shot!
Hi everyone, if you don't know me, I occasionally drop by the forum to have a look and see what's going on with PGE lua, and am a bit of a graphics nut as you may have seen this post (http://forums.luaplayer.org/index.php?topic=680.0). However, more recently I've been getting help with my newest project. As of about 4 days ago I began work on the first PSP-lua ray tracer that I know of which has entailed learning lua and some small parts of PGE (mainly the pge.texture object).
So I'm going to use this effectively as a blog for the project and how it goes, posting my problems, screens and advancements. First of all, I'll give you a quick run down on what a ray tracer is and what it does. You will most likely have heard of rasterized graphics, a vast majority of real time games that are 3D and even 2D will use rasterized graphics because it is a very fast approximation algorithm that can render millions of primitives many times a second. However, it has many downfalls, the main one being physical inaccuracy of what actually occurs in the real word with light. Ray tracing follows more closely to what light actually does in a scene by following rays from the eye out into a scene and bouncing these rays of primitives in the scene to light sources. You can get more information about ray tracing here if you'd like to know: http://www.devmaster.net/articles/raytracing_series/part1.php
I'd like to make this very clear, I am not using any 3D interfaces through lua, I'm not using OpenGLor Gu (pretty much the same), I am coding all the mathematics myself, and only use the pge.texture object to save and display the final image. Additionally, I am not aiming for a real time ray tracer, this is hard enough on a PC, IBM recently did some development on a real time ray tracer which required 3 PS3's to run.
So, let the fun begin! So here we are, I have detected at each pixel wheather or not a ray has intersected the sphere (solved geometrically rather than algebraically where x^2 + y^2 + z^2 = r^2 and solving the cubic, just can't be bothered and it's far too slow). If there was an intersection, write a red pixel, otherwise write a black one, and hey presto!



A 100x100 pixel square with a circle... Not very interesting I have to admit, but I'm working on that! So here's the code that rendered that:


Code
GeSHi (lua):
screenWidth = 100
screenHeight = 100
 
screen = pge.texture.create(screenWidth, screenHeight, PGE_PIXEL_FORMAT_8888, PGE_VRAM)
 
red = pge.gfx.createcolor(255, 0, 0)
black = pge.gfx.createcolor(0, 0, 0)
white = pge.gfx.createcolor(255, 255, 255)
 
function vector(x, y, z)
return { x = x or 0, y = y or 0, z = z or 0 }
end
 
function ray()
return { direction = vector(), position =  vector() }
end
 
function sphere(centre, radius)
return { centre = vector(centre.x, centre.y, centre.z), radius = radius or 1 }
end
 
function traceResult()
return { hasHit = false, distance = 1000000 }
end
 
function generateRayPositionTable()
positions = { }
 
for i = 1, (screenWidth * screenHeight) do
positions[i] = 0
end
 
for y = 0, screenHeight do
for x = 0, screenWidth do
currentPosition = vector()
currentPosition.x = (x - (screenWidth  / 2))/(screenWidth  / 2)
currentPosition.y = (y - (screenHeight / 2))/(screenHeight / 2)
currentPosition.z = 0
 
positions[x + y * screenWidth] = currentPosition
end
end
 
return positions
end
 
function normalise(vec)
length = 1 / pge.math.sqrt((vec.x * vec.x) + (vec.y * vec.y) + (vec.z * vec.z))
 
vec.x = vec.x * length
vec.y = vec.y * length
vec.z = vec.z * length
end
 
function vectorSubtract(vector1, vector2)
return vector(vector1.x - vector2.x, vector1.y - vector2.y, vector1.z - vector2.z)
end
 
function dotProduct(vector1, vector2)
return  ((vector1.x * vector2.x) + (vector1.y * vector2.y) + (vector1.z * vector2.z))
end
 
function crossProduct(vector1, vector2)
CrossProduct = vector(vector1.y * vector2.z - vector1.z * vector1.y, vector1.z * vector2.x - vector1.x * vector2.z, vector1.x * vector2.y - vector1.y * vector2.x)
return CrossProduct
end
 
function raySphereCollision(Ray, Sphere, TraceResult)
rayToSphereCentre = vector(Sphere.centre.x - Ray.position.x, Sphere.centre.y - Ray.position.y, Sphere.centre.z - Ray.position.z)
lengthRTCS2 = dotProduct(rayToSphereCentre, rayToSphereCentre)
 
closestApproach = dotProduct(rayToSphereCentre, Ray.direction)
if closestApproach < 0 then
TraceResult.hasHit = false
return
end
 
halfCord2 = (Sphere.radius * Sphere.radius) - lengthRTCS2 + (closestApproach * closestApproach)
if halfCord2 < 0 then
TraceResult.hasHit = false
return
end
 
t = closestApproach - pge.math.sqrt(halfCord2)
 
if t < TraceResult.distance - 0.0000001 and t > 0.0000001 then
TraceResult.hasHit = true
return
end
 
t2 = closestApproach + pge.math.sqrt(halfCord2)
 
if t2 < TraceResult.distance - 0.0000001 and t > 0.0000001 then
TraceResult.hasHit = true
return
end
end
 
 
while pge.running() do
pge.gfx.startdrawing()
pge.gfx.clearscreen()
 
screen:activate()
 
lightLocation = vector(0, 0, -1)
positionTable = generateRayPositionTable()
Ray = ray()
TraceResult = traceResult()
 
sphereCenter = vector(0.0, 0.0, -3.0)
Sphere = sphere(sphereCenter, 1.0)
 
for y = 0, screenHeight do
for x = 0, screenWidth do
Ray.position.x = positionTable[x + y * screenWidth].x
Ray.position.y = positionTable[x + y * screenWidth].y
Ray.position.z = positionTable[x + y * screenWidth].z
 
pinHole = vector(0.0, 0.0, -1.0)
 
Ray.direction.x = pinHole.x - Ray.position.x
Ray.direction.y = pinHole.y - Ray.position.y
Ray.direction.z = pinHole.z - Ray.position.z
normalise(Ray.direction)
 
raySphereCollision(Ray, Sphere, TraceResult)
 
if TraceResult.hasHit then
pge.texture.pixel(screen, x, y, red)
 
else
pge.texture.pixel(screen, x, y, black)
end
end
end
 
screen:draweasy(0,0)
 
if pge.controls.pressed(PGE_CONTROL_START) then
screen:save("screen.png")
end
 
pge.gfx.enddrawing()
pge.gfx.swapbuffers()
end
 
screen = nil
Created by GeSHI 1.0.7.20


I would also like to thank Criptych at this stage, without his helping me with my lua, I wouldn't have got this far. And that's all for this post, feel free to ask questions  Smiley


Friday, January 2010  2:43 PM  -  Diffuse Shading
Hi again people. I've just (finally) got diffuse shading implemented into the tracer, so without further ado;



Diffuse shading is pretty simple in theory. You first take the normal of the surface that you hit (a vector perpendicular to the surface) and shortest line from the light source location to the point of intersection. You then calculate the angle between the two vectors. If the normal and light direction are going in opposite ways, then that point will be full illuminated, whereas, if the light direction and normal are going in the same direction, it is safe to assume that that point on the object has no light on it at all. Another way to think about it is that as the angle between the normal and the light approaches 90 degrees, the surface will have less light hitting it as more of the normal is exposed, this is represented quite well in the image below;



Well, next on the agenda is rendering multiple spheres with multiple lights, bye for now =]

Code
GeSHi (lua):
function ShadeSphere(Sphere, Ray, distance, lightLocation)
intersection = vector()
intersection.x = Ray.position.x + distance * Ray.direction.x
intersection.y = Ray.position.y + distance * Ray.direction.y
intersection.z = Ray.position.z + distance * Ray.direction.z
 
normal = vector()
oneOverRadius = 1/Sphere.radius
 
normal.x = (intersection.x - Sphere.centre.x) * oneOverRadius
normal.y = (intersection.y - Sphere.centre.y) * oneOverRadius
normal.z = (intersection.z - Sphere.centre.z) * oneOverRadius
 
lightDirection = vector()
lightDirection = vectorSubtract(intersection, lightLocation)
normalise(lightDirection)
 
lightCoef = dotProduct(lightDirection, vector(-(normal.x), -(normal.y), -(normal.z)))
if lightCoef < 0 then
lightCoef = 0
end
 
return pge.gfx.createcolor((255 * lightCoef), 0, 0)
end
Created by GeSHI 1.0.7.20

Monday, April 5th 2010 - Triangles

I've been doing some additional work on the tracer and have begun to incorporate rendering for more common primitives, among them, the triangle. Whether you like it or not, if you wan to program graphics you're gonna have to learn to love the triangle, it has the potential to make up any primitive imaginable and with normal interpolation, even curved surfaces. Alright so I won't go into the math of ray triangle intersection unless anyone is particularly interested... So here's a screen of it in action:



Now, something you may notice about this image versus the other images above is the dimensions of it, I'll explain this further in my next post about shadows! Chao.

Update - Rendering multiple primitives and Shadows
I've worked on it a little more and no less than an hour later I've come up with a few more things as mentioned in the title so here's the screens:

Rendering multiple primitives



This process consists (unoptimised) of tracing every primitive for every pixel, the taking the material of the object that returned the shortest trace distance from the camera to point of intersection. And now shadows:


Shadows are very simple. When you find the shortest point of intersection, you then trace a ray from the point of intersection to your light source (we assume at this stage that there is only one light). If that ray intersects any other object on the way to the light, then we know that this point is in shadow, so we assign it the ambient colour of the object that was hit with the primary ray.
Another trend you may have been noticing thus far is the size of the images continues to increase. I have changed the rendering algorithm slightly so that rather than attempting to render the image in brute force, the image size (for example 1000x1000 pixels) is cut into numerous smaller pieces, in this case a series of 100x100 images which are separated into their own co-routine/threads and stitched together to create the final image which is saved to the memory stick. This avoids over use of system memory, and more importantly the fact that the PSP would probably crash if you tried to store a 1000x1000 pixel image.
« Last Edit: April 05, 2010, 01:51:48 AM by Sean.K.Brennan » Logged
Sean.K.Brennan
Full Member
***
Posts: 137


View Profile Email
« Reply #1 on: January 15, 2010, 05:20:43 PM »

Reserved.
Logged
Sean.K.Brennan
Full Member
***
Posts: 137


View Profile Email
« Reply #2 on: January 15, 2010, 05:20:51 PM »

Reserved.
Logged
Sean.K.Brennan
Full Member
***
Posts: 137


View Profile Email
« Reply #3 on: January 15, 2010, 05:21:01 PM »

Reserved.
Logged
Sean.K.Brennan
Full Member
***
Posts: 137


View Profile Email
« Reply #4 on: January 15, 2010, 05:21:09 PM »

Reserved.
Logged
MetroDyne
Newbie
*
Posts: 45


View Profile Email
« Reply #5 on: January 22, 2010, 09:57:45 AM »

This project looks pretty cool.  Grin I've always been interested in raytracers myself, but unfortunately i suck at math so there a no go for me  Cry
Hope to see more of this as it gets mature!
Logged
Sean.K.Brennan
Full Member
***
Posts: 137


View Profile Email
« Reply #6 on: January 23, 2010, 04:54:01 PM »

The math for ray tracing isn't too difficult, mainly just vectors and some algebra. I'm currently working on diffuse shading however I've been taking a short vacation and will get back to it in a few days. Thanks for the support =]
Logged
Sean.K.Brennan
Full Member
***
Posts: 137


View Profile Email
« Reply #7 on: January 28, 2010, 05:51:00 PM »

Updated: Friday, January 2010  2:43 PM  -  Diffuse Shading
Logged
TheUnderminer
Hero Member
*****
Posts: 500


View Profile
« Reply #8 on: January 29, 2010, 05:00:47 AM »

Care to post render times? Should be fun. In your explanation of diffuse shading, I assume the camera has the same orientation as the light source?
Logged
Sean.K.Brennan
Full Member
***
Posts: 137


View Profile Email
« Reply #9 on: January 29, 2010, 01:51:07 PM »

I would love to post some render times, but whenever I try to print values to the screen, they appear to be in strange block patterns, not sure why, any help would be appreciated. Diffuse shading does not take into account the direction or location of the eye, therefor, they are very seperate objects.
First you find the camera is effectively set up in the main loop via this line:

pinHole = vector(0.0, 0.0, -1.0)


What it does, is create a point in space in which every ray must first pass through, it's named pinHole because it's a pin hole camera as illustrated below. This also means that at this stage the image is actually upside down.



However, the light source is given as

lightLocation = vector(0, 0, -1)

This is the same as the location of the pin hole only by coincidence, I can chose to move both the pinHole and the lightLocation as I wish, for example, here's a screen with the pinhole at (0, 0, -0.5). Now, there's a larger field of view as the point in which the rays pass through is much closer to the origin positions, so we get less intersections, and hence a smaller sphere:



Alright, so now I'm going to move the pinhole back to (0,0,-1), the sphere is located at (0,0,-3) (remeber that we are looking down the z plane) and now I will move the light location to (1.5, 0, -3). This gives the following image:



now, ordinarily, moving the light along the positive x axis would have light the right side, but because of the pin hole, the rays originate from the right, but move left as they have to go through the pin hole.
The most common component of lighting that relies on the cameras location and direction is called specular light (or specular reflection or specular highlight) which is the shiny white bit that you see on some objects, like pool balls. Hope that cleared something up for you Smiley

EDIT: Oh and I forgot to previously add the code for the diffuse lighting, it is now appended to the main post.
Logged
Criptych
Global Moderator
Hero Member
*****
Posts: 804


Why, oh why, didn't I take the blue pill?


View Profile WWW
« Reply #10 on: January 30, 2010, 08:16:40 AM »

whenever I try to print values to the screen, they appear to be in strange block patterns
Did you activate the font before trying to print with it?

If necessary you could also write to a log file or (with PSPLink) the console, instead of the screen.
Logged

I want to change the world—but no one will give me the source code!
FFN Browser (Genesis) | Nanotank! PSP (WIP) | Google Maps PSP
Sean.K.Brennan
Full Member
***
Posts: 137


View Profile Email
« Reply #11 on: January 31, 2010, 11:53:02 AM »

Haven't tried calling activate because documentation said it was activated by default :p guess I should take a closer look, thanks.

Alright, found the problem and with a single diffuse lit sphere 100x100 I'm getting 2.936 seconds as an average render time.
« Last Edit: January 31, 2010, 12:25:59 PM by Sean.K.Brennan » Logged
Criptych
Global Moderator
Hero Member
*****
Posts: 804


Why, oh why, didn't I take the blue pill?


View Profile WWW
« Reply #12 on: January 31, 2010, 03:28:41 PM »

I don't think anything is activated "by default"...

Sounds like the time's not bad at all (consdering it's a raytracer in Lua Tongue). Very cool. Smiley
Logged

I want to change the world—but no one will give me the source code!
FFN Browser (Genesis) | Nanotank! PSP (WIP) | Google Maps PSP
Sean.K.Brennan
Full Member
***
Posts: 137


View Profile Email
« Reply #13 on: January 31, 2010, 03:56:20 PM »

There's a whole lot of room for optimization further along the track, but at this stage I'm focusing on functionality. Thanks Smiley
Logged
Sean.K.Brennan
Full Member
***
Posts: 137


View Profile Email
« Reply #14 on: March 16, 2010, 02:01:15 AM »

Hey everyone, it's been a while since my last update. I've recently had to prioritise some things in my life and sadly the tracer has taken a step back, I plan on resuming it in the summer holidays (which is around December for me). Thanks Smiley
Logged
Pages: [1] 2
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.11 | SMF © 2006-2008, Simple Machines LLC Valid XHTML 1.0! Valid CSS!