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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE +21 -0
- data/README.md +123 -0
- data/Rakefile +12 -0
- data/examples/array_test.rb +99 -0
- data/examples/chemical_heartbeat.rb +166 -0
- data/examples/color_test.rb +61 -0
- data/examples/cube_spinning.rb +87 -0
- data/examples/dark_transit.rb +166 -0
- data/examples/fractured_orb.rb +428 -0
- data/examples/fractured_orb_rb.rb +598 -0
- data/examples/gradient.rb +84 -0
- data/examples/hexagonal_flow.rb +333 -0
- data/examples/multi_return_test.rb +98 -0
- data/examples/plasma.rb +78 -0
- data/examples/sphere_raymarch.rb +126 -0
- data/examples/teapot.rb +362 -0
- data/examples/teapot_mcu.rb +344 -0
- data/examples/triangle_basic.rb +36 -0
- data/examples/triangle_window.rb +62 -0
- data/examples/window_test.rb +36 -0
- data/lib/rbgl/engine/buffer.rb +160 -0
- data/lib/rbgl/engine/context.rb +157 -0
- data/lib/rbgl/engine/framebuffer.rb +115 -0
- data/lib/rbgl/engine/pipeline.rb +35 -0
- data/lib/rbgl/engine/rasterizer.rb +213 -0
- data/lib/rbgl/engine/shader.rb +324 -0
- data/lib/rbgl/engine/texture.rb +125 -0
- data/lib/rbgl/engine.rb +15 -0
- data/lib/rbgl/gui/backend.rb +76 -0
- data/lib/rbgl/gui/cocoa/backend.rb +121 -0
- data/lib/rbgl/gui/event.rb +34 -0
- data/lib/rbgl/gui/file_backend.rb +91 -0
- data/lib/rbgl/gui/wayland/backend.rb +126 -0
- data/lib/rbgl/gui/wayland/connection.rb +331 -0
- data/lib/rbgl/gui/window.rb +148 -0
- data/lib/rbgl/gui/x11/backend.rb +156 -0
- data/lib/rbgl/gui/x11/connection.rb +344 -0
- data/lib/rbgl/gui.rb +16 -0
- data/lib/rbgl/version.rb +5 -0
- data/lib/rbgl.rb +6 -0
- metadata +114 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Dark Transit
|
|
4
|
+
# Original: https://www.shadertoy.com/view/WcdczB
|
|
5
|
+
# 28 steps raymarching tunnel
|
|
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(:dark_transit) do
|
|
18
|
+
uniforms do
|
|
19
|
+
float :time
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Register helper function signatures for Ruby mode
|
|
23
|
+
functions do
|
|
24
|
+
define :get_t, returns: :float, params: { time: :float }
|
|
25
|
+
define :path_point, returns: :vec3, params: { z: :float }
|
|
26
|
+
define :noise_a, returns: :float, params: { f: :float, h: :float, k: :float, p: :vec3 }
|
|
27
|
+
define :cross_prod, returns: :vec3, params: { a: :vec3, b: :vec3 }
|
|
28
|
+
define :mat3_mul, returns: :vec3, params: { c0: :vec3, c1: :vec3, c2: :vec3, v: :vec3 }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
helpers do
|
|
32
|
+
def get_t(time)
|
|
33
|
+
time * 4.0 + 5.0 + 5.0 * sin(time * 0.3)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def path_point(z)
|
|
37
|
+
vec3(12.0 * cos(z * 0.1), 12.0 * cos(z * 0.12), z)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def noise_a(f, h, k, p)
|
|
41
|
+
abs(h * (sin(f * p.x * k) + sin(f * p.y * k) + sin(f * p.z * k))) / k
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def cross_prod(a, b)
|
|
45
|
+
vec3(
|
|
46
|
+
a.y * b.z - a.z * b.y,
|
|
47
|
+
a.z * b.x - a.x * b.z,
|
|
48
|
+
a.x * b.y - a.y * b.x
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def mat3_mul(c0, c1, c2, v)
|
|
53
|
+
vec3(
|
|
54
|
+
c0.x * v.x + c1.x * v.y + c2.x * v.z,
|
|
55
|
+
c0.y * v.x + c1.y * v.y + c2.y * v.z,
|
|
56
|
+
c0.z * v.x + c1.z * v.y + c2.z * v.z
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
fragment do |frag_coord, resolution, u|
|
|
62
|
+
i_time = u.time
|
|
63
|
+
t_val = get_t(i_time)
|
|
64
|
+
|
|
65
|
+
# Scaled coords
|
|
66
|
+
screen_uv = vec2(
|
|
67
|
+
(frag_coord.x - resolution.x * 0.5) / resolution.y,
|
|
68
|
+
(frag_coord.y - resolution.y * 0.5) / resolution.y
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Cinema bars
|
|
72
|
+
if abs(screen_uv.y) > 0.375
|
|
73
|
+
vec3(0.0, 0.0, 0.0)
|
|
74
|
+
else
|
|
75
|
+
# Setup variables
|
|
76
|
+
t = 0.0
|
|
77
|
+
s = 0.0
|
|
78
|
+
i = 0.0
|
|
79
|
+
d = 0.0
|
|
80
|
+
e = 0.0
|
|
81
|
+
c = vec3(0.0, 0.0, 0.0)
|
|
82
|
+
|
|
83
|
+
# Camera path
|
|
84
|
+
p = path_point(t_val)
|
|
85
|
+
z_dir = normalize(path_point(t_val + 4.0) - p)
|
|
86
|
+
x_dir = normalize(vec3(z_dir.z, 0.0, 0.0 - z_dir.x))
|
|
87
|
+
|
|
88
|
+
# View matrix
|
|
89
|
+
neg_x = vec3(0.0 - x_dir.x, 0.0 - x_dir.y, 0.0 - x_dir.z)
|
|
90
|
+
cross_xz = cross_prod(x_dir, z_dir)
|
|
91
|
+
ray_d = mat3_mul(neg_x, cross_xz, z_dir, vec3(screen_uv.x, screen_uv.y, 1.0))
|
|
92
|
+
|
|
93
|
+
# Raymarching loop
|
|
94
|
+
while i < 28.0 && d < 30.0
|
|
95
|
+
i = i + 1.0
|
|
96
|
+
p = p + ray_d * s
|
|
97
|
+
x_dir = path_point(p.z)
|
|
98
|
+
t = sin(i_time)
|
|
99
|
+
|
|
100
|
+
# Orb position
|
|
101
|
+
orb = vec3(x_dir.x + t, x_dir.y + t * 2.0, 6.0 + t_val + t * 2.0)
|
|
102
|
+
e = length(p - orb) - 0.01
|
|
103
|
+
|
|
104
|
+
# Tunnel distance
|
|
105
|
+
px_offset = x_dir.x + 6.0
|
|
106
|
+
d1 = length(vec2(p.x - px_offset, p.y - px_offset))
|
|
107
|
+
d2 = length(vec2(p.x - x_dir.x, p.y - x_dir.y))
|
|
108
|
+
|
|
109
|
+
s = cos(p.z * 0.6) * 2.0 + 4.0 - min(d1, d2) + noise_a(4.0, 0.25, 0.1, p) + noise_a(8.0, 0.22, 2.0, p)
|
|
110
|
+
s = min(e, 0.01 + 0.25 * abs(s))
|
|
111
|
+
d = d + s
|
|
112
|
+
|
|
113
|
+
# Accumulate color
|
|
114
|
+
inv_s = 1.0 / s
|
|
115
|
+
inv_e = 10.0 / max(e, 0.6)
|
|
116
|
+
c = vec3(c.x + inv_s + inv_e, c.y + inv_s + inv_e * 2.0, c.z + inv_s + inv_e * 5.0)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Output color
|
|
120
|
+
vec3(c.x * c.x / 1000000.0, c.y * c.y / 1000000.0, c.z * c.z / 1000000.0)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Initialize display
|
|
126
|
+
window = RBGL::GUI::Window.new(width: WIDTH, height: HEIGHT, title: "Dark Transit")
|
|
127
|
+
|
|
128
|
+
puts "Dark Transit"
|
|
129
|
+
puts "Original: https://www.shadertoy.com/view/WcdczB"
|
|
130
|
+
puts "License: Creative Commons Attribution-NonCommercial-ShareAlike 3.0"
|
|
131
|
+
puts "https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en"
|
|
132
|
+
puts "Press 'q' or Escape to quit"
|
|
133
|
+
|
|
134
|
+
start_time = Time.now
|
|
135
|
+
frame_count = 0
|
|
136
|
+
last_fps_time = start_time
|
|
137
|
+
running = true
|
|
138
|
+
|
|
139
|
+
buffer = "\x00" * (WIDTH * HEIGHT * 4)
|
|
140
|
+
|
|
141
|
+
while running && !window.should_close?
|
|
142
|
+
time = Time.now - start_time
|
|
143
|
+
|
|
144
|
+
shader.render(buffer, WIDTH, HEIGHT, { time: time })
|
|
145
|
+
|
|
146
|
+
window.set_pixels(buffer)
|
|
147
|
+
|
|
148
|
+
events = window.poll_events_raw
|
|
149
|
+
events.each do |e|
|
|
150
|
+
case e[:type]
|
|
151
|
+
when :key_press
|
|
152
|
+
running = false if e[:key] == 12 || e[:key] == "q"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
frame_count += 1
|
|
157
|
+
now = Time.now
|
|
158
|
+
if now - last_fps_time >= 1.0
|
|
159
|
+
fps = frame_count / (now - last_fps_time)
|
|
160
|
+
puts "FPS: #{fps.round(1)}"
|
|
161
|
+
frame_count = 0
|
|
162
|
+
last_fps_time = now
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
window.close
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Fractured Orb (Metal Compute Shader)
|
|
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_metal(:fractured_orb_metal) do
|
|
18
|
+
uniforms do
|
|
19
|
+
float :time
|
|
20
|
+
float :rand_seed
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
helpers(:c) do
|
|
24
|
+
<<~MSL
|
|
25
|
+
#define PHI 1.618033988749895f
|
|
26
|
+
#define PI 3.14159265359f
|
|
27
|
+
#define TAU 6.28318530718f
|
|
28
|
+
#define MAX_DISPERSE 5
|
|
29
|
+
#define MAX_BOUNCE 10
|
|
30
|
+
|
|
31
|
+
constant float OUTER = 0.35f;
|
|
32
|
+
constant float INNER = 0.24f;
|
|
33
|
+
|
|
34
|
+
// pR - 2D rotation
|
|
35
|
+
float2 pR(float2 p, float a) {
|
|
36
|
+
float c = cos(a), s = sin(a);
|
|
37
|
+
return float2(c * p.x + s * p.y, -s * p.x + c * p.y);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// smax - smooth maximum
|
|
41
|
+
float smax(float a, float b, float r) {
|
|
42
|
+
float ua = max(r + a, 0.0f);
|
|
43
|
+
float ub = max(r + b, 0.0f);
|
|
44
|
+
return min(-r, max(a, b)) + sqrt(ua * ua + ub * ub);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// vmax
|
|
48
|
+
float vmax2(float2 v) { return max(v.x, v.y); }
|
|
49
|
+
float vmax3(float3 v) { return max(max(v.x, v.y), v.z); }
|
|
50
|
+
|
|
51
|
+
// fBox - box SDF
|
|
52
|
+
float fBox2(float2 p, float2 b) {
|
|
53
|
+
float2 d = abs(p) - b;
|
|
54
|
+
return length(max(d, float2(0))) + vmax2(min(d, float2(0)));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
float fBox3(float3 p, float3 b) {
|
|
58
|
+
float3 d = abs(p) - b;
|
|
59
|
+
return length(max(d, float3(0))) + vmax3(min(d, float3(0)));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// erot - rotate on axis
|
|
63
|
+
float3 erot(float3 p, float3 ax, float ro) {
|
|
64
|
+
float d = dot(ax, p);
|
|
65
|
+
float3 dax = ax * d;
|
|
66
|
+
float c = cos(ro), s = sin(ro);
|
|
67
|
+
return mix(dax, p, c) + s * cross(ax, p);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Spectrum palette (IQ)
|
|
71
|
+
float3 spectrum(float n) {
|
|
72
|
+
float3 a = float3(0.5f);
|
|
73
|
+
float3 b = float3(0.5f);
|
|
74
|
+
float3 c = float3(1.0f);
|
|
75
|
+
float3 d = float3(0.0f, 0.33f, 0.67f);
|
|
76
|
+
return a + b * cos(TAU * (c * n + d));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// expImpulse
|
|
80
|
+
float expImpulse(float x, float k) {
|
|
81
|
+
float h = k * x;
|
|
82
|
+
return h * exp(1.0f - h);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// boolSign
|
|
86
|
+
float boolSign(float v) {
|
|
87
|
+
return v > 0.0f ? 1.0f : -1.0f;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
float3 boolSign3(float3 v) {
|
|
91
|
+
return float3(boolSign(v.x), boolSign(v.y), boolSign(v.z));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Icosahedron vertex (optimized by iq)
|
|
95
|
+
float3 icosahedronVertex(float3 p) {
|
|
96
|
+
float3 ap = abs(p);
|
|
97
|
+
float3 v = float3(PHI, 1.0f, 0.0f);
|
|
98
|
+
if (ap.x + ap.z * PHI > dot(ap, v)) v = float3(1.0f, 0.0f, PHI);
|
|
99
|
+
if (ap.z + ap.y * PHI > dot(ap, v)) v = float3(0.0f, PHI, 1.0f);
|
|
100
|
+
return v * 0.52573111f * boolSign3(p);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Dodecahedron vertex (optimized by iq)
|
|
104
|
+
float3 dodecahedronVertex(float3 p) {
|
|
105
|
+
float3 ap = abs(p);
|
|
106
|
+
float3 v = float3(PHI);
|
|
107
|
+
float3 v2 = float3(0.0f, 1.0f, PHI + 1.0f);
|
|
108
|
+
float3 v3 = v2.yzx;
|
|
109
|
+
float3 v4 = v2.zxy;
|
|
110
|
+
if (dot(ap, v2) > dot(ap, v)) v = v2;
|
|
111
|
+
if (dot(ap, v3) > dot(ap, v)) v = v3;
|
|
112
|
+
if (dot(ap, v4) > dot(ap, v)) v = v4;
|
|
113
|
+
return v * 0.35682209f * boolSign3(p);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Object SDF
|
|
117
|
+
float object_sdf(float3 p) {
|
|
118
|
+
float d = length(p) - OUTER;
|
|
119
|
+
d = max(d, -d - (OUTER - INNER));
|
|
120
|
+
return d;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Map function
|
|
124
|
+
float2 map(float3 p, float anim_time) {
|
|
125
|
+
float scale = 2.5f;
|
|
126
|
+
p /= scale;
|
|
127
|
+
|
|
128
|
+
float outerBound = length(p) - OUTER;
|
|
129
|
+
|
|
130
|
+
float spin = anim_time * (PI / 2.0f) - 0.15f;
|
|
131
|
+
p.xz = pR(p.xz, spin);
|
|
132
|
+
|
|
133
|
+
// Buckyball faces
|
|
134
|
+
float3 va = icosahedronVertex(p);
|
|
135
|
+
float3 vb = dodecahedronVertex(p);
|
|
136
|
+
|
|
137
|
+
float side = boolSign(dot(p, cross(va, vb)));
|
|
138
|
+
float r = TAU / 5.0f * side;
|
|
139
|
+
float3 vc = erot(vb, va, r);
|
|
140
|
+
float3 vd = erot(vb, va, -r);
|
|
141
|
+
|
|
142
|
+
float d = 1e12f;
|
|
143
|
+
float3 pp = p;
|
|
144
|
+
|
|
145
|
+
for (int i = 0; i < 4; i++) {
|
|
146
|
+
// Animation
|
|
147
|
+
float t = fmod(anim_time * 2.0f / 3.0f + 0.25f - dot(va.xy, float2(1.0f, -1.0f)) / 30.0f, 1.0f);
|
|
148
|
+
if (t < 0.0f) t += 1.0f;
|
|
149
|
+
float t2 = clamp(t * 5.0f - 1.7f, 0.0f, 1.0f);
|
|
150
|
+
float explode = 1.0f - pow(1.0f - t2, 10.0f);
|
|
151
|
+
explode *= 1.0f - pow(t2, 5.0f);
|
|
152
|
+
explode += (smoothstep(0.32f, 0.34f, t) - smoothstep(0.34f, 0.5f, t)) * 0.05f;
|
|
153
|
+
explode *= 1.4f;
|
|
154
|
+
t2 = max(t - 0.53f, 0.0f) * 1.2f;
|
|
155
|
+
float wobble = sin(expImpulse(t2, 20.0f) * 2.2f + pow(3.0f * t2, 1.5f) * 2.0f * TAU - PI) * smoothstep(0.4f, 0.0f, t2) * 0.2f;
|
|
156
|
+
float anim = wobble + explode;
|
|
157
|
+
p -= va * anim / 2.8f;
|
|
158
|
+
|
|
159
|
+
// Build boundary edge of face
|
|
160
|
+
float edgeA = dot(p, normalize(vb - va));
|
|
161
|
+
float edgeB = dot(p, normalize(vc - va));
|
|
162
|
+
float edgeC = dot(p, normalize(vd - va));
|
|
163
|
+
float edge = max(max(edgeA, edgeB), edgeC) - 0.005f;
|
|
164
|
+
|
|
165
|
+
d = min(d, smax(object_sdf(p), edge, 0.002f));
|
|
166
|
+
|
|
167
|
+
p = pp;
|
|
168
|
+
|
|
169
|
+
// Cycle faces
|
|
170
|
+
float3 va2 = va;
|
|
171
|
+
va = vb; vb = vc; vc = vd; vd = va2;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
float bound = outerBound - 0.002f;
|
|
175
|
+
if (bound * scale > 0.002f) {
|
|
176
|
+
d = min(d, bound);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return float2(d * scale, 1.0f);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Normal calculation
|
|
183
|
+
float3 calc_normal(float3 pos, float anim_time) {
|
|
184
|
+
float3 n = float3(0.0f);
|
|
185
|
+
for (int i = 0; i < 4; i++) {
|
|
186
|
+
float3 e = 0.5773f * (2.0f * float3((((i + 3) >> 1) & 1), ((i >> 1) & 1), (i & 1)) - 1.0f);
|
|
187
|
+
n += e * map(pos + 0.001f * e, anim_time).x;
|
|
188
|
+
}
|
|
189
|
+
return normalize(n);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Spherical matrix multiply
|
|
193
|
+
float3 sphericalMatrix_mul(float2 tp, float3 v) {
|
|
194
|
+
float theta = tp.x, phi = tp.y;
|
|
195
|
+
float cx = cos(theta), cy = cos(phi);
|
|
196
|
+
float sx = sin(theta), sy = sin(phi);
|
|
197
|
+
return float3(
|
|
198
|
+
cy * v.x + (-sy * -sx) * v.y + (-sy * cx) * v.z,
|
|
199
|
+
cx * v.y + sx * v.z,
|
|
200
|
+
sy * v.x + (cy * -sx) * v.y + (cy * cx) * v.z
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Light function
|
|
205
|
+
float3 light_func(float3 origin, float3 rayDir, float2 envOrientation) {
|
|
206
|
+
origin = -origin;
|
|
207
|
+
rayDir = -rayDir;
|
|
208
|
+
origin = sphericalMatrix_mul(envOrientation, origin);
|
|
209
|
+
rayDir = sphericalMatrix_mul(envOrientation, rayDir);
|
|
210
|
+
|
|
211
|
+
float3 pos = float3(-6.0f);
|
|
212
|
+
float3 normal = normalize(pos);
|
|
213
|
+
float3 up = normalize(float3(-1.0f, 1.0f, 0.0f));
|
|
214
|
+
|
|
215
|
+
float denom = dot(rayDir, normal);
|
|
216
|
+
if (abs(denom) < 0.0001f) return float3(0.0f);
|
|
217
|
+
|
|
218
|
+
float t = dot(pos - origin, normal) / denom;
|
|
219
|
+
if (t < 0.0f) return float3(0.0f);
|
|
220
|
+
|
|
221
|
+
float3 point = origin + t * rayDir - pos;
|
|
222
|
+
float3 tangent = cross(normal, up);
|
|
223
|
+
float3 bitangent = cross(normal, tangent);
|
|
224
|
+
float2 uv_l = float2(dot(tangent, point), dot(bitangent, point));
|
|
225
|
+
|
|
226
|
+
float l = smoothstep(0.75f, 0.0f, fBox2(uv_l, float2(0.5f, 2.0f)) - 1.0f);
|
|
227
|
+
l *= smoothstep(6.0f, 0.0f, length(uv_l));
|
|
228
|
+
return float3(l);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Environment function
|
|
232
|
+
float3 env_func(float3 origin, float3 rayDir, float2 envOrientation) {
|
|
233
|
+
origin = -origin;
|
|
234
|
+
rayDir = -rayDir;
|
|
235
|
+
origin = sphericalMatrix_mul(envOrientation, origin);
|
|
236
|
+
rayDir = sphericalMatrix_mul(envOrientation, rayDir);
|
|
237
|
+
|
|
238
|
+
float l = smoothstep(0.0f, 1.7f, dot(rayDir, float3(0.5f, -0.3f, 1.0f))) * 0.4f;
|
|
239
|
+
return float3(0.9f, 0.83f, 1.0f) * l;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Simple hash
|
|
243
|
+
float simple_hash(float seed) {
|
|
244
|
+
return fract(sin(seed * 12.9898f) * 43758.5453f);
|
|
245
|
+
}
|
|
246
|
+
MSL
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
fragment do
|
|
250
|
+
<<~MSL
|
|
251
|
+
float duration = 10.0f / 3.0f;
|
|
252
|
+
float anim_time = fmod(u.time / duration, 1.0f);
|
|
253
|
+
|
|
254
|
+
float2 envOrientation = (float2(81.5f / 187.0f, 119.0f / 187.0f) * 2.0f - 1.0f) * 2.0f;
|
|
255
|
+
|
|
256
|
+
// Shadertoy-style centered UV: (fragCoord - resolution/2) / resolution.y
|
|
257
|
+
float2 screen_uv = uv - float2(resolution.x / resolution.y * 0.5f, 0.5f);
|
|
258
|
+
|
|
259
|
+
float3 col = float3(0.0f);
|
|
260
|
+
float3 BGCOL = float3(0.9f, 0.83f, 1.0f);
|
|
261
|
+
float3 bgCol = BGCOL * 0.22f;
|
|
262
|
+
|
|
263
|
+
float maxDist = 30.0f;
|
|
264
|
+
float3 camOrigin = float3(0.0f, 0.0f, 25.0f);
|
|
265
|
+
float3 camDir = normalize(float3(screen_uv * 0.168f, -1.0f));
|
|
266
|
+
|
|
267
|
+
// First march for depth
|
|
268
|
+
float firstLen = 0.0f;
|
|
269
|
+
float firstResY = 0.0f;
|
|
270
|
+
float3 firstP = camOrigin;
|
|
271
|
+
{
|
|
272
|
+
float len = 0.0f;
|
|
273
|
+
float dist = 0.0f;
|
|
274
|
+
for (int i = 0; i < 300; i++) {
|
|
275
|
+
len += dist * 0.8f;
|
|
276
|
+
float3 p = camOrigin + len * camDir;
|
|
277
|
+
float2 res = map(p, anim_time);
|
|
278
|
+
dist = res.x;
|
|
279
|
+
if (dist < 0.001f) { firstResY = res.y; break; }
|
|
280
|
+
if (len >= maxDist) { len = maxDist; firstResY = 0.0f; break; }
|
|
281
|
+
}
|
|
282
|
+
firstLen = len;
|
|
283
|
+
firstP = camOrigin + len * camDir;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Dispersion loop
|
|
287
|
+
for (int disperse = 0; disperse < MAX_DISPERSE; disperse++) {
|
|
288
|
+
float invert = 1.0f;
|
|
289
|
+
float3 sam = float3(0.0f);
|
|
290
|
+
float3 origin = camOrigin;
|
|
291
|
+
float3 rayDir = camDir;
|
|
292
|
+
|
|
293
|
+
float extinctionDist = 0.0f;
|
|
294
|
+
float wavelength = float(disperse) / float(MAX_DISPERSE);
|
|
295
|
+
|
|
296
|
+
float rand = simple_hash(u.rand_seed + float(disperse) * 0.1f + uv.x * 100.0f + uv.y * 1000.0f);
|
|
297
|
+
wavelength += (rand * 2.0f - 1.0f) * 0.1f;
|
|
298
|
+
|
|
299
|
+
int bounceCount = 0;
|
|
300
|
+
|
|
301
|
+
for (int bounce = 0; bounce < MAX_BOUNCE; bounce++) {
|
|
302
|
+
float hitLen, hitResY;
|
|
303
|
+
float3 p;
|
|
304
|
+
|
|
305
|
+
if (bounce == 0) {
|
|
306
|
+
hitLen = firstLen;
|
|
307
|
+
hitResY = firstResY;
|
|
308
|
+
p = firstP;
|
|
309
|
+
} else {
|
|
310
|
+
// March
|
|
311
|
+
float len = 0.0f;
|
|
312
|
+
float dist = 0.0f;
|
|
313
|
+
hitResY = 0.0f;
|
|
314
|
+
for (int mi = 0; mi < 300; mi++) {
|
|
315
|
+
len += dist;
|
|
316
|
+
p = origin + len * rayDir;
|
|
317
|
+
float2 res = map(p, anim_time);
|
|
318
|
+
dist = res.x * invert;
|
|
319
|
+
if (dist < 0.001f) { hitResY = res.y; break; }
|
|
320
|
+
if (len >= maxDist / 2.0f) { len = maxDist / 2.0f; hitResY = 0.0f; break; }
|
|
321
|
+
}
|
|
322
|
+
hitLen = len;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (invert < 0.0f) {
|
|
326
|
+
extinctionDist += hitLen;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (hitResY == 0.0f) break;
|
|
330
|
+
|
|
331
|
+
float3 nor = calc_normal(p, anim_time) * invert;
|
|
332
|
+
float3 ref = reflect(rayDir, nor);
|
|
333
|
+
|
|
334
|
+
// Shade
|
|
335
|
+
sam += light_func(p, ref, envOrientation) * 0.5f;
|
|
336
|
+
float fresnel = 1.0f - abs(dot(rayDir, nor));
|
|
337
|
+
sam += pow(fresnel, 5.0f) * 0.1f;
|
|
338
|
+
sam *= float3(0.85f, 0.85f, 0.98f);
|
|
339
|
+
|
|
340
|
+
// Refract
|
|
341
|
+
float ior = mix(1.2f, 1.8f, wavelength);
|
|
342
|
+
ior = invert < 0.0f ? ior : 1.0f / ior;
|
|
343
|
+
|
|
344
|
+
float3 raf = refract(rayDir, nor, ior);
|
|
345
|
+
bool tif = (length(raf) < 0.001f);
|
|
346
|
+
rayDir = tif ? ref : raf;
|
|
347
|
+
|
|
348
|
+
float offset = 0.01f / (abs(dot(rayDir, nor)) + 0.0001f);
|
|
349
|
+
origin = p + offset * rayDir;
|
|
350
|
+
invert *= -1.0f;
|
|
351
|
+
|
|
352
|
+
bounceCount = bounce;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
sam += (bounceCount == 0) ? bgCol : env_func(firstP, rayDir, envOrientation);
|
|
356
|
+
|
|
357
|
+
if (bounceCount == 0) {
|
|
358
|
+
col += sam * float(MAX_DISPERSE) / 2.0f;
|
|
359
|
+
break;
|
|
360
|
+
} else {
|
|
361
|
+
float3 spec = spectrum(-wavelength + 0.25f);
|
|
362
|
+
col += sam * spec;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
col /= float(MAX_DISPERSE);
|
|
367
|
+
|
|
368
|
+
// Tonemap
|
|
369
|
+
col = pow(col, float3(1.25f)) * 2.5f;
|
|
370
|
+
col = col / 2.0f * 16.0f;
|
|
371
|
+
col = max(float3(0.0f), col - 0.004f);
|
|
372
|
+
col = (col * (6.2f * col + 0.5f)) / (col * (6.2f * col + 1.7f) + 0.06f);
|
|
373
|
+
|
|
374
|
+
return col;
|
|
375
|
+
MSL
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Initialize display
|
|
380
|
+
window = RBGL::GUI::Window.new(width: WIDTH, height: HEIGHT, title: "Fractured Orb (Metal)")
|
|
381
|
+
|
|
382
|
+
unless window.metal_available?
|
|
383
|
+
puts "Metal is not available!"
|
|
384
|
+
exit 1
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
puts "Fractured Orb"
|
|
388
|
+
puts "Original: https://www.shadertoy.com/view/ttycWW"
|
|
389
|
+
puts "License: Creative Commons Attribution-NonCommercial-ShareAlike 3.0"
|
|
390
|
+
puts "https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en"
|
|
391
|
+
puts "Press 'q' or Escape to quit"
|
|
392
|
+
|
|
393
|
+
start_time = Time.now
|
|
394
|
+
frame_count = 0
|
|
395
|
+
last_fps_time = start_time
|
|
396
|
+
running = true
|
|
397
|
+
|
|
398
|
+
while running && !window.should_close?
|
|
399
|
+
time = Time.now - start_time
|
|
400
|
+
rand_seed = rand * 1000.0
|
|
401
|
+
|
|
402
|
+
begin
|
|
403
|
+
shader.render_metal(window.native_handle, WIDTH, HEIGHT, { time: time, rand_seed: rand_seed })
|
|
404
|
+
rescue => e
|
|
405
|
+
puts "Error: #{e.message}"
|
|
406
|
+
puts e.backtrace.first(10).join("\n")
|
|
407
|
+
running = false
|
|
408
|
+
break
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
events = window.poll_events_raw
|
|
412
|
+
events.each do |e|
|
|
413
|
+
if e[:type] == :key_press && (e[:key] == 12 || e[:key] == "q")
|
|
414
|
+
running = false
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
frame_count += 1
|
|
419
|
+
now = Time.now
|
|
420
|
+
if now - last_fps_time >= 1.0
|
|
421
|
+
fps = frame_count / (now - last_fps_time)
|
|
422
|
+
puts "FPS: #{fps.round(1)}"
|
|
423
|
+
frame_count = 0
|
|
424
|
+
last_fps_time = now
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
window.close
|