cornell_box.py

Created by nonogamer9

Created on December 12, 2025

4.79 KB

A Raytracing program that raytraces a Cornell Box on a NumWorks


import math, random, kandinsky as k

WIDTH, HEIGHT = 120, 90
LIGHT_SAMPLES, MAX_DEPTH = 6, 2
EPS = 1e-4
FOV = 1.0472  # ~pi/3
SHOW_PROGRESS = True

v_add = lambda a,b:(a[0]+b[0],a[1]+b[1],a[2]+b[2])
v_sub = lambda a,b:(a[0]-b[0],a[1]-b[1],a[2]-b[2])
v_mul = lambda a,s:(a[0]*s,a[1]*s,a[2]*s)
v_dot = lambda a,b:a[0]*b[0]+a[1]*b[1]+a[2]*b[2]

def v_len(a): return math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2])
def v_norm(a):
    L = v_len(a)
    return (a[0]/L,a[1]/L,a[2]/L) if L else (0,0,0)

def c255(x): return 0 if x<0 else (255 if x>255 else int(x))

walls = [
 {'point':(0,-1,0),'normal':(0,1,0),'color':(166,120,80),'reflect':0.08},
 {'point':(0,3,0),'normal':(0,-1,0),'color':(255,255,255),'reflect':0},
 {'point':(0,0,10),'normal':(0,0,-1),'color':(240,240,240),'reflect':0},
 {'point':(-2,0,0),'normal':(1,0,0),'color':(255,40,40),'reflect':0},
 {'point':(2,0,0),'normal':(-1,0,0),'color':(40,255,40),'reflect':0}
]

spheres = [
 {'center':(-0.8,-0.3,5.2),'radius':0.7,'color':(230,230,230),'specular':60,'reflect':0.2},
 {'center':(1,-0.2,7),'radius':0.9,'color':(150,180,255),'specular':80,'reflect':0.35}
]

light_pos = (0,2.8,4)
light_radius = 0.45

def is_sphere(o,d,s):
    oc = v_sub(o,s['center'])
    A = v_dot(d,d)
    B = 2*v_dot(oc,d)
    C = v_dot(oc,oc) - s['radius']*s['radius']
    D = B*B - 4*A*C
    if D < 0: return None
    sd = math.sqrt(D)
    t0 = (-B - sd)/(2*A)
    t1 = (-B + sd)/(2*A)
    t = None
    if t0>EPS and t1>EPS:
        t = t0 if t0<t1 else t1
    elif t0>EPS:
        t = t0
    elif t1>EPS:
        t = t1
    if t is None: return None
    h = v_add(o, v_mul(d,t))
    n = v_norm(v_sub(h,s['center']))
    return (t,h,n,s)

def is_plane(o,d,p):
    denom = v_dot(d,p['normal'])
    if abs(denom)<1e-6: return None
    t = v_dot(v_sub(p['point'],o),p['normal'])/denom
    if t<=EPS: return None
    h = v_add(o,v_mul(d,t))
    return (t,h,p['normal'],p)

def trace(o,d):
    ac = (0,0,0)
    co = 1
    ro, rd = o, d
    for depth in range(MAX_DEPTH+1):
        nt=1e9; hp=hn=ho=None
        for s in spheres:
            r = is_sphere(ro,rd,s)
            if r and r[0]<nt:
                nt,hp,hn,ho=r
        for w in walls:
            r = is_plane(ro,rd,w)
            if r and r[0]<nt:
                nt,hp,hn,ho=r
        if nt==1e9 or ho is None: break
        base = ho['color']
        if ho is walls[0]:
            px,pz = hp[0],hp[2]
            g = math.sin(px*8)*0.5+0.5
            t = 1-0.3*((math.sin(pz*2)*0.5)+0.5)
            base = (c255(base[0]*t),c255(base[1]*t*(0.8+0.2*g)),c255(base[2]*(0.7+0.3*g)))
        cr, cg, cb = base[0]*0.12, base[1]*0.12, base[2]*0.12
        dr=dg=db=0
        for _ in range(LIGHT_SAMPLES):
            lx = light_pos[0] + (random.random()-0.5)*light_radius
            ly = light_pos[1] + (random.random()-0.5)*light_radius
            lz = light_pos[2] + (random.random()-0.5)*light_radius
            L = v_sub((lx,ly,lz),hp)
            ld = v_len(L)
            L = v_norm(L)
            so = v_add(hp,v_mul(L,EPS))
            blk=False
            for s2 in spheres:
                h2 = is_sphere(so,L,s2)
                if h2 and h2[0]<ld:
                    blk=True
                    break
            if blk: continue
            for w2 in walls:
                h2 = is_plane(so,L,w2)
                if h2 and h2[0]<ld:
                    blk=True
                    break
            if blk: continue
            nd = v_dot(hn,L)
            if nd>0:
                df = nd*1.6
                dr += base[0]*df; dg += base[1]*df; db += base[2]*df
        if LIGHT_SAMPLES>0:
            cr += dr/LIGHT_SAMPLES; cg += dg/LIGHT_SAMPLES; cb += db/LIGHT_SAMPLES
        if 'specular' in ho:
            Lc = v_norm(v_sub(light_pos,hp))
            R = v_sub(v_mul(hn,2*v_dot(hn,Lc)),Lc)
            sp = ho['specular']
            sc = max(0,v_dot(R,v_mul(rd,-1)))**(sp*0.02+1)
            sm = 255*sc
            cr += sm; cg += sm; cb += sm
        ac = (ac[0]+co*cr, ac[1]+co*cg, ac[2]+co*cb)
        rf = ho['reflect']
        if rf>0 and depth<MAX_DEPTH:
            nd=v_dot(hn,rd)
            rd=v_norm(v_sub(rd,v_mul(hn,2*nd)))
            ro=v_add(hp,v_mul(rd,EPS*10))
            co*=rf
        else: break
    r,g,b=ac[0]/255,ac[1]/255,ac[2]/255
    r,g,b=max(0,r),max(0,g),max(0,b)
    r=c255(255*(r**(1/2.2))); g=c255(255*(g**(1/2.2))); b=c255(255*(b**(1/2.2)))
    return (r,g,b)

def render():
    ox=(320-WIDTH)//2; oy=(222-HEIGHT)//2
    for y in range(HEIGHT):
        if SHOW_PROGRESS: k.set_pixel(ox, oy+y, (255,0,0))
        ty = 1 - 2*(y+0.5)/HEIGHT
        for x in range(WIDTH):
            tx = 2*(x+0.5)/WIDTH - 1
            sx = tx*math.tan(FOV/2)*(WIDTH/HEIGHT)
            sy = ty*math.tan(FOV/2)
            c = trace((0,0,0),v_norm((sx,sy,1)))
            k.set_pixel(int(ox+x), int(oy+y), c)
    k.set_pixel(ox, oy, (0,255,0))

render()

During your visit to our site, NumWorks needs to install "cookies" or use other technologies to collect data about you in order to:

With the exception of Cookies essential to the operation of the site, NumWorks leaves you the choice: you can accept Cookies for audience measurement by clicking on the "Accept and continue" button, or refuse these Cookies by clicking on the "Continue without accepting" button or by continuing your browsing. You can update your choice at any time by clicking on the link "Manage my cookies" at the bottom of the page. For more information, please consult our cookies policy.