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,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module Engine
|
|
5
|
+
class Rasterizer
|
|
6
|
+
attr_accessor :viewport
|
|
7
|
+
|
|
8
|
+
def initialize(framebuffer)
|
|
9
|
+
@framebuffer = framebuffer
|
|
10
|
+
@viewport = { x: 0, y: 0, width: framebuffer.width, height: framebuffer.height }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def rasterize_triangle(v0, v1, v2, fragment_shader, uniforms, cull_mode: :none)
|
|
14
|
+
p0 = viewport_transform(v0[:position])
|
|
15
|
+
p1 = viewport_transform(v1[:position])
|
|
16
|
+
p2 = viewport_transform(v2[:position])
|
|
17
|
+
|
|
18
|
+
min_x = [p0.x, p1.x, p2.x].min.floor.clamp(0, @framebuffer.width - 1)
|
|
19
|
+
max_x = [p0.x, p1.x, p2.x].max.ceil.clamp(0, @framebuffer.width - 1)
|
|
20
|
+
min_y = [p0.y, p1.y, p2.y].min.floor.clamp(0, @framebuffer.height - 1)
|
|
21
|
+
max_y = [p0.y, p1.y, p2.y].max.ceil.clamp(0, @framebuffer.height - 1)
|
|
22
|
+
|
|
23
|
+
area = edge_function(p0, p1, p2)
|
|
24
|
+
return if area.abs < 1e-10
|
|
25
|
+
|
|
26
|
+
case cull_mode
|
|
27
|
+
when :back
|
|
28
|
+
return if area < 0
|
|
29
|
+
when :front
|
|
30
|
+
return if area > 0
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
(min_y..max_y).each do |y|
|
|
34
|
+
(min_x..max_x).each do |x|
|
|
35
|
+
px = x + 0.5
|
|
36
|
+
py = y + 0.5
|
|
37
|
+
p = Larb::Vec2.new(px, py)
|
|
38
|
+
|
|
39
|
+
w0 = edge_function(p1, p2, p)
|
|
40
|
+
w1 = edge_function(p2, p0, p)
|
|
41
|
+
w2 = edge_function(p0, p1, p)
|
|
42
|
+
|
|
43
|
+
if (w0 >= 0 && w1 >= 0 && w2 >= 0) || (w0 <= 0 && w1 <= 0 && w2 <= 0)
|
|
44
|
+
inv_area = 1.0 / area
|
|
45
|
+
w0 *= inv_area
|
|
46
|
+
w1 *= inv_area
|
|
47
|
+
w2 *= inv_area
|
|
48
|
+
|
|
49
|
+
depth = w0 * p0.z + w1 * p1.z + w2 * p2.z
|
|
50
|
+
|
|
51
|
+
interpolated = interpolate_attributes(v0, v1, v2, w0, w1, w2)
|
|
52
|
+
|
|
53
|
+
frag_output = fragment_shader.process(interpolated, uniforms)
|
|
54
|
+
color = frag_output[:color]
|
|
55
|
+
|
|
56
|
+
@framebuffer.write_pixel(x, y, color, depth)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def rasterize_line(v0, v1, fragment_shader, uniforms)
|
|
63
|
+
p0 = viewport_transform(v0[:position])
|
|
64
|
+
p1 = viewport_transform(v1[:position])
|
|
65
|
+
|
|
66
|
+
x0 = p0.x.round
|
|
67
|
+
y0 = p0.y.round
|
|
68
|
+
x1 = p1.x.round
|
|
69
|
+
y1 = p1.y.round
|
|
70
|
+
|
|
71
|
+
dx = (x1 - x0).abs
|
|
72
|
+
dy = -(y1 - y0).abs
|
|
73
|
+
sx = x0 < x1 ? 1 : -1
|
|
74
|
+
sy = y0 < y1 ? 1 : -1
|
|
75
|
+
err = dx + dy
|
|
76
|
+
|
|
77
|
+
total_dist = Math.sqrt((x1 - x0)**2 + (y1 - y0)**2)
|
|
78
|
+
|
|
79
|
+
loop do
|
|
80
|
+
current_dist = Math.sqrt((x0 - p0.x.round)**2 + (y0 - p0.y.round)**2)
|
|
81
|
+
t = total_dist > 0 ? current_dist / total_dist : 0
|
|
82
|
+
|
|
83
|
+
depth = p0.z + (p1.z - p0.z) * t
|
|
84
|
+
|
|
85
|
+
interpolated = interpolate_line_attributes(v0, v1, t)
|
|
86
|
+
|
|
87
|
+
frag_output = fragment_shader.process(interpolated, uniforms)
|
|
88
|
+
@framebuffer.write_pixel(x0, y0, frag_output[:color], depth)
|
|
89
|
+
|
|
90
|
+
break if x0 == x1 && y0 == y1
|
|
91
|
+
|
|
92
|
+
e2 = 2 * err
|
|
93
|
+
if e2 >= dy
|
|
94
|
+
err += dy
|
|
95
|
+
x0 += sx
|
|
96
|
+
end
|
|
97
|
+
if e2 <= dx
|
|
98
|
+
err += dx
|
|
99
|
+
y0 += sy
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def rasterize_point(vertex, fragment_shader, uniforms, size: 1)
|
|
105
|
+
p = viewport_transform(vertex[:position])
|
|
106
|
+
x = p.x.round
|
|
107
|
+
y = p.y.round
|
|
108
|
+
depth = p.z
|
|
109
|
+
|
|
110
|
+
half = size / 2
|
|
111
|
+
(-half..half).each do |dy|
|
|
112
|
+
(-half..half).each do |dx|
|
|
113
|
+
frag_output = fragment_shader.process(vertex, uniforms)
|
|
114
|
+
@framebuffer.write_pixel(x + dx, y + dy, frag_output[:color], depth)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def viewport_transform(position)
|
|
122
|
+
ndc = if position.is_a?(Larb::Vec4)
|
|
123
|
+
position.perspective_divide
|
|
124
|
+
else
|
|
125
|
+
position
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
Larb::Vec3.new(
|
|
129
|
+
(ndc.x + 1) * 0.5 * @viewport[:width] + @viewport[:x],
|
|
130
|
+
(1 - ndc.y) * 0.5 * @viewport[:height] + @viewport[:y],
|
|
131
|
+
(ndc.z + 1) * 0.5
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def edge_function(a, b, c)
|
|
136
|
+
(c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def interpolate_attributes(v0, v1, v2, w0, w1, w2)
|
|
140
|
+
result = ShaderIO.new
|
|
141
|
+
|
|
142
|
+
all_keys = (v0.to_h.keys | v1.to_h.keys | v2.to_h.keys) - [:position]
|
|
143
|
+
|
|
144
|
+
all_keys.each do |key|
|
|
145
|
+
a0 = v0[key]
|
|
146
|
+
a1 = v1[key]
|
|
147
|
+
a2 = v2[key]
|
|
148
|
+
next unless a0 && a1 && a2
|
|
149
|
+
|
|
150
|
+
result[key] = interpolate_value(a0, a1, a2, w0, w1, w2)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
result
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def interpolate_value(a, b, c, w0, w1, w2)
|
|
157
|
+
case a
|
|
158
|
+
when Larb::Vec2
|
|
159
|
+
Larb::Vec2.new(
|
|
160
|
+
a.x * w0 + b.x * w1 + c.x * w2,
|
|
161
|
+
a.y * w0 + b.y * w1 + c.y * w2
|
|
162
|
+
)
|
|
163
|
+
when Larb::Vec3
|
|
164
|
+
Larb::Vec3.new(
|
|
165
|
+
a.x * w0 + b.x * w1 + c.x * w2,
|
|
166
|
+
a.y * w0 + b.y * w1 + c.y * w2,
|
|
167
|
+
a.z * w0 + b.z * w1 + c.z * w2
|
|
168
|
+
)
|
|
169
|
+
when Larb::Vec4
|
|
170
|
+
Larb::Vec4.new(
|
|
171
|
+
a.x * w0 + b.x * w1 + c.x * w2,
|
|
172
|
+
a.y * w0 + b.y * w1 + c.y * w2,
|
|
173
|
+
a.z * w0 + b.z * w1 + c.z * w2,
|
|
174
|
+
a.w * w0 + b.w * w1 + c.w * w2
|
|
175
|
+
)
|
|
176
|
+
when Larb::Color
|
|
177
|
+
Larb::Color.new(
|
|
178
|
+
a.r * w0 + b.r * w1 + c.r * w2,
|
|
179
|
+
a.g * w0 + b.g * w1 + c.g * w2,
|
|
180
|
+
a.b * w0 + b.b * w1 + c.b * w2,
|
|
181
|
+
a.a * w0 + b.a * w1 + c.a * w2
|
|
182
|
+
)
|
|
183
|
+
when Numeric
|
|
184
|
+
a * w0 + b * w1 + c * w2
|
|
185
|
+
else
|
|
186
|
+
a
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def interpolate_line_attributes(v0, v1, t)
|
|
191
|
+
result = ShaderIO.new
|
|
192
|
+
all_keys = (v0.to_h.keys | v1.to_h.keys) - [:position]
|
|
193
|
+
|
|
194
|
+
all_keys.each do |key|
|
|
195
|
+
a0 = v0[key]
|
|
196
|
+
a1 = v1[key]
|
|
197
|
+
next unless a0 && a1
|
|
198
|
+
|
|
199
|
+
result[key] = case a0
|
|
200
|
+
when Larb::Vec2, Larb::Vec3, Larb::Vec4, Larb::Color
|
|
201
|
+
a0.lerp(a1, t)
|
|
202
|
+
when Numeric
|
|
203
|
+
a0 + (a1 - a0) * t
|
|
204
|
+
else
|
|
205
|
+
a0
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
result
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module Engine
|
|
5
|
+
class ShaderIO
|
|
6
|
+
def initialize
|
|
7
|
+
@data = {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def method_missing(name, *args)
|
|
11
|
+
if name.to_s.end_with?("=")
|
|
12
|
+
@data[name.to_s.chomp("=").to_sym] = args.first
|
|
13
|
+
else
|
|
14
|
+
@data[name]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def respond_to_missing?(_name, _include_private = false)
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def [](key)
|
|
23
|
+
@data[key]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def []=(key, value)
|
|
27
|
+
@data[key] = value
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_h
|
|
31
|
+
@data.dup
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def keys
|
|
35
|
+
@data.keys
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class Uniforms
|
|
40
|
+
def initialize(data = {})
|
|
41
|
+
@data = data.transform_keys(&:to_sym)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def method_missing(name, *args)
|
|
45
|
+
if name.to_s.end_with?("=")
|
|
46
|
+
@data[name.to_s.chomp("=").to_sym] = args.first
|
|
47
|
+
else
|
|
48
|
+
@data[name]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def respond_to_missing?(_name, _include_private = false)
|
|
53
|
+
true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def [](key)
|
|
57
|
+
@data[key.to_sym]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def []=(key, value)
|
|
61
|
+
@data[key.to_sym] = value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def merge(other)
|
|
65
|
+
Uniforms.new(@data.merge(other.to_h))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_h
|
|
69
|
+
@data.dup
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
module ShaderBuiltins
|
|
74
|
+
def vec2(x, y = nil)
|
|
75
|
+
y ||= x
|
|
76
|
+
Larb::Vec2.new(x, y)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def vec3(x, y = nil, z = nil)
|
|
80
|
+
if y.nil? && z.nil?
|
|
81
|
+
Larb::Vec3.new(x, x, x)
|
|
82
|
+
elsif z.nil? && y.is_a?(Larb::Vec2)
|
|
83
|
+
Larb::Vec3.new(x, y.x, y.y)
|
|
84
|
+
else
|
|
85
|
+
Larb::Vec3.new(x, y, z)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def vec4(x, y = nil, z = nil, w = nil)
|
|
90
|
+
case
|
|
91
|
+
when y.nil? && z.nil? && w.nil?
|
|
92
|
+
Larb::Vec4.new(x, x, x, x)
|
|
93
|
+
when x.is_a?(Larb::Vec3) && !y.nil?
|
|
94
|
+
Larb::Vec4.new(x.x, x.y, x.z, y)
|
|
95
|
+
when x.is_a?(Larb::Vec2) && y.is_a?(Larb::Vec2)
|
|
96
|
+
Larb::Vec4.new(x.x, x.y, y.x, y.y)
|
|
97
|
+
else
|
|
98
|
+
Larb::Vec4.new(x, y, z, w)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def dot(a, b)
|
|
103
|
+
a.dot(b)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def cross(a, b)
|
|
107
|
+
a.cross(b)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def normalize(v)
|
|
111
|
+
v.normalize
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def length(v)
|
|
115
|
+
v.length
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def reflect(v, n)
|
|
119
|
+
v.reflect(n)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def refract(v, n, eta)
|
|
123
|
+
cos_i = -dot(n, v)
|
|
124
|
+
sin_t2 = eta * eta * (1.0 - cos_i * cos_i)
|
|
125
|
+
return vec3(0) if sin_t2 > 1.0
|
|
126
|
+
|
|
127
|
+
cos_t = Math.sqrt(1.0 - sin_t2)
|
|
128
|
+
v * eta + n * (eta * cos_i - cos_t)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def mix(a, b, t)
|
|
132
|
+
case a
|
|
133
|
+
when Numeric then a + (b - a) * t
|
|
134
|
+
when Larb::Vec2, Larb::Vec3, Larb::Vec4 then a.lerp(b, t)
|
|
135
|
+
when Larb::Color then a.lerp(b, t)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
alias lerp mix
|
|
139
|
+
|
|
140
|
+
def clamp(v, min_val, max_val)
|
|
141
|
+
case v
|
|
142
|
+
when Numeric then v.clamp(min_val, max_val)
|
|
143
|
+
when Larb::Vec3
|
|
144
|
+
Larb::Vec3.new(
|
|
145
|
+
v.x.clamp(min_val, max_val),
|
|
146
|
+
v.y.clamp(min_val, max_val),
|
|
147
|
+
v.z.clamp(min_val, max_val)
|
|
148
|
+
)
|
|
149
|
+
when Larb::Color then v.clamp
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def saturate(v)
|
|
154
|
+
clamp(v, 0.0, 1.0)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def smoothstep(edge0, edge1, x)
|
|
158
|
+
t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0)
|
|
159
|
+
t * t * (3.0 - 2.0 * t)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def step(edge, x)
|
|
163
|
+
x < edge ? 0.0 : 1.0
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def fract(x)
|
|
167
|
+
x - x.floor
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def mod(x, y)
|
|
171
|
+
x - y * (x / y).floor
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def abs(x)
|
|
175
|
+
case x
|
|
176
|
+
when Numeric then x.abs
|
|
177
|
+
when Larb::Vec3 then Larb::Vec3.new(x.x.abs, x.y.abs, x.z.abs)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def sign(x)
|
|
182
|
+
x <=> 0
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def floor(x)
|
|
186
|
+
case x
|
|
187
|
+
when Numeric then x.floor
|
|
188
|
+
when Larb::Vec3 then Larb::Vec3.new(x.x.floor, x.y.floor, x.z.floor)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def ceil(x)
|
|
193
|
+
case x
|
|
194
|
+
when Numeric then x.ceil
|
|
195
|
+
when Larb::Vec3 then Larb::Vec3.new(x.x.ceil, x.y.ceil, x.z.ceil)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def pow(x, y)
|
|
200
|
+
case x
|
|
201
|
+
when Numeric then x**y
|
|
202
|
+
when Larb::Vec3 then Larb::Vec3.new(x.x**y, x.y**y, x.z**y)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def sqrt(x)
|
|
207
|
+
case x
|
|
208
|
+
when Numeric then Math.sqrt(x)
|
|
209
|
+
when Larb::Vec3 then Larb::Vec3.new(Math.sqrt(x.x), Math.sqrt(x.y), Math.sqrt(x.z))
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def sin(x)
|
|
214
|
+
Math.sin(x)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def cos(x)
|
|
218
|
+
Math.cos(x)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def tan(x)
|
|
222
|
+
Math.tan(x)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def asin(x)
|
|
226
|
+
Math.asin(x)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def acos(x)
|
|
230
|
+
Math.acos(x)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def atan(y, x = nil)
|
|
234
|
+
x ? Math.atan2(y, x) : Math.atan(y)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def min(*args)
|
|
238
|
+
args.flatten.min
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def max(*args)
|
|
242
|
+
args.flatten.max
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def texture(tex, uv)
|
|
246
|
+
tex.sample(uv.x, uv.y)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def texture_lod(tex, uv, lod)
|
|
250
|
+
tex.sample(uv.x, uv.y, lod: lod)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def rgb(r, g, b)
|
|
254
|
+
Larb::Color.rgb(r, g, b)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def rgba(r, g, b, a)
|
|
258
|
+
Larb::Color.rgba(r, g, b, a)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def color_from_vec3(v)
|
|
262
|
+
Larb::Color.from_vec3(v)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def color_from_vec4(v)
|
|
266
|
+
Larb::Color.from_vec4(v)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
class VertexShader
|
|
271
|
+
include ShaderBuiltins
|
|
272
|
+
|
|
273
|
+
def initialize(&block)
|
|
274
|
+
@process_block = block
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def process(input, uniforms)
|
|
278
|
+
output = ShaderIO.new
|
|
279
|
+
@input = input
|
|
280
|
+
@uniforms = uniforms
|
|
281
|
+
@output = output
|
|
282
|
+
|
|
283
|
+
instance_exec(input, uniforms, output, &@process_block)
|
|
284
|
+
|
|
285
|
+
raise "VertexShader must set output.position" unless output[:position]
|
|
286
|
+
|
|
287
|
+
output
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
attr_reader :input, :uniforms, :output
|
|
291
|
+
|
|
292
|
+
def self.create(&block)
|
|
293
|
+
new(&block)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
class FragmentShader
|
|
298
|
+
include ShaderBuiltins
|
|
299
|
+
|
|
300
|
+
def initialize(&block)
|
|
301
|
+
@process_block = block
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def process(input, uniforms)
|
|
305
|
+
output = ShaderIO.new
|
|
306
|
+
@input = input
|
|
307
|
+
@uniforms = uniforms
|
|
308
|
+
@output = output
|
|
309
|
+
|
|
310
|
+
instance_exec(input, uniforms, output, &@process_block)
|
|
311
|
+
|
|
312
|
+
output[:color] ||= Larb::Color.white
|
|
313
|
+
|
|
314
|
+
output
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
attr_reader :input, :uniforms, :output
|
|
318
|
+
|
|
319
|
+
def self.create(&block)
|
|
320
|
+
new(&block)
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module Engine
|
|
5
|
+
class Texture
|
|
6
|
+
attr_reader :width, :height, :data
|
|
7
|
+
attr_accessor :wrap_s, :wrap_t, :filter_min, :filter_mag
|
|
8
|
+
|
|
9
|
+
WRAP_REPEAT = :repeat
|
|
10
|
+
WRAP_CLAMP = :clamp
|
|
11
|
+
WRAP_MIRROR = :mirror
|
|
12
|
+
|
|
13
|
+
FILTER_NEAREST = :nearest
|
|
14
|
+
FILTER_LINEAR = :linear
|
|
15
|
+
|
|
16
|
+
def initialize(width, height, data = nil)
|
|
17
|
+
@width = width
|
|
18
|
+
@height = height
|
|
19
|
+
@data = data || Array.new(width * height) { Larb::Color.black }
|
|
20
|
+
@wrap_s = WRAP_REPEAT
|
|
21
|
+
@wrap_t = WRAP_REPEAT
|
|
22
|
+
@filter_min = FILTER_LINEAR
|
|
23
|
+
@filter_mag = FILTER_LINEAR
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def sample(u, v, lod: 0)
|
|
27
|
+
u = wrap_coord(u, @wrap_s)
|
|
28
|
+
v = wrap_coord(v, @wrap_t)
|
|
29
|
+
|
|
30
|
+
x = u * (@width - 1)
|
|
31
|
+
y = v * (@height - 1)
|
|
32
|
+
|
|
33
|
+
if @filter_mag == FILTER_NEAREST
|
|
34
|
+
sample_nearest(x, y)
|
|
35
|
+
else
|
|
36
|
+
sample_bilinear(x, y)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get_pixel(x, y)
|
|
41
|
+
x = x.clamp(0, @width - 1).to_i
|
|
42
|
+
y = y.clamp(0, @height - 1).to_i
|
|
43
|
+
@data[y * @width + x]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def set_pixel(x, y, color)
|
|
47
|
+
return if x < 0 || x >= @width || y < 0 || y >= @height
|
|
48
|
+
|
|
49
|
+
@data[y.to_i * @width + x.to_i] = color
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.from_ppm(filename)
|
|
53
|
+
content = File.read(filename, mode: "rb")
|
|
54
|
+
lines = content.lines.reject { |l| l.start_with?("#") }
|
|
55
|
+
|
|
56
|
+
_format = lines.shift.strip
|
|
57
|
+
dimensions = lines.shift.strip.split.map(&:to_i)
|
|
58
|
+
width, height = dimensions
|
|
59
|
+
max_val = lines.shift.strip.to_i
|
|
60
|
+
|
|
61
|
+
data = []
|
|
62
|
+
pixels = lines.join.split.map(&:to_i)
|
|
63
|
+
(pixels.size / 3).times do |i|
|
|
64
|
+
r = pixels[i * 3] / max_val.to_f
|
|
65
|
+
g = pixels[i * 3 + 1] / max_val.to_f
|
|
66
|
+
b = pixels[i * 3 + 2] / max_val.to_f
|
|
67
|
+
data << Larb::Color.rgb(r, g, b)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
new(width, height, data)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.checker(width, height, size, color1 = Larb::Color.white, color2 = Larb::Color.black)
|
|
74
|
+
data = Array.new(width * height)
|
|
75
|
+
height.times do |y|
|
|
76
|
+
width.times do |x|
|
|
77
|
+
checker = ((x / size) + (y / size)) % 2
|
|
78
|
+
data[y * width + x] = checker == 0 ? color1 : color2
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
new(width, height, data)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.solid(width, height, color)
|
|
85
|
+
new(width, height, Array.new(width * height) { color })
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def wrap_coord(coord, mode)
|
|
91
|
+
case mode
|
|
92
|
+
when WRAP_REPEAT
|
|
93
|
+
coord - coord.floor
|
|
94
|
+
when WRAP_CLAMP
|
|
95
|
+
coord.clamp(0.0, 1.0)
|
|
96
|
+
when WRAP_MIRROR
|
|
97
|
+
t = coord - coord.floor
|
|
98
|
+
coord.floor.to_i.even? ? t : 1.0 - t
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def sample_nearest(x, y)
|
|
103
|
+
get_pixel(x.round, y.round)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def sample_bilinear(x, y)
|
|
107
|
+
x0 = x.floor.to_i
|
|
108
|
+
y0 = y.floor.to_i
|
|
109
|
+
x1 = x0 + 1
|
|
110
|
+
y1 = y0 + 1
|
|
111
|
+
fx = x - x0
|
|
112
|
+
fy = y - y0
|
|
113
|
+
|
|
114
|
+
c00 = get_pixel(x0, y0)
|
|
115
|
+
c10 = get_pixel(x1, y0)
|
|
116
|
+
c01 = get_pixel(x0, y1)
|
|
117
|
+
c11 = get_pixel(x1, y1)
|
|
118
|
+
|
|
119
|
+
c0 = c00.lerp(c10, fx)
|
|
120
|
+
c1 = c01.lerp(c11, fx)
|
|
121
|
+
c0.lerp(c1, fy)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
data/lib/rbgl/engine.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "engine/framebuffer"
|
|
4
|
+
require_relative "engine/buffer"
|
|
5
|
+
require_relative "engine/shader"
|
|
6
|
+
require_relative "engine/texture"
|
|
7
|
+
require_relative "engine/rasterizer"
|
|
8
|
+
require_relative "engine/pipeline"
|
|
9
|
+
require_relative "engine/context"
|
|
10
|
+
|
|
11
|
+
module RBGL
|
|
12
|
+
module Engine
|
|
13
|
+
VERSION = "0.1.0"
|
|
14
|
+
end
|
|
15
|
+
end
|