# Voxel-based Ray Tracing
# No refraction for simplicity!
# Single point light source for simplicity!
# FB36 - 20130829
import math
from PIL import Image
imgx = 400; imgy = 400 ; imgz = 400 # voxel-box size
image = Image.new("RGB", (imgx, imgy))
pixels = image.load()
print "Creating voxels..."
# each voxel can have RGB color
voxelRGB = [[[(0, 0, 0) for x in range(imgx)] for y in range(imgy)] for z in range(imgz)]
# each voxel can have an opacity coefficient 0 or 1 (for simplicity)
opacity = [[[0 for x in range(imgx)] for y in range(imgy)] for z in range(imgz)]
# each voxel can have a 3d normal unit vector (for reflections/refractions)
normal = [[[(0.0, 0.0, 0.0) for x in range(imgx)] for y in range(imgy)] for z in range(imgz)]
# each voxel can have a reflectivity coefficient between 0 and 1
reflectivity = [[[0.0 for x in range(imgx)] for y in range(imgy)] for z in range(imgz)]
eye = (imgx / 2.0, 250.0, -100.0)
light = (- 10.0, -10.0, -10.0)
ambientBrightness = 0.3 # between 0 and 1
# cx, cy, cz: center; r: radius (in voxels)
# rc: reflectivity coefficient between 0 and 1
def CreateMirrorSphere(cx, cy, cz, r, colorRGB, rc):
print "Creating mirror sphere..."
# sphere is set of voxels which have distance = r to center
for z in range(imgz):
for y in range(imgy):
for x in range(imgx):
dx = x - cx
dy = y - cy
dz = z - cz
d = math.sqrt(dx * dx + dy * dy + dz * dz)
if abs(d - r) < 1.0:
voxelRGB[z][y][x] = colorRGB
opacity[z][y][x] = 1
normal[z][y][x] = (dx / d, dy / d, dz / d)
reflectivity[z][y][x] = rc
def CreateCheckerboardFloor(sqrX, sqrZ, colorRGB0, colorRGB1):
print "Creating checkerboard floor..."
for z in range(imgz):
for x in range(imgx):
if int(x / sqrX) % 2 != int(z / sqrZ) % 2: # xor
voxelRGB[z][imgy - 1][x] = colorRGB0
else:
voxelRGB[z][imgy - 1][x] = colorRGB1
opacity[z][imgy - 1][x] = 1
normal[z][imgy - 1][x] = (0.0, -1.0, 0.0)
def CombineColors(c0, c1, w):
return (int(round(c0[0] + (c1[0] - c0[0]) * w)),
int(round(c0[1] + (c1[1] - c0[1]) * w)),
int(round(c0[2] + (c1[2] - c0[2]) * w)))
# 3D Reflection (all vectors are unit vectors)
def Reflection(normalVector, incidentRayVector):
(nx, ny, nz) = normalVector
(ix, iy, iz) = incidentRayVector
dotProduct = nx * ix + ny * iy + nz * iz
rx = ix - 2 * dotProduct * nx
ry = iy - 2 * dotProduct * ny
rz = iz - 2 * dotProduct * nz
return (rx, ry, rz)
def LightIntensity(surfaceVoxelCoordinates, surfaceNormalVector):
(svx, svy, svz) = surfaceVoxelCoordinates
(nx, ny, nz) = surfaceNormalVector
(Lx, Ly, Lz) = light
dx = Lx - svx
dy = Ly - svy
dz = Lz - svz
d = math.sqrt(dx * dx + dy * dy + dz * dz)
dx = dx / d; dy = dy / d; dz = dz / d # unit vector towards light
cosT = nx * dx + ny * dy + nz * dz
if cosT < 0.0: cosT = 0.0 # the surface faces away from the light source
return cosT
def ShadowIntensity(rayX, rayY, rayZ):
(Lx, Ly, Lz) = light
dx = Lx - rayX
dy = Ly - rayY
dz = Lz - rayZ
d = math.sqrt(dx * dx + dy * dy + dz * dz)
dx = dx / d; dy = dy / d; dz = dz / d # unit vector towards light
while True: # shadow ray tracing
rayX += dx; rayY += dy; rayZ += dz # move the ray by 1 voxel
rayXint = int(round(rayX))
rayYint = int(round(rayY))
rayZint = int(round(rayZ))
# if ray goes outside of the voxel-box
if rayXint < 0 or rayXint > imgx - 1 \
or rayYint < 0 or rayYint > imgy - 1 \
or rayZint < 0 or rayZint > imgz - 1:
return 1.0
# if ray hits an object
if opacity[rayZint][rayYint][rayXint] == 1:
# stop tracing here for simplicity
return 0.0
# if ray hits the light source
vx = Lx - rayX
vy = Ly - rayY
vz = Lz - rayZ
d = math.sqrt(vx * vx + vy * vy + vz * vz)
if d < 1.0:
return 1.0
# Ray Tracer (traces the ray and returns an RGB color)
def RayTrace(rayX, rayY, rayZ, dx, dy, dz):
brightness = 1.0 # the ray has full brightness at the beginning
color = (0, 0, 0)
while True:
rayX += dx; rayY += dy; rayZ += dz # move the ray by 1 voxel
rayXint = int(round(rayX))
rayYint = int(round(rayY))
rayZint = int(round(rayZ))
# if ray goes outside of the voxel-box
if rayXint < 0 or rayXint > imgx - 1 \
or rayYint < 0 or rayYint > imgy - 1 \
or rayZint < 0 or rayZint > imgz - 1:
return color
# if ray hits an object
if opacity[rayZint][rayYint][rayXint] == 1:
# if ray hits a non-reflective object
if reflectivity[rayZint][rayYint][rayXint] == 0.0:
brightness *= ShadowIntensity(rayX, rayY, rayZ)
brightness *= LightIntensity((rayX, rayY, rayZ), \
normal[rayZint][rayYint][rayXint])
brightness = brightness * (1.0 - ambientBrightness) + ambientBrightness
color = CombineColors(color, voxelRGB[rayZint][rayYint][rayXint], brightness)
return color
# if ray hits a reflective object
if reflectivity[rayZint][rayYint][rayXint] > 0.0:
brightness *= reflectivity[rayZint][rayYint][rayXint]
brightnessTemp = ShadowIntensity(rayX, rayY, rayZ)
brightnessTemp *= LightIntensity((rayX, rayY, rayZ), \
normal[rayZint][rayYint][rayXint])
brightnessTemp = brightnessTemp * (1.0 - ambientBrightness) + ambientBrightness
colorTemp = voxelRGB[rayZint][rayYint][rayXint]
colorTemp = (int(colorTemp[0] * brightnessTemp), \
int(colorTemp[1] * brightnessTemp), \
int(colorTemp[2] * brightnessTemp))
color = CombineColors(color, colorTemp, \
1.0 - reflectivity[rayZint][rayYint][rayXint])
(dx, dy, dz) = Reflection(normal[rayZint][rayYint][rayXint], (dx, dy, dz))
def CreateScene():
print "Creating scene..."
CreateCheckerboardFloor(20, 20, (255, 255, 0), (0, 0, 255))
CreateMirrorSphere(imgx / 2.0, imgy / 2.0, imgz / 2, 150.0, (0, 255, 0), 0.8)
def RenderScene():
print "Rendering scene..."
for ky in range(imgy):
print str(100 * ky / (imgy - 1)).zfill(3) + "%"
for kx in range(imgx):
dx = kx - eye[0]
dy = ky - eye[1]
dz = 0.0 - eye[2]
d = math.sqrt(dx * dx + dy * dy + dz * dz)
dx = dx / d; dy = dy / d; dz = dz / d # ray unit vector
pixels[kx, ky] = RayTrace(kx, ky, 0, dx, dy, dz)
# MAIN
CreateScene()
RenderScene()
image.save("Voxel_Based_Ray_Tracing.png", "PNG")