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.phpI'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:
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 = nilCreated 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
Friday, January 2010 2:43 PM - Diffuse ShadingHi 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 =]
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)
endCreated by GeSHI 1.0.7.20
Monday, April 5th 2010 - TrianglesI'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 ShadowsI'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.