rbgl 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +7 -0
  3. data/LICENSE +21 -0
  4. data/README.md +123 -0
  5. data/Rakefile +12 -0
  6. data/examples/array_test.rb +99 -0
  7. data/examples/chemical_heartbeat.rb +166 -0
  8. data/examples/color_test.rb +61 -0
  9. data/examples/cube_spinning.rb +87 -0
  10. data/examples/dark_transit.rb +166 -0
  11. data/examples/fractured_orb.rb +428 -0
  12. data/examples/fractured_orb_rb.rb +598 -0
  13. data/examples/gradient.rb +84 -0
  14. data/examples/hexagonal_flow.rb +333 -0
  15. data/examples/multi_return_test.rb +98 -0
  16. data/examples/plasma.rb +78 -0
  17. data/examples/sphere_raymarch.rb +126 -0
  18. data/examples/teapot.rb +362 -0
  19. data/examples/teapot_mcu.rb +344 -0
  20. data/examples/triangle_basic.rb +36 -0
  21. data/examples/triangle_window.rb +62 -0
  22. data/examples/window_test.rb +36 -0
  23. data/lib/rbgl/engine/buffer.rb +160 -0
  24. data/lib/rbgl/engine/context.rb +157 -0
  25. data/lib/rbgl/engine/framebuffer.rb +115 -0
  26. data/lib/rbgl/engine/pipeline.rb +35 -0
  27. data/lib/rbgl/engine/rasterizer.rb +213 -0
  28. data/lib/rbgl/engine/shader.rb +324 -0
  29. data/lib/rbgl/engine/texture.rb +125 -0
  30. data/lib/rbgl/engine.rb +15 -0
  31. data/lib/rbgl/gui/backend.rb +76 -0
  32. data/lib/rbgl/gui/cocoa/backend.rb +121 -0
  33. data/lib/rbgl/gui/event.rb +34 -0
  34. data/lib/rbgl/gui/file_backend.rb +91 -0
  35. data/lib/rbgl/gui/wayland/backend.rb +126 -0
  36. data/lib/rbgl/gui/wayland/connection.rb +331 -0
  37. data/lib/rbgl/gui/window.rb +148 -0
  38. data/lib/rbgl/gui/x11/backend.rb +156 -0
  39. data/lib/rbgl/gui/x11/connection.rb +344 -0
  40. data/lib/rbgl/gui.rb +16 -0
  41. data/lib/rbgl/version.rb +5 -0
  42. data/lib/rbgl.rb +6 -0
  43. metadata +114 -0
