rbgl 1.0.0 → 1.0.1

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +7 -1
  4. data/README.md +130 -74
  5. data/Rakefile +7 -0
  6. data/bench/renderer_bench.rb +283 -0
  7. data/docs/.nojekyll +1 -0
  8. data/docs/index.html +38 -0
  9. data/examples/array_test.rb +1 -1
  10. data/examples/color_test.rb +1 -1
  11. data/examples/cube_spinning.rb +1 -1
  12. data/examples/gradient.rb +1 -1
  13. data/examples/multi_return_test.rb +1 -1
  14. data/examples/plasma.rb +1 -1
  15. data/examples/sphere_raymarch.rb +1 -1
  16. data/examples/triangle_window.rb +1 -1
  17. data/exe/rbgl +42 -0
  18. data/lib/rbgl/cli/doctor.rb +155 -0
  19. data/lib/rbgl/engine/buffer.rb +49 -8
  20. data/lib/rbgl/engine/clip_space_clipper.rb +23 -3
  21. data/lib/rbgl/engine/context.rb +23 -6
  22. data/lib/rbgl/engine/framebuffer.rb +137 -41
  23. data/lib/rbgl/engine/immutable_color.rb +32 -0
  24. data/lib/rbgl/engine/rasterizer/attribute_interpolator.rb +100 -10
  25. data/lib/rbgl/engine/rasterizer/line_renderer.rb +30 -14
  26. data/lib/rbgl/engine/rasterizer/point_renderer.rb +3 -3
  27. data/lib/rbgl/engine/rasterizer/triangle_renderer.rb +72 -23
  28. data/lib/rbgl/engine/rasterizer.rb +3 -20
  29. data/lib/rbgl/engine/shader/base_shader.rb +3 -7
  30. data/lib/rbgl/engine/shader/builtins.rb +13 -10
  31. data/lib/rbgl/engine/shader/dynamic_data.rb +18 -10
  32. data/lib/rbgl/engine/texture.rb +74 -50
  33. data/lib/rbgl/engine.rb +3 -1
  34. data/lib/rbgl/gui/backend.rb +12 -7
  35. data/lib/rbgl/gui/backend_factory.rb +7 -3
  36. data/lib/rbgl/gui/cocoa/backend.rb +50 -17
  37. data/lib/rbgl/gui/cocoa/key_mapper.rb +48 -0
  38. data/lib/rbgl/gui/event.rb +4 -2
  39. data/lib/rbgl/gui/file_backend/bmp_writer.rb +2 -2
  40. data/lib/rbgl/gui/file_backend/frame_writer.rb +7 -0
  41. data/lib/rbgl/gui/file_backend/png_writer.rb +18 -0
  42. data/lib/rbgl/gui/file_backend/ppm_writer.rb +1 -0
  43. data/lib/rbgl/gui/file_backend.rb +24 -6
  44. data/lib/rbgl/gui/wayland/backend.rb +127 -55
  45. data/lib/rbgl/gui/wayland/codec.rb +7 -11
  46. data/lib/rbgl/gui/wayland/connection.rb +137 -20
  47. data/lib/rbgl/gui/wayland/event_dispatcher.rb +103 -12
  48. data/lib/rbgl/gui/wayland/global_binder.rb +6 -4
  49. data/lib/rbgl/gui/wayland/input_mapper.rb +61 -0
  50. data/lib/rbgl/gui/wayland/protocol_objects/arguments.rb +14 -2
  51. data/lib/rbgl/gui/wayland/protocol_objects/seat.rb +115 -0
  52. data/lib/rbgl/gui/wayland/protocol_objects/shm.rb +23 -12
  53. data/lib/rbgl/gui/wayland/protocol_objects/xdg_shell.rb +15 -0
  54. data/lib/rbgl/gui/wayland/protocol_objects.rb +1 -0
  55. data/lib/rbgl/gui/window/render_loop.rb +71 -18
  56. data/lib/rbgl/gui/window.rb +39 -8
  57. data/lib/rbgl/gui/x11/atom_cache.rb +4 -1
  58. data/lib/rbgl/gui/x11/backend.rb +66 -28
  59. data/lib/rbgl/gui/x11/connection.rb +216 -86
  60. data/lib/rbgl/gui/x11/event_parser.rb +13 -2
  61. data/lib/rbgl/gui/x11/key_mapper.rb +48 -0
  62. data/lib/rbgl/gui/x11/pixel_encoder.rb +164 -0
  63. data/lib/rbgl/gui/x11/request_encoder.rb +29 -28
  64. data/lib/rbgl/gui/x11/setup_parser.rb +138 -0
  65. data/lib/rbgl/gui/x11/transport.rb +52 -2
  66. data/lib/rbgl/gui/x11/x_authority.rb +107 -0
  67. data/lib/rbgl/gui.rb +2 -5
  68. data/lib/rbgl/version.rb +1 -1
  69. metadata +36 -24
  70. data/examples/chemical_heartbeat.rb +0 -166
  71. data/examples/dark_transit.rb +0 -166
  72. data/examples/fractured_orb.rb +0 -428
  73. data/examples/fractured_orb_rb.rb +0 -598
  74. data/examples/hexagonal_flow.rb +0 -333
  75. data/examples/teapot.rb +0 -362
  76. data/examples/teapot_mcu.rb +0 -344
