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()