Projects

Ticket #711: ao-render-gcd.rb

File ao-render-gcd.rb, 6.2 KB (added by nagachika00@…, 2 years ago)

AO Bench GCD version with Dispatch::Job

Line 
1#  -*- encoding: utf-8 -*-
2# AO render benchmark (GCD version for MacRuby 0.6)
3# Original program (C) Syoyo Fujita in Javascript (and other languages)
4#      http://lucille.atso-net.jp/blog/?p=642
5#      http://lucille.atso-net.jp/blog/?p=711
6# Ruby(yarv2llvm) version by Hideki Miura
7#      http://github.com/miura1729/yarv2llvm/blob/a888d8ce6855e70b630a8673d4cfe075a8e44f0e/sample/ao-render.rb
8# Modified by Tomoyuki Chikanaga
9#
10
11require "dispatch"
12
13IMAGE_WIDTH = 256
14IMAGE_HEIGHT = 256
15NSUBSAMPLES = 2
16NAO_SAMPLES = 8
17
18class Vec
19  def initialize(x, y, z)
20    @x = x
21    @y = y
22    @z = z
23  end
24
25  attr_accessor :x, :y, :z
26
27  def vadd(b)
28    Vec.new(@x + b.x, @y + b.y, @z + b.z)
29  end
30
31  def vsub(b)
32    Vec.new(@x - b.x, @y - b.y, @z - b.z)
33  end
34
35  def vcross(b)
36    Vec.new(@y * b.z - @z * b.y,
37            @z * b.x - @x * b.z,
38            @x * b.y - @y * b.x)
39  end
40
41  def vdot(b)
42    @x * b.x + @y * b.y + @z * b.z
43  end
44
45  def vlength
46    Math.sqrt(@x * @x + @y * @y + @z * @z)
47  end
48
49  def vnormalize!
50    len = vlength
51    if len > 1.0e-17
52      r_len = 1.0 / len
53      @x *= r_len
54      @y *= r_len
55      @z *= r_len
56    end
57    self
58  end
59end
60
61class Sphere
62  def initialize(center, radius)
63    @center = center
64    @radius_2 = radius * radius
65  end
66
67  def intersect(ray, isect)
68    rs = ray.org.vsub(@center)
69    b = rs.vdot(ray.dir)
70    c = rs.vdot(rs) - @radius_2
71    d = b * b - c
72    if d > 0.0
73      t = - b - Math.sqrt(d)
74
75      isect.cross(self, ray, t)
76    end
77    nil
78  end
79
80  def normal_vec(pos)
81    pos.vsub(@center).vnormalize!
82  end
83end
84
85class Plane
86  def initialize(p, n)
87    @p = p
88    @n = n
89  end
90
91  def intersect(ray, isect)
92    d = -@p.vdot(@n)
93    v = ray.dir.vdot(@n)
94    v0 = v
95    if v < 0.0
96      v0 = -v
97    end
98    if v0 < 1.0e-17
99      return
100    end
101
102    t = -(ray.org.vdot(@n) + d) / v
103
104    isect.cross(self, ray, t)
105    nil
106  end
107
108  def normal_vec(pos)
109    @n
110  end
111end
112
113class Ray
114  def initialize(org, dir)
115    @org = org
116    @dir = dir
117  end
118  attr_reader :org, :dir
119end
120
121class Isect
122  def initialize
123    @t = Float::MAX
124    @hit = false
125    @pl = Vec.new(0.0, 0.0, 0.0)
126    @normal = Vec.new(0.0, 0.0, 0.0)
127  end
128
129  attr_reader :normal
130
131  def hit?
132    @hit
133  end
134
135  def cross(geom, ray, dist)
136    if 0.0 < dist and dist < @t
137      @hit = true
138      @t = dist
139      @pl = Vec.new(ray.org.x + ray.dir.x * dist,
140                    ray.org.y + ray.dir.y * dist,
141                    ray.org.z + ray.dir.z * dist)
142      @normal = geom.normal_vec(@pl)
143    end
144    nil
145  end
146
147  def surface(eps)
148    Vec.new(@pl.x + eps * @normal.x,
149            @pl.y + eps * @normal.y,
150            @pl.z + eps * @normal.z)
151  end
152end
153
154def clamp(f)
155  i = f * 255.5
156  if i > 255.0
157    i = 255.0
158  end
159  if i < 0.0
160    i = 0.0
161  end
162  i.round
163end
164
165def otherBasis(basis, n)
166  basis[2] = Vec.new(n.x, n.y, n.z)
167  basis[1] = Vec.new(0.0, 0.0, 0.0)
168 
169  if n.x < 0.6 and n.x > -0.6
170    basis[1].x = 1.0
171  elsif n.y < 0.6 and n.y > -0.6
172    basis[1].y = 1.0
173  elsif n.z < 0.6 and n.z > -0.6
174    basis[1].z = 1.0
175  else
176    basis[1].x = 1.0
177  end
178
179  basis[0] = basis[1].vcross(basis[2])
180  basis[0].vnormalize!
181
182  basis[1] = basis[2].vcross(basis[0])
183  basis[1].vnormalize!
184end
185
186class Scene
187  def initialize
188    @spheres = Array.new
189    @spheres[0] = Sphere.new(Vec.new(-2.0, 0.0, -3.5), 0.5)
190    @spheres[1] = Sphere.new(Vec.new(-0.5, 0.0, -3.0), 0.5)
191    @spheres[2] = Sphere.new(Vec.new(1.0, 0.0, -2.2), 0.5)
192    @plane = Plane.new(Vec.new(0.0, -0.5, 0.0), Vec.new(0.0, 1.0, 0.0))
193  end
194
195  def ambient_occlusion(isect)
196    basis = Array.new
197    otherBasis(basis, isect.normal)
198
199    ntheta    = NAO_SAMPLES
200    nphi      = NAO_SAMPLES
201    eps       = 0.0001
202    occlusion = 0.0
203
204    p0 = isect.surface(eps)
205    nphi.times do |j|
206      ntheta.times do |i|
207        r = rand
208        rr = Math.sqrt(1.0 - r)
209        phi = 2.0 * Math::PI * rand
210        x = Math.cos(phi) * rr
211        y = Math.sin(phi) * rr
212        z = Math.sqrt(r)
213
214        rx = x * basis[0].x + y * basis[1].x + z * basis[2].x
215        ry = x * basis[0].y + y * basis[1].y + z * basis[2].y
216        rz = x * basis[0].z + y * basis[1].z + z * basis[2].z
217
218        raydir = Vec.new(rx, ry, rz)
219        ray = Ray.new(p0, raydir)
220
221        occisect = Isect.new
222        @spheres[0].intersect(ray, occisect)
223        @spheres[1].intersect(ray, occisect)
224        @spheres[2].intersect(ray, occisect)
225        @plane.intersect(ray, occisect)
226        if occisect.hit?
227          occlusion = occlusion + 1.0
228        else
229          0.0
230        end
231      end
232    end
233
234    occlusion = (ntheta * nphi - occlusion).to_f / (ntheta * nphi)
235
236    occlusion
237  end
238
239  def render(w, h, nsubsamples, y)
240    cnt = 0
241    pixbuf = []
242    nsf = nsubsamples.to_f
243    nsf_2 = nsf * nsf
244    w.times do |x|
245      rad = 0.0
246
247      # Subsmpling
248      nsubsamples.times do |v|
249        nsubsamples.times do |u|
250
251          cnt = cnt + 1
252          wf = w.to_f
253          hf = h.to_f
254          xf = x.to_f
255          yf = y.to_f
256          uf = u.to_f
257          vf = v.to_f
258
259          px = (xf + (uf / nsf) - (wf / 2.0)) / (wf / 2.0)
260          py = -(yf + (vf / nsf) - (hf / 2.0)) / (hf / 2.0)
261
262          eye = Vec.new(px, py, -1.0)
263          eye.vnormalize!
264
265          ray = Ray.new(Vec.new(0.0, 0.0, 0.0), eye)
266
267          isect = Isect.new
268          @spheres[0].intersect(ray, isect)
269          @spheres[1].intersect(ray, isect)
270          @spheres[2].intersect(ray, isect)
271          @plane.intersect(ray, isect)
272          if isect.hit?
273            rad += ambient_occlusion(isect)
274          end
275        end
276      end
277
278      pixbuf << clamp(rad / nsf_2)
279    end
280
281    pixbuf.pack("C*")
282  end
283end
284
285file = ARGV[0] || "ao.pgm"
286File.open(file, "w") do |fp|
287  fp.printf("P5\n")
288  fp.printf("%d %d\n", IMAGE_WIDTH, IMAGE_HEIGHT)
289  fp.printf("255\n")
290  job = Dispatch::Job.new
291  IMAGE_HEIGHT.times do |y|
292    job.add do
293      [y, Scene.new.render(IMAGE_WIDTH, IMAGE_HEIGHT, NSUBSAMPLES, y)]
294    end
295  end
296  job.join
297  job.values.to_a.sort_by{|y,| y}.each do |y, scanline|
298    fp.print(scanline)
299  end
300end