@@ -0,0 +1,598 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Fractured Orb (RLSL Ruby Mode)
4
+ # Original: https://www.shadertoy.com/view/ttycWW
5
+ # A mashup of 'Crystal Tetrahedron' and 'Buckyball Fracture'
6
+ # License: Creative Commons Attribution-NonCommercial-ShareAlike 3.0
7
+ # https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en
8
+
9
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
10
+
11
+ require "rbgl"
12
+ require "rlsl"
13
+
14
+ WIDTH = 640
15
+ HEIGHT = 480
16
+
17
+ shader = RLSL.define(:fractured_orb_ruby) do
18
+ uniforms do
19
+ float :time
20
+ float :rand_seed
21
+ end
22
+
23
+ functions do
24
+ # Helper functions
25
+ define :pR, returns: :vec2, params: { p: :vec2, a: :float }
26
+ define :smax, returns: :float, params: { a: :float, b: :float, r: :float }
27
+ define :vmax2, returns: :float, params: { v: :vec2 }
28
+ define :vmax3, returns: :float, params: { v: :vec3 }
29
+ define :fBox2, returns: :float, params: { p: :vec2, b: :vec2 }
30
+ define :fBox3, returns: :float, params: { p: :vec3, b: :vec3 }
31
+ define :erot, returns: :vec3, params: { p: :vec3, ax: :vec3, ro: :float }
32
+ define :spectrum, returns: :vec3, params: { n: :float }
33
+ define :expImpulse, returns: :float, params: { x: :float, k: :float }
34
+ define :boolSign, returns: :float, params: { v: :float }
35
+ define :boolSign3, returns: :vec3, params: { v: :vec3 }
36
+ define :icosahedronVertex, returns: :vec3, params: { p: :vec3 }
37
+ define :dodecahedronVertex, returns: :vec3, params: { p: :vec3 }
38
+ define :object_sdf, returns: :float, params: { p: :vec3 }
39
+ define :map_scene, returns: :vec2, params: { p: :vec3, anim_time: :float }
40
+ define :calc_normal, returns: :vec3, params: { pos: :vec3, anim_time: :float }
41
+ define :sphericalMatrix_mul, returns: :vec3, params: { tp: :vec2, v: :vec3 }
42
+ define :light_func, returns: :vec3, params: { origin: :vec3, rayDir: :vec3, envOrientation: :vec2 }
43
+ define :env_func, returns: :vec3, params: { origin: :vec3, rayDir: :vec3, envOrientation: :vec2 }
44
+ define :simple_hash, returns: :float, params: { seed: :float }
45
+ end
46
+
47
+ helpers do
48
+ # Constants
49
+ PHI = 1.618033988749895
50
+ OUTER = 0.35
51
+ INNER = 0.24
52
+
53
+ # pR - 2D rotation
54
+ def pR(p, a)
55
+ c = cos(a)
56
+ s = sin(a)
57
+ vec2(c * p.x + s * p.y, 0.0 - s * p.x + c * p.y)
58
+ end
59
+
60
+ # smax - smooth maximum
61
+ def smax(a, b, r)
62
+ ua = max(r + a, 0.0)
63
+ ub = max(r + b, 0.0)
64
+ min(0.0 - r, max(a, b)) + sqrt(ua * ua + ub * ub)
65
+ end
66
+
67
+ # vmax
68
+ def vmax2(v)
69
+ max(v.x, v.y)
70
+ end
71
+
72
+ def vmax3(v)
73
+ max(max(v.x, v.y), v.z)
74
+ end
75
+
76
+ # fBox - box SDF
77
+ def fBox2(p, b)
78
+ d = vec2(abs(p.x) - b.x, abs(p.y) - b.y)
79
+ mx = vec2(max(d.x, 0.0), max(d.y, 0.0))
80
+ mn = vec2(min(d.x, 0.0), min(d.y, 0.0))
81
+ length(mx) + vmax2(mn)
82
+ end
83
+
84
+ def fBox3(p, b)
85
+ d = vec3(abs(p.x) - b.x, abs(p.y) - b.y, abs(p.z) - b.z)
86
+ mx = vec3(max(d.x, 0.0), max(d.y, 0.0), max(d.z, 0.0))
87
+ mn = vec3(min(d.x, 0.0), min(d.y, 0.0), min(d.z, 0.0))
88
+ length(mx) + vmax3(mn)
89
+ end
90
+
91
+ # erot - rotate on axis
92
+ def erot(p, ax, ro)
93
+ d = ax.x * p.x + ax.y * p.y + ax.z * p.z
94
+ dax = vec3(ax.x * d, ax.y * d, ax.z * d)
95
+ c = cos(ro)
96
+ s = sin(ro)
97
+ # cross(ax, p)
98
+ cx = ax.y * p.z - ax.z * p.y
99
+ cy = ax.z * p.x - ax.x * p.z
100
+ cz = ax.x * p.y - ax.y * p.x
101
+ # mix(dax, p, c) + s * cross
102
+ vec3(
103
+ dax.x + (p.x - dax.x) * c + s * cx,
104
+ dax.y + (p.y - dax.y) * c + s * cy,
105
+ dax.z + (p.z - dax.z) * c + s * cz
106
+ )
107
+ end
108
+
109
+ # Spectrum palette (IQ)
110
+ def spectrum(n)
111
+ # a + b * cos(TAU * (c * n + d))
112
+ t = TAU * n
113
+ vec3(
114
+ 0.5 + 0.5 * cos(t + 0.0),
115
+ 0.5 + 0.5 * cos(t + TAU * 0.33),
116
+ 0.5 + 0.5 * cos(t + TAU * 0.67)
117
+ )
118
+ end
119
+
120
+ # expImpulse
121
+ def expImpulse(x, k)
122
+ h = k * x
123
+ h * exp(1.0 - h)
124
+ end
125
+
126
+ # boolSign
127
+ def boolSign(v)
128
+ if v > 0.0
129
+ 1.0
130
+ else
131
+ 0.0 - 1.0
132
+ end
133
+ end
134
+
135
+ def boolSign3(v)
136
+ vec3(boolSign(v.x), boolSign(v.y), boolSign(v.z))
137
+ end
138
+
139
+ # Icosahedron vertex (optimized by iq)
140
+ def icosahedronVertex(p)
141
+ ap = vec3(abs(p.x), abs(p.y), abs(p.z))
142
+ v = vec3(PHI, 1.0, 0.0)
143
+ test1 = ap.x + ap.z * PHI
144
+ dot1 = ap.x * v.x + ap.y * v.y + ap.z * v.z
145
+ if test1 > dot1
146
+ v = vec3(1.0, 0.0, PHI)
147
+ end
148
+ test2 = ap.z + ap.y * PHI
149
+ dot2 = ap.x * v.x + ap.y * v.y + ap.z * v.z
150
+ if test2 > dot2
151
+ v = vec3(0.0, PHI, 1.0)
152
+ end
153
+ bs = boolSign3(p)
154
+ vec3(v.x * 0.52573111 * bs.x, v.y * 0.52573111 * bs.y, v.z * 0.52573111 * bs.z)
155
+ end
156
+
157
+ # Dodecahedron vertex (optimized by iq)
158
+ def dodecahedronVertex(p)
159
+ ap = vec3(abs(p.x), abs(p.y), abs(p.z))
160
+ v = vec3(PHI, PHI, PHI)
161
+ v2 = vec3(0.0, 1.0, PHI + 1.0)
162
+ # v2.yzx = (1.0, PHI+1.0, 0.0)
163
+ v3 = vec3(1.0, PHI + 1.0, 0.0)
164
+ # v2.zxy = (PHI+1.0, 0.0, 1.0)
165
+ v4 = vec3(PHI + 1.0, 0.0, 1.0)
166
+
167
+ dotv = ap.x * v.x + ap.y * v.y + ap.z * v.z
168
+ dotv2 = ap.x * v2.x + ap.y * v2.y + ap.z * v2.z
169
+ if dotv2 > dotv
170
+ v = v2
171
+ end
172
+ dotv = ap.x * v.x + ap.y * v.y + ap.z * v.z
173
+ dotv3 = ap.x * v3.x + ap.y * v3.y + ap.z * v3.z
174
+ if dotv3 > dotv
175
+ v = v3
176
+ end
177
+ dotv = ap.x * v.x + ap.y * v.y + ap.z * v.z
178
+ dotv4 = ap.x * v4.x + ap.y * v4.y + ap.z * v4.z
179
+ if dotv4 > dotv
180
+ v = v4
181
+ end
182
+ bs = boolSign3(p)
183
+ vec3(v.x * 0.35682209 * bs.x, v.y * 0.35682209 * bs.y, v.z * 0.35682209 * bs.z)
184
+ end
185
+
186
+ # Object SDF
187
+ def object_sdf(p)
188
+ d = length(p) - OUTER
189
+ max(d, 0.0 - d - (OUTER - INNER))
190
+ end
191
+
192
+ # Map function
193
+ def map_scene(p, anim_time)
194
+ scale = 2.5
195
+ p = vec3(p.x / scale, p.y / scale, p.z / scale)
196
+
197
+ outerBound = length(p) - OUTER
198
+
199
+ spin = anim_time * (PI / 2.0) - 0.15
200
+ rotated = pR(vec2(p.x, p.z), spin)
201
+ p = vec3(rotated.x, p.y, rotated.y)
202
+
203
+ # Buckyball faces
204
+ va = icosahedronVertex(p)
205
+ vb = dodecahedronVertex(p)
206
+
207
+ # cross(va, vb)
208
+ cx = va.y * vb.z - va.z * vb.y
209
+ cy = va.z * vb.x - va.x * vb.z
210
+ cz = va.x * vb.y - va.y * vb.x
211
+ side = boolSign(p.x * cx + p.y * cy + p.z * cz)
212
+ r = TAU / 5.0 * side
213
+ vc = erot(vb, va, r)
214
+ vd = erot(vb, va, 0.0 - r)
215
+
216
+ d = 1000000.0
217
+ pp = p
218
+
219
+ i = 0.0
220
+ while i < 4.0
221
+ # Animation
222
+ t = mod(anim_time * 2.0 / 3.0 + 0.25 - (va.x + 0.0 - va.y) / 30.0, 1.0)
223
+ if t < 0.0
224
+ t = t + 1.0
225
+ end
226
+ t2 = clamp(t * 5.0 - 1.7, 0.0, 1.0)
227
+ explode = 1.0 - pow(1.0 - t2, 10.0)
228
+ explode = explode * (1.0 - pow(t2, 5.0))
229
+ explode = explode + (smoothstep(0.32, 0.34, t) - smoothstep(0.34, 0.5, t)) * 0.05
230
+ explode = explode * 1.4
231
+ t2 = max(t - 0.53, 0.0) * 1.2
232
+ wobble = sin(expImpulse(t2, 20.0) * 2.2 + pow(3.0 * t2, 1.5) * 2.0 * TAU - PI) * smoothstep(0.4, 0.0, t2) * 0.2
233
+ anim = wobble + explode
234
+ p = vec3(pp.x - va.x * anim / 2.8, pp.y - va.y * anim / 2.8, pp.z - va.z * anim / 2.8)
235
+
236
+ # Build boundary edge of face
237
+ diffB = vec3(vb.x - va.x, vb.y - va.y, vb.z - va.z)
238
+ normB = normalize(diffB)
239
+ edgeA = p.x * normB.x + p.y * normB.y + p.z * normB.z
240
+
241
+ diffC = vec3(vc.x - va.x, vc.y - va.y, vc.z - va.z)
242
+ normC = normalize(diffC)
243
+ edgeB = p.x * normC.x + p.y * normC.y + p.z * normC.z
244
+
245
+ diffD = vec3(vd.x - va.x, vd.y - va.y, vd.z - va.z)
246
+ normD = normalize(diffD)
247
+ edgeC = p.x * normD.x + p.y * normD.y + p.z * normD.z
248
+
249
+ edge = max(max(edgeA, edgeB), edgeC) - 0.005
250
+
251
+ d = min(d, smax(object_sdf(p), edge, 0.002))
252
+
253
+ p = pp
254
+
255
+ # Cycle faces
256
+ va2 = va
257
+ va = vb
258
+ vb = vc
259
+ vc = vd
260
+ vd = va2
261
+
262
+ i = i + 1.0
263
+ end
264
+
265
+ bound = outerBound - 0.002
266
+ if bound * scale > 0.002
267
+ d = min(d, bound)
268
+ end
269
+
270
+ vec2(d * scale, 1.0)
271
+ end
272
+
273
+ # Normal calculation
274
+ def calc_normal(pos, anim_time)
275
+ n = vec3(0.0, 0.0, 0.0)
276
+ k = 0.5773
277
+ e0 = vec3(k * (0.0 - 1.0), k * (0.0 - 1.0), k * (0.0 - 1.0))
278
+ e1 = vec3(k * (0.0 - 1.0), k * 1.0, k * 1.0)
279
+ e2 = vec3(k * 1.0, k * (0.0 - 1.0), k * 1.0)
280
+ e3 = vec3(k * 1.0, k * 1.0, k * (0.0 - 1.0))
281
+
282
+ d0 = map_scene(vec3(pos.x + 0.001 * e0.x, pos.y + 0.001 * e0.y, pos.z + 0.001 * e0.z), anim_time).x
283
+ d1 = map_scene(vec3(pos.x + 0.001 * e1.x, pos.y + 0.001 * e1.y, pos.z + 0.001 * e1.z), anim_time).x
284
+ d2 = map_scene(vec3(pos.x + 0.001 * e2.x, pos.y + 0.001 * e2.y, pos.z + 0.001 * e2.z), anim_time).x
285
+ d3 = map_scene(vec3(pos.x + 0.001 * e3.x, pos.y + 0.001 * e3.y, pos.z + 0.001 * e3.z), anim_time).x
286
+
287
+ n = vec3(
288
+ e0.x * d0 + e1.x * d1 + e2.x * d2 + e3.x * d3,
289
+ e0.y * d0 + e1.y * d1 + e2.y * d2 + e3.y * d3,
290
+ e0.z * d0 + e1.z * d1 + e2.z * d2 + e3.z * d3
291
+ )
292
+ normalize(n)
293
+ end
294
+
295
+ # Spherical matrix multiply
296
+ def sphericalMatrix_mul(tp, v)
297
+ theta = tp.x
298
+ phi = tp.y
299
+ cx = cos(theta)
300
+ cy = cos(phi)
301
+ sx = sin(theta)
302
+ sy = sin(phi)
303
+ vec3(
304
+ cy * v.x + (0.0 - sy) * (0.0 - sx) * v.y + (0.0 - sy) * cx * v.z,
305
+ cx * v.y + sx * v.z,
306
+ sy * v.x + cy * (0.0 - sx) * v.y + cy * cx * v.z
307
+ )
308
+ end
309
+
310
+ # Light function
311
+ def light_func(origin, rayDir, envOrientation)
312
+ org = vec3(0.0 - origin.x, 0.0 - origin.y, 0.0 - origin.z)
313
+ rd = vec3(0.0 - rayDir.x, 0.0 - rayDir.y, 0.0 - rayDir.z)
314
+ org = sphericalMatrix_mul(envOrientation, org)
315
+ rd = sphericalMatrix_mul(envOrientation, rd)
316
+
317
+ pos = vec3(0.0 - 6.0, 0.0 - 6.0, 0.0 - 6.0)
318
+ normal = normalize(pos)
319
+ up = normalize(vec3(0.0 - 1.0, 1.0, 0.0))
320
+
321
+ denom = rd.x * normal.x + rd.y * normal.y + rd.z * normal.z
322
+
323
+ result = vec3(0.0, 0.0, 0.0)
324
+
325
+ if abs(denom) >= 0.0001
326
+ diff = vec3(pos.x - org.x, pos.y - org.y, pos.z - org.z)
327
+ t = (diff.x * normal.x + diff.y * normal.y + diff.z * normal.z) / denom
328
+
329
+ if t >= 0.0
330
+ point = vec3(org.x + t * rd.x - pos.x, org.y + t * rd.y - pos.y, org.z + t * rd.z - pos.z)
331
+ # cross(normal, up)
332
+ tangent_x = normal.y * up.z - normal.z * up.y
333
+ tangent_y = normal.z * up.x - normal.x * up.z
334
+ tangent_z = normal.x * up.y - normal.y * up.x
335
+ tangent = vec3(tangent_x, tangent_y, tangent_z)
336
+ # cross(normal, tangent)
337
+ bitangent = vec3(
338
+ normal.y * tangent.z - normal.z * tangent.y,
339
+ normal.z * tangent.x - normal.x * tangent.z,
340
+ normal.x * tangent.y - normal.y * tangent.x
341
+ )
342
+ uv_l = vec2(
343
+ tangent.x * point.x + tangent.y * point.y + tangent.z * point.z,
344
+ bitangent.x * point.x + bitangent.y * point.y + bitangent.z * point.z
345
+ )
346
+
347
+ l = smoothstep(0.75, 0.0, fBox2(uv_l, vec2(0.5, 2.0)) - 1.0)
348
+ l = l * smoothstep(6.0, 0.0, length(uv_l))
349
+ result = vec3(l, l, l)
350
+ end
351
+ end
352
+
353
+ result
354
+ end
355
+
356
+ # Environment function
357
+ def env_func(origin, rayDir, envOrientation)
358
+ origin = vec3(0.0 - origin.x, 0.0 - origin.y, 0.0 - origin.z)
359
+ rayDir = vec3(0.0 - rayDir.x, 0.0 - rayDir.y, 0.0 - rayDir.z)
360
+ origin = sphericalMatrix_mul(envOrientation, origin)
361
+ rayDir = sphericalMatrix_mul(envOrientation, rayDir)
362
+
363
+ l = smoothstep(0.0, 1.7, rayDir.x * 0.5 + rayDir.y * (0.0 - 0.3) + rayDir.z * 1.0) * 0.4
364
+ vec3(0.9 * l, 0.83 * l, 1.0 * l)
365
+ end
366
+
367
+ # Simple hash
368
+ def simple_hash(seed)
369
+ fract(sin(seed * 12.9898) * 43758.5453)
370
+ end
371
+ end
372
+
373
+ fragment do |frag_coord, resolution, u|
374
+ duration = 10.0 / 3.0
375
+ anim_time = mod(u.time / duration, 1.0)
376
+
377
+ envOrientation = vec2(
378
+ (81.5 / 187.0 * 2.0 - 1.0) * 2.0,
379
+ (119.0 / 187.0 * 2.0 - 1.0) * 2.0
380
+ )
381
+
382
+ # UV (same as Metal: frag_coord / resolution.y)
383
+ uv = vec2(frag_coord.x / resolution.y, frag_coord.y / resolution.y)
384
+
385
+ # Shadertoy-style centered UV
386
+ screen_uv = vec2(uv.x - resolution.x / resolution.y * 0.5, uv.y - 0.5)
387
+
388
+ col = vec3(0.0, 0.0, 0.0)
389
+ bgCol = vec3(0.9 * 0.22, 0.83 * 0.22, 1.0 * 0.22)
390
+
391
+ maxDist = 30.0
392
+ camOrigin = vec3(0.0, 0.0, 25.0)
393
+ camDir = normalize(vec3(screen_uv.x * 0.168, screen_uv.y * 0.168, 0.0 - 1.0))
394
+
395
+ # First march for depth
396
+ firstLen = 0.0
397
+ firstResY = 0.0
398
+ len = 0.0
399
+ dist = 0.0
400
+ mi = 0.0
401
+ p = vec3(0.0, 0.0, 0.0)
402
+ res = vec2(0.0, 0.0)
403
+ while mi < 300.0
404
+ len = len + dist * 0.8
405
+ p = vec3(camOrigin.x + len * camDir.x, camOrigin.y + len * camDir.y, camOrigin.z + len * camDir.z)
406
+ res = map_scene(p, anim_time)
407
+ dist = res.x
408
+ if dist < 0.001
409
+ firstResY = res.y
410
+ break
411
+ end
412
+ if len >= maxDist
413
+ len = maxDist
414
+ firstResY = 0.0
415
+ break
416
+ end
417
+ mi = mi + 1.0
418
+ end
419
+ firstLen = len
420
+ firstP = vec3(camOrigin.x + len * camDir.x, camOrigin.y + len * camDir.y, camOrigin.z + len * camDir.z)
421
+
422
+ # Dispersion loop (3 samples for CPU, original uses 5)
423
+ maxDisperse = 3.0
424
+ disperse = 0.0
425
+ while disperse < maxDisperse
426
+ invert = 1.0
427
+ sam = vec3(0.0, 0.0, 0.0)
428
+ origin = camOrigin
429
+ rayDir = camDir
430
+
431
+ extinctionDist = 0.0
432
+ wavelength = disperse / maxDisperse
433
+
434
+ rand_val = simple_hash(u.rand_seed + disperse * 0.1 + uv.x * 100.0 + uv.y * 1000.0)
435
+ wavelength = wavelength + (rand_val * 2.0 - 1.0) * 0.1
436
+
437
+ bounceCount = 0.0
438
+
439
+ bounce = 0.0
440
+ hitLen = 0.0
441
+ hitResY = 0.0
442
+ while bounce < 5.0
443
+ if bounce < 0.5
444
+ hitLen = firstLen
445
+ hitResY = firstResY
446
+ p = firstP
447
+ else
448
+ # March
449
+ len2 = 0.0
450
+ dist2 = 0.0
451
+ hitResY = 0.0
452
+ mi2 = 0.0
453
+ while mi2 < 150.0
454
+ len2 = len2 + dist2
455
+ p = vec3(origin.x + len2 * rayDir.x, origin.y + len2 * rayDir.y, origin.z + len2 * rayDir.z)
456
+ res2 = map_scene(p, anim_time)
457
+ dist2 = res2.x * invert
458
+ if dist2 < 0.001
459
+ hitResY = res2.y
460
+ break
461
+ end
462
+ if len2 >= maxDist / 2.0
463
+ len2 = maxDist / 2.0
464
+ hitResY = 0.0
465
+ break
466
+ end
467
+ mi2 = mi2 + 1.0
468
+ end
469
+ hitLen = len2
470
+ end
471
+
472
+ if invert < 0.0
473
+ extinctionDist = extinctionDist + hitLen
474
+ end
475
+
476
+ if hitResY < 0.001
477
+ break
478
+ end
479
+
480
+ nor = calc_normal(p, anim_time)
481
+ nor = vec3(nor.x * invert, nor.y * invert, nor.z * invert)
482
+ ref = reflect(rayDir, nor)
483
+
484
+ # Shade
485
+ light = light_func(p, ref, envOrientation)
486
+ sam = vec3(sam.x + light.x * 0.5, sam.y + light.y * 0.5, sam.z + light.z * 0.5)
487
+ fresnel = 1.0 - abs(rayDir.x * nor.x + rayDir.y * nor.y + rayDir.z * nor.z)
488
+ fresnel5 = pow(fresnel, 5.0) * 0.1
489
+ sam = vec3(sam.x + fresnel5, sam.y + fresnel5, sam.z + fresnel5)
490
+ sam = vec3(sam.x * 0.85, sam.y * 0.85, sam.z * 0.98)
491
+
492
+ # Refract
493
+ ior = mix(1.2, 1.8, wavelength)
494
+ if invert < 0.0
495
+ ior = ior
496
+ else
497
+ ior = 1.0 / ior
498
+ end
499
+
500
+ raf = refract(rayDir, nor, ior)
501
+ tif = length(raf) < 0.001
502
+ if tif
503
+ rayDir = ref
504
+ else
505
+ rayDir = raf
506
+ end
507
+
508
+ dotRN = abs(rayDir.x * nor.x + rayDir.y * nor.y + rayDir.z * nor.z)
509
+ offset = 0.01 / (dotRN + 0.0001)
510
+ origin = vec3(p.x + offset * rayDir.x, p.y + offset * rayDir.y, p.z + offset * rayDir.z)
511
+ invert = invert * (0.0 - 1.0)
512
+
513
+ bounceCount = bounce
514
+
515
+ bounce = bounce + 1.0
516
+ end
517
+
518
+ # Add background or environment
519
+ if bounceCount < 0.5
520
+ sam = vec3(sam.x + bgCol.x, sam.y + bgCol.y, sam.z + bgCol.z)
521
+ else
522
+ env = env_func(firstP, rayDir, envOrientation)
523
+ sam = vec3(sam.x + env.x, sam.y + env.y, sam.z + env.z)
524
+ end
525
+
526
+ # Accumulate color with spectrum weighting
527
+ if bounceCount < 0.5
528
+ # No hit - add background with boost and break early
529
+ col = vec3(col.x + sam.x * maxDisperse / 2.0, col.y + sam.y * maxDisperse / 2.0, col.z + sam.z * maxDisperse / 2.0)
530
+ break
531
+ else
532
+ spec = spectrum(0.0 - wavelength + 0.25)
533
+ col = vec3(col.x + sam.x * spec.x, col.y + sam.y * spec.y, col.z + sam.z * spec.z)
534
+ end
535
+
536
+ disperse = disperse + 1.0
537
+ end
538
+
539
+ # Average over dispersion samples
540
+ col = vec3(col.x / maxDisperse, col.y / maxDisperse, col.z / maxDisperse)
541
+
542
+ # Tonemap
543
+ col = vec3(pow(col.x, 1.25) * 2.5, pow(col.y, 1.25) * 2.5, pow(col.z, 1.25) * 2.5)
544
+ col = vec3(col.x / 2.0 * 16.0, col.y / 2.0 * 16.0, col.z / 2.0 * 16.0)
545
+ col = vec3(max(0.0, col.x - 0.004), max(0.0, col.y - 0.004), max(0.0, col.z - 0.004))
546
+ col = vec3(
547
+ (col.x * (6.2 * col.x + 0.5)) / (col.x * (6.2 * col.x + 1.7) + 0.06),
548
+ (col.y * (6.2 * col.y + 0.5)) / (col.y * (6.2 * col.y + 1.7) + 0.06),
549
+ (col.z * (6.2 * col.z + 0.5)) / (col.z * (6.2 * col.z + 1.7) + 0.06)
550
+ )
551
+
552
+ col
553
+ end
554
+ end
555
+
556
+ # Initialize display
557
+ window = RBGL::GUI::Window.new(width: WIDTH, height: HEIGHT, title: "Fractured Orb (Ruby Mode)")
558
+
559
+ puts "Fractured Orb (RLSL Ruby Mode)"
560
+ puts "Original: https://www.shadertoy.com/view/ttycWW"
561
+ puts "License: Creative Commons Attribution-NonCommercial-ShareAlike 3.0"
562
+ puts "https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en"
563
+ puts "Note: Simplified version (3 dispersion samples, 5 bounces) for CPU performance"
564
+ puts "Press 'q' or Escape to quit"
565
+
566
+ start_time = Time.now
567
+ frame_count = 0
568
+ last_fps_time = start_time
569
+ running = true
570
+
571
+ buffer = "\x00" * (WIDTH * HEIGHT * 4)
572
+
573
+ while running && !window.should_close?
574
+ time = Time.now - start_time
575
+ rand_seed = rand * 1000.0
576
+
577
+ shader.render(buffer, WIDTH, HEIGHT, { time: time, rand_seed: rand_seed })
578
+
579
+ window.set_pixels(buffer)
580
+
581
+ events = window.poll_events_raw
582
+ events.each do |e|
583
+ if e[:type] == :key_press && (e[:key] == 12 || e[:key] == "q")
584
+ running = false
585
+ end
586
+ end
587
+
588
+ frame_count += 1
589
+ now = Time.now
590
+ if now - last_fps_time >= 1.0
591
+ fps = frame_count / (now - last_fps_time)
592
+ puts "FPS: #{fps.round(1)}"
593
+ frame_count = 0
594
+ last_fps_time = now
595
+ end
596
+ end
597
+
598
+ window.close
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Simple gradient shader to verify Metal pipeline works
4
+
5
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
6
+
7
+ require "rbgl"
8
+ require "rlsl"
9
+
10
+ WIDTH = 640
11
+ HEIGHT = 480
12
+
13
+ # Define a simple Metal shader using pure Ruby DSL
14
+ shader = RLSL.define_metal(:metal_test) do
15
+ uniforms do
16
+ float :time
17
+ end
18
+
19
+ fragment do |frag_coord, resolution, u|
20
+ p = frag_coord / resolution
21
+
22
+ r = 0.5 + 0.5 * sin(p.x * 10.0 + u.time)
23
+ g = 0.5 + 0.5 * sin(p.y * 10.0 + u.time * 1.3)
24
+ b = 0.5 + 0.5 * sin((p.x + p.y) * 10.0 + u.time * 0.7)
25
+
26
+ vec3(r, g, b)
27
+ end
28
+ end
29
+
30
+ puts "Generated MSL source:"
31
+ puts "-" * 40
32
+ puts shader.msl_source
33
+ puts "-" * 40
34
+
35
+ # Initialize display
36
+ window = RBGL::GUI::Window.new(width: WIDTH, height: HEIGHT, title: "Metal Test")
37
+
38
+ puts "Metal Test - Gradient Shader"
39
+ puts "Press 'q' or Escape to quit"
40
+
41
+ # Check Metal availability
42
+ if window.metal_available?
43
+ puts "Metal compute is available!"
44
+ else
45
+ puts "Metal compute is NOT available"
46
+ exit 1
47
+ end
48
+
49
+ start_time = Time.now
50
+ frame_count = 0
51
+ last_fps_time = start_time
52
+ running = true
53
+
54
+ while running && !window.should_close?
55
+ time = Time.now - start_time
56
+
57
+ begin
58
+ shader.render_metal(window.native_handle, WIDTH, HEIGHT, { time: time })
59
+ rescue => e
60
+ puts "Error: #{e.message}"
61
+ puts e.backtrace.first(5).join("\n")
62
+ running = false
63
+ break
64
+ end
65
+
66
+ events = window.poll_events_raw
67
+ events.each do |e|
68
+ case e[:type]
69
+ when :key_press
70
+ running = false if e[:key] == 12 || e[:key] == "q"
71
+ end
72
+ end
73
+
74
+ frame_count += 1
75
+ now = Time.now
76
+ if now - last_fps_time >= 1.0
77
+ fps = frame_count / (now - last_fps_time)
78
+ puts "FPS: #{fps.round(1)}"
79
+ frame_count = 0
80
+ last_fps_time = now
81
+ end
82
+ end
83
+
84
+ window.close