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,333 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Hexagon X5 - Hexagonal Flow Pattern
4
+ # Original: https://www.shadertoy.com/view/4cVfWG
5
+ # Created by @byt3_m3chanic - 12/17/2024
6
+ # License: Creative Commons Attribution-NonCommercial-ShareAlike 3.0
7
+
8
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
9
+
10
+ require "rbgl"
11
+ require "rlsl"
12
+
13
+ WIDTH = 640
14
+ HEIGHT = 480
15
+
16
+ # Hexagonal grid constants
17
+ module HexConstants
18
+ N = 3.0
19
+ S4 = 0.577350 # 1/sqrt(3)
20
+ S3 = 0.288683 # 1/(2*sqrt(3))
21
+ S2 = 0.866025 # sqrt(3)/2
22
+ end
23
+
24
+ shader = RLSL.define(:hexflow) do
25
+ uniforms do
26
+ float :time
27
+ vec2 :mouse
28
+ end
29
+
30
+ helpers(:c) do
31
+ n = HexConstants::N
32
+ s4 = HexConstants::S4
33
+ s3 = HexConstants::S3
34
+ s2 = HexConstants::S2
35
+
36
+ <<~C
37
+ // Constants (PI and TAU already defined in code_generator.rb)
38
+ #define PI2 6.283185307f
39
+
40
+ static const float N = #{n}f;
41
+ static const float s4 = #{s4}f;
42
+ static const float s3 = #{s3}f;
43
+ static const float s2 = #{s2}f;
44
+
45
+ // Global state
46
+ static vec3 clr, trm;
47
+ static float tk, ln;
48
+ static float r2_cos, r2_sin, r3_cos, r3_sin;
49
+
50
+ // 2x2 matrix multiply
51
+ static inline vec2 mat2_mul(float c, float s, vec2 v) {
52
+ return vec2_new(c * v.x + s * v.y, -s * v.x + c * v.y);
53
+ }
54
+
55
+ // Hash function (custom version for hex grid)
56
+ static float hex_hash21(vec2 p) {
57
+ p.x = fmodf(p.x, 3.0f * N);
58
+ if (p.x < 0.0f) p.x += 3.0f * N;
59
+ float d = p.x * 26.37f + p.y * 45.93f;
60
+ return fract(sinf(d) * 4374.23f);
61
+ }
62
+
63
+ // Hexagon grid system
64
+ static vec4 hexgrid(vec2 uv) {
65
+ vec2 p1 = vec2_new(floorf(uv.x / 1.732f) + 0.5f, floorf(uv.y) + 0.5f);
66
+ vec2 p2 = vec2_new(floorf((uv.x - 1.0f) / 1.732f) + 0.5f, floorf(uv.y - 0.5f) + 0.5f);
67
+
68
+ vec2 h1 = vec2_new(uv.x - p1.x * 1.732f, uv.y - p1.y);
69
+ vec2 h2 = vec2_new(uv.x - (p2.x + 0.5f) * 1.732f, uv.y - (p2.y + 0.5f));
70
+
71
+ if (vec2_dot(h1, h1) < vec2_dot(h2, h2)) {
72
+ return vec4_new(h1.x, h1.y, p1.x, p1.y);
73
+ } else {
74
+ return vec4_new(h2.x, h2.y, p2.x + 0.5f, p2.y + 0.5f);
75
+ }
76
+ }
77
+
78
+ // Draw function with anti-aliasing
79
+ static void draw(float d, float px, vec3 *C) {
80
+ float b = fabsf(d) - tk;
81
+
82
+ // Shadow
83
+ float t1 = smoothstep(0.1f + px, -px, b - 0.01f);
84
+ C->x = mix_f(C->x, C->x * 0.25f, t1);
85
+ C->y = mix_f(C->y, C->y * 0.25f, t1);
86
+ C->z = mix_f(C->z, C->z * 0.25f, t1);
87
+
88
+ // Fill
89
+ float t2 = smoothstep(px, -px, b);
90
+ C->x = mix_f(C->x, clr.x, t2);
91
+ C->y = mix_f(C->y, clr.y, t2);
92
+ C->z = mix_f(C->z, clr.z, t2);
93
+
94
+ // Highlight
95
+ float t3 = smoothstep(0.01f + px, -px, b + 0.1f);
96
+ C->x = mix_f(C->x, clamp_f(C->x + 0.2f, C->x, 0.95f), t3);
97
+ C->y = mix_f(C->y, clamp_f(C->y + 0.2f, C->y, 0.95f), t3);
98
+ C->z = mix_f(C->z, clamp_f(C->z + 0.2f, C->z, 0.95f), t3);
99
+
100
+ // Trim
101
+ float t4 = smoothstep(px, -px, fabsf(b) - ln);
102
+ C->x = mix_f(C->x, trm.x, t4);
103
+ C->y = mix_f(C->y, trm.y, t4);
104
+ C->z = mix_f(C->z, trm.z, t4);
105
+ }
106
+
107
+ // Procedural texture replacement (since we don't have iChannel0)
108
+ static vec3 proc_texture(vec2 p) {
109
+ float n = sinf(p.x * 10.0f) * sinf(p.y * 10.0f);
110
+ n = n * 0.5f + 0.5f;
111
+ return vec3_new(0.906f * n, 0.282f * n, 0.075f * n);
112
+ }
113
+ C
114
+ end
115
+
116
+ fragment do
117
+ <<~C
118
+ // Initialize rotation matrices (1.047 radians = 60 degrees)
119
+ r2_cos = cosf(1.047f);
120
+ r2_sin = sinf(1.047f);
121
+ r3_cos = cosf(-1.047f);
122
+ r3_sin = sinf(-1.047f);
123
+
124
+ // Normalized coordinates
125
+ vec2 uv_screen = vec2_new(
126
+ (2.0f * frag_coord.x - resolution.x) / fmaxf(resolution.x, resolution.y),
127
+ (2.0f * frag_coord.y - resolution.y) / fmaxf(resolution.x, resolution.y)
128
+ );
129
+
130
+ // Mouse offset
131
+ vec2 mouse_norm = vec2_new(
132
+ (2.0f * u.mouse.x - resolution.x) / resolution.x,
133
+ (2.0f * u.mouse.y - resolution.y) / resolution.y
134
+ );
135
+
136
+ // Log-polar transformation
137
+ float len = sqrtf(uv_screen.x * uv_screen.x + uv_screen.y * uv_screen.y);
138
+ if (len < 0.001f) len = 0.001f;
139
+
140
+ vec2 uv_polar = vec2_new(
141
+ -logf(len) - mouse_norm.x,
142
+ -atan2f(uv_screen.y, uv_screen.x) - mouse_norm.y
143
+ );
144
+
145
+ uv_polar = vec2_div(uv_polar, 3.628f);
146
+ uv_polar = vec2_mul(uv_polar, N);
147
+
148
+ // Animation
149
+ uv_polar.y += u.time * 0.05f;
150
+ uv_polar.x += u.time * 0.15f;
151
+
152
+ float sc = 3.0f;
153
+ float px = 0.01f; // Approximate fwidth
154
+
155
+ // Hexgrid with swapped coordinates
156
+ vec4 H = hexgrid(vec2_new(uv_polar.y * sc, uv_polar.x * sc));
157
+ vec2 p = vec2_new(H.x, H.y);
158
+ vec2 id = vec2_new(H.z, H.w);
159
+
160
+ float hs = hex_hash21(id);
161
+
162
+ // Random rotation
163
+ if (hs < 0.5f) {
164
+ if (hs < 0.25f) {
165
+ p = mat2_mul(r3_cos, r3_sin, p);
166
+ } else {
167
+ p = mat2_mul(r2_cos, r2_sin, p);
168
+ }
169
+ }
170
+
171
+ // Triangle vertices
172
+ vec2 p0 = vec2_new(p.x - (-s3), p.y - 0.5f);
173
+ vec2 p1 = vec2_new(p.x - s4, p.y);
174
+ vec2 p2 = vec2_new(p.x - (-s3), p.y - (-0.5f));
175
+
176
+ vec3 d3 = vec3_new(vec2_length(p0), vec2_length(p1), vec2_length(p2));
177
+ vec2 pp = vec2_new(0.0f, 0.0f);
178
+
179
+ if (d3.x > d3.y) pp = p1;
180
+ if (d3.y > d3.z) pp = p2;
181
+ if (d3.z > d3.x && d3.y > d3.x) pp = p0;
182
+
183
+ ln = 0.015f;
184
+ tk = 0.14f + 0.1f * sinf(uv_polar.x * 5.0f + u.time);
185
+
186
+ vec3 C = vec3_new(0.0f, 0.0f, 0.0f);
187
+
188
+ // Tile background (hexagon SDF)
189
+ float d = fmaxf(fabsf(p.x) * s2 + fabsf(p.y) * 0.5f, fabsf(p.y)) - (0.5f - ln);
190
+ vec3 tex_col = proc_texture(vec2_mul(p, 2.0f));
191
+
192
+ float t_bg = smoothstep(px, -px, d);
193
+ C.x = mix_f(0.0125f, tex_col.x, t_bg);
194
+ C.y = mix_f(0.0125f, tex_col.y, t_bg);
195
+ C.z = mix_f(0.0125f, tex_col.z, t_bg);
196
+
197
+ // Shading
198
+ float shade1 = clamp_f(1.0f - (H.y + 0.15f), 0.0f, 1.0f);
199
+ float t_s1 = mix_f(smoothstep(px, -px, d + 0.035f), 0.0f, shade1);
200
+ C.x = mix_f(C.x, C.x + 0.1f, t_s1);
201
+ C.y = mix_f(C.y, C.y + 0.1f, t_s1);
202
+ C.z = mix_f(C.z, C.z + 0.1f, t_s1);
203
+
204
+ float shade2 = clamp_f(1.0f - (H.x + 0.5f), 0.0f, 1.0f);
205
+ float t_s2 = mix_f(smoothstep(px, -px, d + 0.025f), 0.0f, shade2);
206
+ C.x = mix_f(C.x, C.x * 0.1f, t_s2);
207
+ C.y = mix_f(C.y, C.y * 0.1f, t_s2);
208
+ C.z = mix_f(C.z, C.z * 0.1f, t_s2);
209
+
210
+ // Base tile distance
211
+ float b = vec2_length(pp) - s3;
212
+ float t_val = 1e5f, g = 1e5f;
213
+ float tg = 1.0f;
214
+
215
+ hs = fract(hs * 53.71f);
216
+
217
+ // Alternate tile patterns
218
+ if (hs > 0.95f) {
219
+ vec2 p4 = mat2_mul(r3_cos, r3_sin, p);
220
+ vec2 p5 = mat2_mul(r2_cos, r2_sin, p);
221
+
222
+ b = vec2_length(vec2_new(p.x, fabsf(p.y) - 0.5f));
223
+ g = fabsf(p5.x);
224
+ t_val = fabsf(p4.x);
225
+ tg = 0.0f;
226
+ } else if (hs > 0.65f) {
227
+ b = fabsf(p.x);
228
+ g = fminf(vec2_length(p1) - s3, vec2_length(vec2_new(p1.x + 1.155f, p1.y)) - s3);
229
+ tg = 0.0f;
230
+ } else if (hs < 0.15f) {
231
+ vec2 p4 = mat2_mul(r3_cos, r3_sin, p);
232
+ vec2 p5 = mat2_mul(r2_cos, r2_sin, p);
233
+
234
+ t_val = fabsf(p.x);
235
+ b = fabsf(p5.x);
236
+ g = fabsf(p4.x);
237
+ tg = 0.0f;
238
+ } else if (hs < 0.22f) {
239
+ b = vec2_length(vec2_new(p.x, fabsf(p.y) - 0.5f));
240
+ g = fminf(vec2_length(p1) - s3, vec2_length(vec2_new(p1.x + 1.155f, p1.y)) - s3);
241
+ }
242
+
243
+ clr = vec3_new(0.420f, 0.278f, 0.043f);
244
+ trm = vec3_new(0.0f, 0.0f, 0.0f);
245
+
246
+ // Draw segments
247
+ draw(t_val, px, &C);
248
+ draw(g, px, &C);
249
+ draw(b, px, &C);
250
+
251
+ // Solid balls
252
+ if (tg > 0.0f) {
253
+ float v = vec2_length(p) - 0.25f;
254
+
255
+ float t1 = smoothstep(0.1f + px, -px, v - 0.01f);
256
+ C.x = mix_f(C.x, C.x * 0.25f, t1);
257
+ C.y = mix_f(C.y, C.y * 0.25f, t1);
258
+ C.z = mix_f(C.z, C.z * 0.25f, t1);
259
+
260
+ float t2 = smoothstep(px, -px, v);
261
+ C.x = mix_f(C.x, clr.x, t2);
262
+ C.y = mix_f(C.y, clr.y, t2);
263
+ C.z = mix_f(C.z, clr.z, t2);
264
+
265
+ float t3 = smoothstep(0.01f + px, -px, v + 0.1f);
266
+ C.x = mix_f(C.x, clamp_f(C.x + 0.2f, C.x, 0.95f), t3);
267
+ C.y = mix_f(C.y, clamp_f(C.y + 0.2f, C.y, 0.95f), t3);
268
+ C.z = mix_f(C.z, clamp_f(C.z + 0.2f, C.z, 0.95f), t3);
269
+
270
+ float t4 = smoothstep(px, -px, fabsf(v) - ln);
271
+ C.x = mix_f(C.x, trm.x, t4);
272
+ C.y = mix_f(C.y, trm.y, t4);
273
+ C.z = mix_f(C.z, trm.z, t4);
274
+ }
275
+
276
+ // Gamma correction
277
+ C.x = powf(C.x, 0.4545f);
278
+ C.y = powf(C.y, 0.4545f);
279
+ C.z = powf(C.z, 0.4545f);
280
+
281
+ return C;
282
+ C
283
+ end
284
+ end
285
+
286
+ # Initialize display
287
+ window = RBGL::GUI::Window.new(width: WIDTH, height: HEIGHT, title: "Hexagon X5")
288
+
289
+ puts "Hexagon X5 - Hexagonal Flow Pattern"
290
+ puts "Original: https://www.shadertoy.com/view/4cVfWG"
291
+ puts "License: Creative Commons Attribution-NonCommercial-ShareAlike 3.0"
292
+ puts "https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en"
293
+ puts "Move mouse to pan"
294
+ puts "Press 'q' or Escape to quit"
295
+
296
+ start_time = Time.now
297
+ frame_count = 0
298
+ last_fps_time = start_time
299
+ running = true
300
+ mouse_x = WIDTH / 2.0
301
+ mouse_y = HEIGHT / 2.0
302
+
303
+ buffer = "\x00" * (WIDTH * HEIGHT * 4)
304
+
305
+ while running && !window.should_close?
306
+ time = Time.now - start_time
307
+
308
+ shader.render(buffer, WIDTH, HEIGHT, { time: time, mouse: [mouse_x, mouse_y] })
309
+
310
+ window.set_pixels(buffer)
311
+
312
+ events = window.poll_events_raw
313
+ events.each do |e|
314
+ case e[:type]
315
+ when :key_press
316
+ running = false if e[:key] == 12 || e[:key] == "q"
317
+ when :mouse_move
318
+ mouse_x = e[:x].to_f
319
+ mouse_y = e[:y].to_f
320
+ end
321
+ end
322
+
323
+ frame_count += 1
324
+ now = Time.now
325
+ if now - last_fps_time >= 1.0
326
+ fps = frame_count / (now - last_fps_time)
327
+ puts "FPS: #{fps.round(1)}"
328
+ frame_count = 0
329
+ last_fps_time = now
330
+ end
331
+ end
332
+
333
+ window.close
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Multiple Return Value Test Shader
4
+ # Tests Ruby-mode multiple return value support in RLSL
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
7
+
8
+ require "rbgl"
9
+ require "rlsl"
10
+
11
+ WIDTH = 640
12
+ HEIGHT = 480
13
+
14
+ shader = RLSL.define(:multi_return_test) do
15
+ uniforms do
16
+ float :time
17
+ end
18
+
19
+ functions do
20
+ # Function that returns multiple values (tuple)
21
+ define :compute_basis, returns: [:vec3, :vec3], params: { n: :vec3 }
22
+ end
23
+
24
+ helpers do
25
+ # Function returning multiple values as an array
26
+ def compute_basis(n)
27
+ # Orthonormal basis calculation
28
+ a = 1.0 / (1.0 + n.z + 0.0001)
29
+ b = n.y * a
30
+ c = 0.0 - n.x * a
31
+ xp = vec3(n.z + b, c, 0.0 - n.x)
32
+ yp = vec3(c, 1.0 - b, 0.0 - n.y)
33
+ [xp, yp]
34
+ end
35
+ end
36
+
37
+ fragment do |frag_coord, resolution, u|
38
+ uv = vec2(
39
+ (frag_coord.x - resolution.x * 0.5) / resolution.y,
40
+ (frag_coord.y - resolution.y * 0.5) / resolution.y
41
+ )
42
+
43
+ # Create a normal from UV coordinates
44
+ t = u.time * 0.5
45
+ n = normalize(vec3(uv.x + sin(t), uv.y + cos(t), 1.0))
46
+
47
+ # Get basis vectors using multiple return values
48
+ tang, binorm = compute_basis(n)
49
+
50
+ # Visualize the basis vectors
51
+ r = abs(tang.x) * 0.5 + 0.5
52
+ g = abs(binorm.y) * 0.5 + 0.5
53
+ b = abs(n.z) * 0.5 + 0.5
54
+
55
+ vec3(r, g, b)
56
+ end
57
+ end
58
+
59
+ # Initialize display
60
+ window = RBGL::GUI::Window.new(width: WIDTH, height: HEIGHT, title: "Multi Return Test")
61
+
62
+ puts "Multiple Return Value Test Shader"
63
+ puts "Tests Ruby-mode multiple return value support"
64
+ puts "Press 'q' or Escape to quit"
65
+
66
+ start_time = Time.now
67
+ frame_count = 0
68
+ last_fps_time = start_time
69
+ running = true
70
+
71
+ buffer = "\x00" * (WIDTH * HEIGHT * 4)
72
+
73
+ while running && !window.should_close?
74
+ time = Time.now - start_time
75
+
76
+ shader.render(buffer, WIDTH, HEIGHT, { time: time })
77
+
78
+ window.set_pixels(buffer)
79
+
80
+ events = window.poll_events_raw
81
+ events.each do |e|
82
+ case e[:type]
83
+ when :key_press
84
+ running = false if e[:key] == 12 || e[:key] == "q"
85
+ end
86
+ end
87
+
88
+ frame_count += 1
89
+ now = Time.now
90
+ if now - last_fps_time >= 1.0
91
+ fps = frame_count / (now - last_fps_time)
92
+ puts "FPS: #{fps.round(1)}"
93
+ frame_count = 0
94
+ last_fps_time = now
95
+ end
96
+ end
97
+
98
+ window.close
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Classic Plasma Effect using Native Shader DSL
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 plasma shader using pure Ruby DSL
14
+ shader = RLSL.define(:plasma) do
15
+ uniforms do
16
+ float :time
17
+ end
18
+
19
+ fragment do |frag_coord, resolution, u|
20
+ uv = frag_coord / resolution.y
21
+ cx = uv.x - 0.5
22
+ cy = uv.y - 0.5
23
+
24
+ v1 = sin(cx * 10.0 + u.time)
25
+ v2 = sin(10.0 * (cx * sin(u.time / 2.0) + cy * cos(u.time / 3.0)) + u.time)
26
+
27
+ cx2 = cx + 0.5 * sin(u.time / 5.0)
28
+ cy2 = cy + 0.5 * cos(u.time / 3.0)
29
+ v3 = sin(sqrt(100.0 * (cx2 * cx2 + cy2 * cy2) + 1.0) + u.time)
30
+
31
+ v = v1 + v2 + v3
32
+
33
+ r = sin(v * PI)
34
+ g = sin(v * PI + 2.0 * PI / 3.0)
35
+ b = sin(v * PI + 4.0 * PI / 3.0)
36
+
37
+ vec3((r + 1.0) * 0.5, (g + 1.0) * 0.5, (b + 1.0) * 0.5)
38
+ end
39
+ end
40
+
41
+ # Initialize display
42
+ window = RBGL::GUI::Window.new(width: WIDTH, height: HEIGHT, title: "Plasma Effect")
43
+
44
+ puts "Plasma Effect - Native Shader DSL"
45
+ puts "Press 'q' or Escape to quit"
46
+
47
+ start_time = Time.now
48
+ frame_count = 0
49
+ last_fps_time = start_time
50
+ running = true
51
+
52
+ buffer = "\x00" * (WIDTH * HEIGHT * 4)
53
+
54
+ while running && !window.should_close?
55
+ time = Time.now - start_time
56
+
57
+ shader.render(buffer, WIDTH, HEIGHT, { time: time })
58
+
59
+ window.set_pixels(buffer)
60
+
61
+ events = window.poll_events_raw
62
+ events.each do |e|
63
+ if e[:type] == :key_press && (e[:key] == 12 || e[:key] == "q")
64
+ running = false
65
+ end
66
+ end
67
+
68
+ frame_count += 1
69
+ now = Time.now
70
+ if now - last_fps_time >= 1.0
71
+ fps = frame_count / (now - last_fps_time)
72
+ puts "FPS: #{fps.round(1)}"
73
+ frame_count = 0
74
+ last_fps_time = now
75
+ end
76
+ end
77
+
78
+ window.close
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Raymarching Sphere - Native Shader DSL
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
+ shader = RLSL.define(:raymarch) do
14
+ uniforms do
15
+ float :time
16
+ end
17
+
18
+ fragment do |frag_coord, resolution, u|
19
+ uv = frag_coord / resolution.y
20
+
21
+ # Ray origin (camera position)
22
+ ro = vec3(0.0, 0.0, -3.0)
23
+
24
+ # Ray direction
25
+ centered = uv - vec2(0.5, 0.5)
26
+ rd = normalize(vec3(centered.x, centered.y, 1.0))
27
+
28
+ # Sphere center (animated)
29
+ sphere_center = vec3(
30
+ sin(u.time) * 0.5,
31
+ cos(u.time * 0.7) * 0.3,
32
+ 0.0
33
+ )
34
+ sphere_radius = 0.8
35
+
36
+ # Ray-sphere intersection
37
+ oc = ro - sphere_center
38
+ a = dot(rd, rd)
39
+ b = 2.0 * dot(oc, rd)
40
+ c = dot(oc, oc) - sphere_radius * sphere_radius
41
+ discriminant = b * b - 4.0 * a * c
42
+
43
+ if discriminant > 0.0
44
+ t = (0.0 - b - sqrt(discriminant)) / (2.0 * a)
45
+ if t > 0.0
46
+ # Hit point
47
+ hit = ro + rd * t
48
+
49
+ # Normal at hit point
50
+ normal = normalize(hit - sphere_center)
51
+
52
+ # Light direction (animated)
53
+ light_dir = normalize(vec3(
54
+ sin(u.time * 0.5),
55
+ 1.0,
56
+ cos(u.time * 0.3)
57
+ ))
58
+
59
+ # Diffuse lighting
60
+ diff = clamp(dot(normal, light_dir), 0.0, 1.0)
61
+
62
+ # Specular
63
+ view_dir = rd * -1.0
64
+ reflect_dir = normal * 2.0 * dot(normal, light_dir) - light_dir
65
+ spec = pow(clamp(dot(view_dir, reflect_dir), 0.0, 1.0), 32.0)
66
+
67
+ # Base color (hue shifts with time)
68
+ base_color = vec3(
69
+ 0.5 + 0.5 * sin(u.time),
70
+ 0.5 + 0.5 * sin(u.time + 2.0),
71
+ 0.5 + 0.5 * sin(u.time + 4.0)
72
+ )
73
+
74
+ # Combine
75
+ ambient = 0.1
76
+ base_color * (ambient + diff * 0.7) + vec3(spec * 0.5, spec * 0.5, spec * 0.5)
77
+ else
78
+ # Behind camera
79
+ vec3(0.05, 0.05, 0.1)
80
+ end
81
+ else
82
+ # Background gradient
83
+ t = uv.y
84
+ mix(vec3(0.1, 0.1, 0.2), vec3(0.02, 0.02, 0.05), t)
85
+ end
86
+ end
87
+ end
88
+
89
+ # Initialize display
90
+ window = RBGL::GUI::Window.new(width: WIDTH, height: HEIGHT, title: "Raymarched Sphere")
91
+
92
+ puts "Raymarching Sphere - Native Shader DSL"
93
+ puts "Press 'q' or Escape to quit"
94
+
95
+ start_time = Time.now
96
+ frame_count = 0
97
+ last_fps_time = start_time
98
+ running = true
99
+
100
+ buffer = "\x00" * (WIDTH * HEIGHT * 4)
101
+
102
+ while running && !window.should_close?
103
+ time = Time.now - start_time
104
+
105
+ shader.render(buffer, WIDTH, HEIGHT, { time: time })
106
+
107
+ window.set_pixels(buffer)
108
+
109
+ events = window.poll_events_raw
110
+ events.each do |e|
111
+ if e[:type] == :key_press && (e[:key] == 12 || e[:key] == "q")
112
+ running = false
113
+ end
114
+ end
115
+
116
+ frame_count += 1
117
+ now = Time.now
118
+ if now - last_fps_time >= 1.0
119
+ fps = frame_count / (now - last_fps_time)
120
+ puts "FPS: #{fps.round(1)}"
121
+ frame_count = 0
122
+ last_fps_time = now
123
+ end
124
+ end
125
+
126
+ window.close