Welcome, guest | Sign In | My Account | Store | Cart
# 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")

History