Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 287 lines (261 sloc) 7.921 kb
c4bf767d »
2010-08-18 * working snapshot
1 # raytracer -
2 # $Id: raytracer.rb 2010-08-18 20:48:53 nineties $
3
4 class Vector
5 def initialize(x, y, z)
6 @x = x
7 @y = y
8 @z = z
9 end
10 def length
11 Math.sqrt(@x * @x + @y * @y + @z * @z)
12 end
13 def normalize
14 l = 1 / length()
15 @x *= l
16 @y *= l
17 @z *= l
18 self
19 end
20 def + (v)
21 Vector.new(@x + v.x, @y + v.y, @z + v.z)
22 end
23 def - (v)
24 Vector.new(@x - v.x, @y - v.y, @z - v.z)
25 end
26 def * (t)
27 if t.kind_of?(Vector) then
28 Vector.new(@x * t.x, @y * t.y, @z * t.z)
29 else
30 Vector.new(@x * t, @y * t, @z * t)
31 end
32 end
33 attr_accessor :x, :y, :z
34 end
35
36 def iprod(u, v)
37 u.x * v.x + u.y * v.y + u.z * v.z
38 end
39
40 Color = Vector
41
42 # objects
43 class Material
44 def initialize(color, diffuse = 0.0, reflection = 0.0, refraction = 0.0, specular = -1.0)
45 @color = color
46 @diffuse = diffuse
47 @reflection = reflection
48 @refraction = refraction
49 @specular = specular
50 @specular = 1.0 - @diffuse if specular < 0
51 end
52 attr_accessor :color, :diffuse, :reflection, :refraction, :specular
53 end
54
55 class Ray
56 def initialize(origin, direction, distance = Float::MAX)
57 @origin = origin
58 @direction = direction.normalize
59 @distance = distance
60 @inside = false
61 @hit = nil
62 end
63 def terminal
64 @origin + @direction * @distance
65 end
66 attr_accessor :origin, :direction, :distance, :inside, :hit
67 end
68
69 class Primitive
70 attr_accessor :material
71 end
72
73 class Sphere < Primitive
74 def initialize(mat, center, radius)
75 @material = mat
76 @center = center
77 @radius = radius
78 end
79 def intersect(ray)
80 c = @center - ray.origin
81 cd = iprod(c, ray.direction)
82 det = @radius * @radius - iprod(c, c) + cd * cd
83 if det > 0 then
84 det = Math.sqrt(det)
85 d1 = cd + det
86 d2 = cd - det
87 return if d1 < 0
88 if d2 < 0 then
89 if d1 < ray.distance then
90 ray.distance = d1
91 ray.hit = self
92 ray.inside = true
93 end
94 else
95 if d2 < ray.distance then
96 ray.distance = d2
97 ray.hit = self
98 ray.inside = false
99 end
100 end
101 end
102 end
103 def normal(point)
104 n = point - @center
105 n.normalize
106 end
107 attr_reader :center, :radius
108 end
109
110 class Plane < Primitive
111 def initialize(mat, normal, point)
112 @material = mat
113 @normal = normal.normalize
114 @const = iprod(normal, point)
115 end
116 def intersect(ray)
117 d = iprod(@normal, ray.direction)
118 if d != 0 then
119 l = (@const - iprod(ray.origin, @normal))/d
120 return nil if l < 0
121 if l < ray.distance then
122 ray.distance = l
123 ray.hit = self
124 ray.inside = false
125 end
126 end
127 end
128 def normal(point)
129 @normal
130 end
131 end
132
133 class Light < Sphere
134 def initialize(color, center, radius)
135 super(Material.new(color), center, radius)
136 end
137 end
138
139 class Raytracer
140 def initialize
141 @window_width = 800
142 @window_height = 600
143 @view_width = 7.0
144 @view_height = 5.25
145
146 @origin = Vector.new(0.0, 0.0, -5.0)
147 @primitives = []
148 end
149 attr_accessor :window_width, :window_height, :view_width, :view_height
150 attr_accessor :origin
151
152 def setup_mapping
153 @dx = @view_width / @window_width
154 @dy = @view_height / @window_height
155 @left = -@view_width/2
156 @top = @view_height/2
157 end
158
159 def << (prim)
160 @primitives << prim
161 end
162
163 def render
164 setup_mapping
165 print "P3\n#{@window_width} #{@window_height}\n255\n"
166 y = @top
167 @window_height.times do
168 x = @left
169 @window_width.times do
170 dir = Vector.new(x, y, 0.0) - @origin
171 ray = Ray.new(@origin, dir)
172 color = trace(ray, 1, 1.0)
173 if color then
174 r = (color.x * 255).to_i
175 g = (color.y * 255).to_i
176 b = (color.z * 255).to_i
177 r = 255 if r > 255
178 g = 255 if g > 255
179 b = 255 if b > 255
180 print "#{r} #{g} #{b} "
181 else
182 print "0 0 0 "
183 end
184 x += @dx
185 end
186 print "\n"
187 y -= @dy
188 end
189 end
190
191 DEPTH_MAX = 6
192 EPSILON = 0.0001
193 def trace(ray, depth, cur_refr)
194 return nil if depth > DEPTH_MAX
195 ray.hit = nil
196 @primitives.each do |prim|
197 prim.intersect(ray)
198 end
199 return nil unless ray.hit
200 prim = ray.hit
201 if prim.kind_of?(Light) then
202 return Color.new(1.0, 1.0, 1.0)
203 else
204 color = Color.new(0.0, 0.0, 0.0)
205
206 # interaction point
207 ip = ray.terminal
208
209 # handle lights
210 @primitives.each do |light|
211 next unless light.kind_of?(Light)
212
213 l = light.center - ip
214 r = Ray.new(ip + l * EPSILON, l, l.length)
215
216 shade = 1.0
217 @primitives.each do |prim|
218 next if prim == light
219 prim.intersect(r)
220 if r.hit then
221 shade = 0.0
222 break
223 end
224 end
225
226 n = prim.normal(ip)
227 l.normalize
228
229 # diffuse shading
230 if prim.material.diffuse > 0 then
231 d = iprod(n, l)
232 if d > 0 then
233 diffuse = d * prim.material.diffuse * shade
234 color += prim.material.color * light.material.color * diffuse
235 end
236 end
237 # specular
238 if prim.material.specular > 0 then
239 r = l - n * 2.0 * iprod(l, n)
240 d = iprod(ray.direction, r)
241 if d > 0 then
242 specular = d**20 * prim.material.specular * shade
243 color += light.material.color * specular
244 end
245 end
246 end
247
248 # reflection
249 refl = prim.material.reflection
250 if refl > 0 then
251 n = prim.normal(ip)
252 r = ray.direction - n * 2.0 * iprod(ray.direction, n)
253 c = trace(Ray.new(ip + r * EPSILON, r), depth + 1, cur_refr)
254 color += c * prim.material.color * refl if c
255 end
256
257 # refraction
258 refr = prim.material.refraction
259 if refr > 0 then
260 n = cur_refr / refr
261 norm = prim.normal(ip)
262 norm *= -1 if ray.inside
263 cosI = iprod(norm, ray.direction)
264 cosT2 = 1.0 - n*n*(1.0 - cosI * cosI)
265 if cosT2 > 0 then
266 t = ray.direction * n + norm * (n * cosI - Math.sqrt(cosT2))
267 ray = Ray.new(ip + t * EPSILON, t)
268 c = trace(ray, depth + 1, refr)
269 if c then
270 # Beer's law
271 absorbance = prim.material.color * 0.15 * (-ray.distance)
272 transparency = Color.new(
273 Math.exp(absorbance.x),
274 Math.exp(absorbance.y),
275 Math.exp(absorbance.z)
276 )
277 color += transparency * c
278 end
279 end
280 end
281
282 color
283 end
284 end
285 end
286
Something went wrong with that request. Please try again.