1
+ ; ;;;;;;;;;;;;;;;;;
2
+ ; ; Ray Tracer ;;
3
+ ; ;;;;;;;;;;;;;;;;;
4
+
5
+ (defstruct surface color) ; [1]
6
+
7
+ (defparameter *world* nil ) ; [2]
8
+ (defconstant eye (make-point :x 0 :y 0 :z 200 )) ; [3] <- to move eye,
9
+ ; change this.
10
+ (defun tracer (pathname &optional (res 1 )) ; [4]
11
+ (with-open-file (p pathname :direction :output )
12
+ (format p " P2 ~A ~A 255" (* res 100 ) (* res 100 )) ; [5]
13
+ (let ((inc (/ res))) ; [6]
14
+ (do ((y -50 (+ y inc))) ; [7]
15
+ ((< (- 50 y) inc)) ; [8]
16
+ (do ((x -50 (+ x inc))) ; [9]
17
+ ((< (- 50 x) inc))
18
+ (print (color-at x y) p)))))) ; <- all we need to do is print the pixel color.
19
+
20
+ (defun color-at (x y) ; [10]
21
+ (multiple-value-bind (xr yr zr)
22
+ (unit-vector (- x (x eye)) ; <- dir. from eye to pixel. we'll trace
23
+ (- y (y eye)) ; back from eye to source, through pixel.
24
+ (- 0 (z eye)))
25
+ (round (* (sendray eye xr yr zr) 255 )))) ; [11]
26
+
27
+ (defun sendray (pt xr yr zr) ; [12]
28
+ (multiple-value-bind (s hit-pt) (first-hit pt xr yr zr)
29
+ (if s ; [13]
30
+ (multiple-value-bind (xs ys zs) (unit-vector 0 0 -200 ) ; [14] <- to place light source
31
+ (* (lambert s hit-pt xr yr zr) (surface-color s))) ; [15] elsewhere than at eye
32
+ 0 ))) ; [16] (but see note 14).
33
+
34
+ (defun first-hit (pt xr yr zr) ; [17]
35
+ (let (surface hit-pt dist) ; <- collect these vals for closest point ; [18]
36
+ (dolist (s *world* ) ; [19]
37
+ (let ((h (intersect s pt xr yr zr))) ; [20]
38
+ (when h ; <- current s was hit by the ray
39
+ (let ((d (distance h pt))) ; <- distance from hit-pt to eye
40
+ (when (or (null dist) (< d dist)) ; <- if closest so far
41
+ (setf surface s hit-pt h dist d)))))) ; <- update all hit pt vars
42
+ (values surface hit-pt))) ; [21]
43
+
44
+ (defun lambert (s int xs ys zs) ; [22]
45
+ (multiple-value-bind (xn yn zn) (normal s int)
46
+ (max 0 (+ (* xs xn) (* ys yn) (* zs zn)))))
47
+
48
+ ; ; A Ray Tracer is the rendering algorithm deluxe; yields the best images, but takes the most time.
49
+ ; ; Benefits: easy to get real optical effects, defined using geometric objects, easy to implement.
50
+
51
+ ; ; This Ray Tracer
52
+ ; ; - black and white images
53
+ ; ; - single light source at same position as eye
54
+ ; ; - hence look like flash photographs
55
+ ; ; Hence, Optional Improvements
56
+ ; ; - move the light source [x]
57
+ ; ; - add another light source [ ] (harder)
58
+
59
+
60
+ ; ; [1] We'll program the tracer to this interface, then define specific surfaces later.
61
+ ; ; All we need to establish that interface is surface-color, which here is a gray level.
62
+ ; ; [2] We'll put surfaces in here and iterate through it to trace.
63
+ ; ; [3] Eye is a point set back from the image plane; image plane is coincident with the x y plane.
64
+ ; ; This means eye will only see objects which have negative z coordinates.
65
+ ; ; Q. Why is an image plane alone not enough? Why do we need an eye if the eye is just a point?
66
+ ; ; A. Find a window - on the glass surface is the image we'll construct. Walk toward then away
67
+ ; ; away, and notice how the image changes. With eye closer to the image plane, more of the
68
+ ; ; world is seen, which also means objects on the fixed size image (the glass) become smaller.
69
+ ; ; If you move back, less of world is seen, & those objects which are seen become larger.
70
+ ; ; Thus, a vantage point is necessary to allow us compute what to display. No vantage point, no image.
71
+ ; ; An image is here constructed by considering all the rays of light passing from the world to
72
+ ; ; the position of the eye, but is captured at the point those rays hit the image plane.
73
+ ; ; Your eye itself is similar but has some differences. There, the vantage point, your retina, is fixed.
74
+ ; ; Q. Must the eye always lie on the central axis?
75
+ ; ; A. No. The plane is transparent. Movement of the eye in x y directions simply means you see
76
+ ; ; different regions of the world, whereas z direction affects only image scale.
77
+ ; ; Again, you can do this with a window, and think about the image on the glass pane.
78
+ ; ; Note, the axes in the diagram in the book are wrong, need to reverse the directions,
79
+ ; ; i.e. use negative values to get the directions indicated in the book on each axis.
80
+ ; ; [4] Go pixel by pixel along the image plane, tracing the light back into the simulated world.
81
+ ; ; Here we simply get the color of the pixel with color-at, and write that to a file.
82
+ ; ; Hence, the entire rest of the program is in terms of 'getting the color of a pixel'.
83
+ ; ; Calls color-at, which is all we need to find our way into the rest of the program.
84
+ ; ; [5] Param res is image plane size in hundreds of pixels square.
85
+ ; ; The filetype is a simple ascii format called pgm.
86
+ ; ; The header is as per format here, P2 type, breadth, height & max int size per pixel.
87
+ ; ; Hence, 100x100 will be 10k ints of max 256 (white).
88
+ ; ; [6] (/ res) is same as (/ 1 res)
89
+ ; ; [7] var init update; y from -50, incd by 1/res
90
+ ; ; [8] test; at standard res, test passes until y reaches 50; then it fails, ending loop.
91
+ ; ; [9] Same for each x within y; tracing an xy plane.
92
+
93
+ ; ; [10] To get a color at a point on the plane, we define a unit vector in the direction
94
+ ; ; of that point on the plane from the eye, then send a ray in that direction.
95
+ ; ; sendray will ultimately return a color. in ray tracing we trace back from the eye.
96
+ ; ; multiple-value-bind binds the values returned from its first arg, for use later,
97
+ ; ; like let; allows three values to be passed w/o any premature container.
98
+ ; ; [11] sendray returns a value in (0,1), here we round it to 8 bit, 0-255.
99
+ ; ; [12] Get the first hit surface or none, and the intersect point, and if a surface was hit,
100
+ ; ; compute the amount of light to return & return it, else, return 0, for no light.
101
+ ; ; Could call this from pixel position instead of eye position; should work same. try it.
102
+ ; ; [13] Surface was hit on way back to light source.
103
+ ; ; [14] If want to move light source to position other than at the eye, compute a unit vector
104
+ ; ; as desired here and pass it to lambert. Otherwise, for light at the eye, pass xr yr zr.
105
+ ; ; Vector must be from outside the plane, i.e. -ve z coord, or no reflection is possible
106
+ ; ; (and here we only calculate reflection, not lightsource itself or transparency).
107
+ ; ; *BUT*, while this roughly works, shouldn't lambert direction also depend on pixel?
108
+ ; ; PROGRESS STEP: pencil & paper diagrams of what's happening.
109
+ ; ; [15] Amount of light is lambert coefficient times surface's intrinsic color; reflectivity.
110
+ ; ; [16] Background color, convention black.
111
+ ; ; [17] Get the surface if any our ray tracing back to the source hits first, along with point if so.
112
+ ; ; Just like sendray, this takes point and direction. No transparency, hence want only first hit.
113
+ ; ; This is a brute method of course.
114
+ ; ; [18] Initialise all three to nil. Dist to hold d during update, & be overwritten if smaller d found.
115
+ ; ; [19] To find first-hit, we use a slow brute method for now.
116
+ ; ; Consider all surfaces in world.
117
+ ; ; [20] Intersect finds the solutions to the eqn. of that surface that also satisfy line of the ray.
118
+ ; ; If ray goes in and out other side, there will be two roots. In that case, the min root is
119
+ ; ; the nearest. Hence, intersect uses minroot. If another surface in world also intersects,
120
+ ; ; it will only win if it's the nearer.
121
+ ; ; [21] Return closest hit point of closest surface, or nil nil if no hits.
122
+ ; ; [22] To get intensity of the ray, we use lamberts law. That means dot product
123
+ ; ; of unit normal vector and the unit vector in direction of light source.
124
+ ; ; Originaly program uses light source at eye, in which case use the ray itself.
125
+ ; ; We also added an option to use a different ray, in [14] above.
126
+ ; ; [23] Wonder if this file might be neater with docstrings than comments, in time.
0 commit comments