@@ -1,333 +0,0 @@
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
data/examples/teapot.rb DELETED
@@ -1,362 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Teapot - Raymarched Bezier Curves (Ruby Mode)
4
- # Original: https://www.shadertoy.com/view/MdKcDw
5
- # Created by Sebastien Durand - 2014
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(:teapot_ruby) do
18
- uniforms do
19
- float :time
20
- vec2 :mouse
21
- end
22
-
23
- functions do
24
- # Helper functions
25
- define :cross2d, returns: :float, params: { a: :vec2, b: :vec2 }
26
- define :smin, returns: :float, params: { a: :float, b: :float, k: :float }
27
- define :bezier_dist, returns: :vec2, params: { m: :vec2, n: :vec2, o: :vec2, p: :vec3 }
28
- define :scene_dist, returns: :float, params: { p: :vec3 }
29
- define :basis, returns: [:vec3, :vec3], params: { n: :vec3 }
30
- define :calc_normal, returns: :vec3, params: { p: :vec3, ray: :vec3, t: :float, res_x: :float }
31
- define :compute_brdf, returns: :vec3, params: { n: :vec3, l: :vec3, h: :vec3, r: :vec3, tang: :vec3, binorm: :vec3 }
32
- define :hsv2rgb_smooth, returns: :vec3, params: { h: :float, s: :float, v: :float }
33
- end
34
-
35
- helpers do
36
- # Control points - Body profile (15 points)
37
- A = [
38
- vec2(0.0, 0.0), vec2(0.64, 0.0), vec2(0.64, 0.03),
39
- vec2(0.8, 0.12), vec2(0.8, 0.3), vec2(0.8, 0.48),
40
- vec2(0.64, 0.9), vec2(0.6, 0.93), vec2(0.56, 0.9),
41
- vec2(0.56, 0.96), vec2(0.12, 1.02), vec2(0.0, 1.05),
42
- vec2(0.16, 1.14), vec2(0.2, 1.2), vec2(0.0, 1.2)
43
- ]
44
-
45
- # Control points - Spout (5 points)
46
- T1 = [
47
- vec2(1.16, 0.96), vec2(1.04, 0.9), vec2(1.0, 0.72),
48
- vec2(0.92, 0.48), vec2(0.72, 0.42)
49
- ]
50
-
51
- # Control points - Handle (5 points)
52
- T2 = [
53
- vec2(-0.6, 0.78), vec2(-1.16, 0.84), vec2(-1.16, 0.63),
54
- vec2(-1.2, 0.42), vec2(-0.72, 0.24)
55
- ]
56
-
57
- # Material properties
58
- LO = vec2(0.450, 0.048)
59
- ALPHA_M = vec2(0.045, 0.068)
60
- SCALE = vec3(1.0, 20.0, 10.0)
61
- SURFACE_COLOR = vec3(0.45, 0.54, 1.0)
62
-
63
- # Light direction (pre-normalized: normalize(vec3(1.0, 0.72, 1.0)))
64
- L = vec3(0.6286, 0.4526, 0.6286)
65
-
66
- # Up vector
67
- Y = vec3(0.0, 1.0, 0.0)
68
-
69
- # Constants
70
- ONE_OVER_PI = 0.31830988618
71
-
72
- # Cross product 2D
73
- def cross2d(a, b)
74
- a.x * b.y - b.x * a.y
75
- end
76
-
77
- # Smooth minimum for blending
78
- def smin(a, b, k)
79
- h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0)
80
- mix(b, a, h) - k * h * (1.0 - h)
81
- end
82
-
83
- # Distance to quadratic Bezier curve
84
- def bezier_dist(m, n, o, p)
85
- q = vec2(p.x, p.y)
86
- m = vec2(m.x - q.x, m.y - q.y)
87
- n = vec2(n.x - q.x, n.y - q.y)
88
- o = vec2(o.x - q.x, o.y - q.y)
89
-
90
- x = cross2d(m, o)
91
- y = 2.0 * cross2d(n, m)
92
- z = 2.0 * cross2d(o, n)
93
-
94
- i = vec2(o.x - m.x, o.y - m.y)
95
- j = vec2(o.x - n.x, o.y - n.y)
96
- k = vec2(n.x - m.x, n.y - m.y)
97
-
98
- s = vec2(
99
- 2.0 * (x * i.x + y * j.x + z * k.x),
100
- 2.0 * (x * i.y + y * j.y + z * k.y)
101
- )
102
-
103
- dot_s = s.x * s.x + s.y * s.y
104
- if dot_s < 0.0000000001
105
- dot_s = 0.0000000001
106
- end
107
-
108
- r = vec2(
109
- m.x + (y * z - x * x) * s.y / dot_s,
110
- m.y - (y * z - x * x) * s.x / dot_s
111
- )
112
-
113
- denom = x + x + y + z
114
- if abs(denom) < 0.0000000001
115
- denom = 0.0000000001
116
- end
117
-
118
- t = clamp((cross2d(r, i) + 2.0 * cross2d(k, r)) / denom, 0.0, 1.0)
119
-
120
- jk = vec2(j.x - k.x, j.y - k.y)
121
- r = vec2(
122
- m.x + t * (k.x + k.x + t * jk.x),
123
- m.y + t * (k.y + k.y + t * jk.y)
124
- )
125
-
126
- vec2(sqrt(r.x * r.x + r.y * r.y + p.z * p.z), t)
127
- end
128
-
129
- # Scene distance function
130
- def scene_dist(p)
131
- # Spout curve
132
- h = bezier_dist(T1[2], T1[3], T1[4], p)
133
-
134
- # Handle distance
135
- handle_d = min(
136
- bezier_dist(T2[0], T2[1], T2[2], p).x,
137
- bezier_dist(T2[2], T2[3], T2[4], p).x
138
- ) - 0.06
139
-
140
- # Spout distance
141
- spout_hole = abs(bezier_dist(T1[0], T1[1], T1[2], p).x - 0.07) - 0.01
142
- spout_body = h.x * (1.0 - 0.75 * h.y) - 0.08
143
- spout_d = max(p.y - 0.9, min(spout_hole, spout_body))
144
-
145
- b = min(handle_d, spout_d)
146
-
147
- # Body distance (rotation symmetry)
148
- r_xz = sqrt(p.x * p.x + p.z * p.z)
149
- qq = vec3(r_xz, p.y, 0.0)
150
-
151
- # Body curves (step by 2)
152
- a0 = bezier_dist(A[0], A[1], A[2], qq).x - 0.015
153
- a1 = bezier_dist(A[2], A[3], A[4], qq).x - 0.015
154
- a2 = bezier_dist(A[4], A[5], A[6], qq).x - 0.015
155
- a3 = bezier_dist(A[6], A[7], A[8], qq).x - 0.015
156
- a4 = bezier_dist(A[8], A[9], A[10], qq).x - 0.015
157
- a5 = bezier_dist(A[10], A[11], A[12], qq).x - 0.015
158
- a6 = bezier_dist(A[12], A[13], A[14], qq).x - 0.015
159
-
160
- a = min(a0, min(a1, min(a2, min(a3, min(a4, min(a5, a6)))))) * 0.7
161
-
162
- smin(a, b, 0.02)
163
- end
164
-
165
- # Build orthonormal basis from normal
166
- def basis(n)
167
- a = n.y / (1.0 + n.z + 0.0000000001)
168
- b = n.y * a
169
- c = 0.0 - n.x * a
170
- xp = vec3(n.z + b, c, 0.0 - n.x)
171
- yp = vec3(c, 1.0 - b, 0.0 - n.y)
172
- [xp, yp]
173
- end
174
-
175
- # Normal calculation
176
- def calc_normal(p, ray, t, res_x)
177
- eps = 0.4 * t / res_x
178
-
179
- d0 = scene_dist(p)
180
- dx = scene_dist(vec3(p.x + eps, p.y, p.z)) - d0
181
- dy = scene_dist(vec3(p.x, p.y + eps, p.z)) - d0
182
- dz = scene_dist(vec3(p.x, p.y, p.z + eps)) - d0
183
-
184
- grad = vec3(dx, dy, dz)
185
-
186
- # Prevent normals pointing away from camera
187
- d = grad.x * ray.x + grad.y * ray.y + grad.z * ray.z
188
- if d > 0.0
189
- grad = vec3(grad.x - ray.x * d, grad.y - ray.y * d, grad.z - ray.z * d)
190
- end
191
-
192
- normalize(grad)
193
- end
194
-
195
- # BRDF computation
196
- def compute_brdf(n, l, h, r, tang, binorm)
197
- e1 = (h.x * tang.x + h.y * tang.y + h.z * tang.z) / ALPHA_M.x
198
- e2 = (h.x * binorm.x + h.y * binorm.y + h.z * binorm.z) / ALPHA_M.y
199
- hn = h.x * n.x + h.y * n.y + h.z * n.z
200
- big_e = 0.0 - 2.0 * ((e1 * e1 + e2 * e2) / (1.0 + hn))
201
-
202
- cos_i = n.x * l.x + n.y * l.y + n.z * l.z
203
- cos_r = n.x * r.x + n.y * r.y + n.z * r.z
204
- denom = sqrt(abs(cos_i * cos_r) + 0.000001)
205
-
206
- brdf = LO.x * ONE_OVER_PI + LO.y * (1.0 / denom) * (1.0 / (4.0 * PI * ALPHA_M.x * ALPHA_M.y)) * exp(big_e)
207
-
208
- intensity = SCALE.x * LO.x * ONE_OVER_PI + SCALE.y * LO.y * cos_i * brdf + SCALE.z * hn * LO.y
209
-
210
- vec3(SURFACE_COLOR.x * intensity, SURFACE_COLOR.y * intensity, SURFACE_COLOR.z * intensity)
211
- end
212
-
213
- # HSV to RGB with cubic smoothing
214
- def hsv2rgb_smooth(h, s, v)
215
- rx = abs(mod(h * 6.0 + 0.0, 6.0) - 3.0) - 1.0
216
- gx = abs(mod(h * 6.0 + 4.0, 6.0) - 3.0) - 1.0
217
- bx = abs(mod(h * 6.0 + 2.0, 6.0) - 3.0) - 1.0
218
-
219
- rx = clamp(rx, 0.0, 1.0)
220
- gx = clamp(gx, 0.0, 1.0)
221
- bx = clamp(bx, 0.0, 1.0)
222
-
223
- # Cubic smoothing
224
- rx = rx * rx * (3.0 - 2.0 * rx)
225
- gx = gx * gx * (3.0 - 2.0 * gx)
226
- bx = bx * bx * (3.0 - 2.0 * bx)
227
-
228
- vec3(
229
- v * (1.0 + s * (rx - 1.0)),
230
- v * (1.0 + s * (gx - 1.0)),
231
- v * (1.0 + s * (bx - 1.0))
232
- )
233
- end
234
- end
235
-
236
- fragment do |frag_coord, resolution, u|
237
- # UV coordinates (aspect-ratio preserving, same as Metal)
238
- uv = vec2(frag_coord.x / resolution.y, frag_coord.y / resolution.y)
239
-
240
- # Normalized screen coordinates
241
- q = vec2(uv.x * resolution.y / resolution.x, uv.y)
242
-
243
- # Centered coordinates for camera ray
244
- p = vec2(q.x * 2.0 - 1.0, q.y * 2.0 - 1.0)
245
- p = vec2(p.x * resolution.x / resolution.y, p.y)
246
-
247
- # Camera with mouse control
248
- mouse_x = u.mouse.x / resolution.x
249
- mouse_y = u.mouse.y / resolution.y
250
- cam_angle = 5.0 + 0.2 * u.time + 4.0 * mouse_x
251
-
252
- origin = vec3(cos(cam_angle) * 3.5, (0.7 - mouse_y) * 3.5, sin(cam_angle) * 3.5)
253
- target = vec3(Y.x * 0.4, Y.y * 0.4, Y.z * 0.4)
254
- w = normalize(vec3(target.x - origin.x, target.y - origin.y, target.z - origin.z))
255
-
256
- # Camera basis
257
- cam_u = normalize(vec3(
258
- w.y * Y.z - w.z * Y.y,
259
- w.z * Y.x - w.x * Y.z,
260
- w.x * Y.y - w.y * Y.x
261
- ))
262
- cam_v = vec3(
263
- cam_u.y * w.z - cam_u.z * w.y,
264
- cam_u.z * w.x - cam_u.x * w.z,
265
- cam_u.x * w.y - cam_u.y * w.x
266
- )
267
-
268
- ray = normalize(vec3(
269
- cam_u.x * p.x + cam_v.x * p.y + w.x * 2.0,
270
- cam_u.y * p.x + cam_v.y * p.y + w.y * 2.0,
271
- cam_u.z * p.x + cam_v.z * p.y + w.z * 2.0
272
- ))
273
-
274
- # Raymarching
275
- t = 0.0
276
- h = 0.1
277
- i = 0.0
278
- while i < 48.0 && h > 0.0001 && t < 4.7
279
- pos = vec3(origin.x + ray.x * t, origin.y + ray.y * t, origin.z + ray.z * t)
280
- h = scene_dist(pos)
281
- t = t + h
282
- i = i + 1.0
283
- end
284
-
285
- # Background gradient
286
- color = mix(
287
- hsv2rgb_smooth(0.5 + u.time * 0.02, 0.35, 0.4),
288
- hsv2rgb_smooth(0.0 - 0.5 + u.time * 0.02, 0.35, 0.7),
289
- q.y
290
- )
291
-
292
- if h < 0.001
293
- hit = vec3(origin.x + ray.x * t, origin.y + ray.y * t, origin.z + ray.z * t)
294
- n = calc_normal(hit, ray, t, resolution.x)
295
-
296
- big_v = normalize(vec3(origin.x - hit.x, origin.y - hit.y, origin.z - hit.z))
297
- big_h = normalize(vec3(L.x + big_v.x, L.y + big_v.y, L.z + big_v.z))
298
- dot_nl = n.x * L.x + n.y * L.y + n.z * L.z
299
- big_r = normalize(vec3(
300
- n.x * 2.0 * dot_nl - L.x,
301
- n.y * 2.0 * dot_nl - L.y,
302
- n.z * 2.0 * dot_nl - L.z
303
- ))
304
-
305
- tang, binorm = basis(n)
306
- color = compute_brdf(n, L, big_h, big_r, tang, binorm)
307
- end
308
-
309
- # Vignette
310
- vignette = pow(16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y), 0.16)
311
- vec3(color.x * vignette, color.y * vignette, color.z * vignette)
312
- end
313
- end
314
-
315
- # Initialize display
316
- window = RBGL::GUI::Window.new(width: WIDTH, height: HEIGHT, title: "Teapot Ruby (Raymarched Bezier)")
317
-
318
- puts "Teapot - Raymarched Bezier Curves (Ruby Mode)"
319
- puts "Original: https://www.shadertoy.com/view/MdKcDw"
320
- puts "License: Creative Commons Attribution-NonCommercial-ShareAlike 3.0"
321
- puts "https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en"
322
- puts "Move mouse to rotate camera"
323
- puts "Press 'q' or Escape to quit"
324
-
325
- start_time = Time.now
326
- frame_count = 0
327
- last_fps_time = start_time
328
- running = true
329
- mouse_x = 0.0
330
- mouse_y = 0.0
331
-
332
- buffer = "\x00" * (WIDTH * HEIGHT * 4)
333
-
334
- while running && !window.should_close?
335
- time = Time.now - start_time
336
-
337
- shader.render(buffer, WIDTH, HEIGHT, { time: time, mouse: [mouse_x, mouse_y] })
338
-
339
- window.set_pixels(buffer)
340
-
341
- events = window.poll_events_raw
342
- events.each do |e|
343
- case e[:type]
344
- when :key_press
345
- running = false if e[:key] == 12 || e[:key] == "q"
346
- when :mouse_move
347
- mouse_x = e[:x].to_f
348
- mouse_y = e[:y].to_f
349
- end
350
- end
351
-
352
- frame_count += 1
353
- now = Time.now
354
- if now - last_fps_time >= 1.0
355
- fps = frame_count / (now - last_fps_time)
356
- puts "FPS: #{fps.round(1)}"
357
- frame_count = 0
358
- last_fps_time = now
359
- end
360
- end
361
-
362
- window.close