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,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
4
|
+
|
|
5
|
+
require "rbgl"
|
|
6
|
+
|
|
7
|
+
include Larb
|
|
8
|
+
include RBGL::Engine
|
|
9
|
+
|
|
10
|
+
ctx = Context.new(width: 320, height: 240)
|
|
11
|
+
|
|
12
|
+
pipeline = Pipeline.create do
|
|
13
|
+
vertex do |input, _uniforms, output|
|
|
14
|
+
output.position = input.position.to_vec4
|
|
15
|
+
output.color = input.color
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
fragment do |input, _uniforms, output|
|
|
19
|
+
output.color = input.color
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
layout = VertexLayout.position_color
|
|
24
|
+
vertices = VertexBuffer.from_array(layout, [
|
|
25
|
+
{ position: Vec3[0.0, 0.5, 0.0], color: Color.red },
|
|
26
|
+
{ position: Vec3[-0.5, -0.5, 0.0], color: Color.green },
|
|
27
|
+
{ position: Vec3[0.5, -0.5, 0.0], color: Color.blue }
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
ctx.clear(color: Color.from_hex("#1a1a2e"))
|
|
31
|
+
ctx.bind_pipeline(pipeline)
|
|
32
|
+
ctx.bind_vertex_buffer(vertices)
|
|
33
|
+
ctx.draw_arrays(:triangles, 0, 3)
|
|
34
|
+
|
|
35
|
+
File.write("hello_triangle.ppm", ctx.framebuffer.to_ppm)
|
|
36
|
+
puts "Rendered to hello_triangle.ppm"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
4
|
+
|
|
5
|
+
require "rbgl"
|
|
6
|
+
|
|
7
|
+
include Larb
|
|
8
|
+
include RBGL::Engine
|
|
9
|
+
|
|
10
|
+
window = RBGL::GUI::Window.new(
|
|
11
|
+
width: 320,
|
|
12
|
+
height: 240,
|
|
13
|
+
title: "Hello Triangle - Window",
|
|
14
|
+
backend: :auto
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
pipeline = Pipeline.create do
|
|
18
|
+
vertex do |input, uniforms, output|
|
|
19
|
+
angle = uniforms.time || 0
|
|
20
|
+
pos = input.position
|
|
21
|
+
|
|
22
|
+
rotated_x = pos.x * Math.cos(angle) - pos.y * Math.sin(angle)
|
|
23
|
+
rotated_y = pos.x * Math.sin(angle) + pos.y * Math.cos(angle)
|
|
24
|
+
|
|
25
|
+
output.position = Vec4[rotated_x, rotated_y, pos.z, 1.0]
|
|
26
|
+
output.color = input.color
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
fragment do |input, _uniforms, output|
|
|
30
|
+
output.color = input.color
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
layout = VertexLayout.position_color
|
|
35
|
+
vertices = VertexBuffer.from_array(layout, [
|
|
36
|
+
{ position: Vec3[0.0, 0.5, 0.0], color: Color.red },
|
|
37
|
+
{ position: Vec3[-0.5, -0.5, 0.0], color: Color.green },
|
|
38
|
+
{ position: Vec3[0.5, -0.5, 0.0], color: Color.blue }
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
time = 0.0
|
|
42
|
+
|
|
43
|
+
window.on(:key_press) do |event|
|
|
44
|
+
puts "Key: #{event.key}"
|
|
45
|
+
window.stop if event.key == 12 || event.key == "q" || event.key == "Escape"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
window.on(:mouse_move) do |event|
|
|
49
|
+
puts "Mouse: #{event.x}, #{event.y}" if event.x && event.y
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
puts "Spinning triangle - Press 'q' or Escape to quit"
|
|
53
|
+
|
|
54
|
+
window.run do |ctx, dt|
|
|
55
|
+
time += dt
|
|
56
|
+
|
|
57
|
+
ctx.clear(color: Color.from_hex("#1a1a2e"))
|
|
58
|
+
ctx.bind_pipeline(pipeline)
|
|
59
|
+
ctx.bind_vertex_buffer(vertices)
|
|
60
|
+
ctx.set_uniform(:time, time)
|
|
61
|
+
ctx.draw_arrays(:triangles, 0, 3)
|
|
62
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
4
|
+
|
|
5
|
+
require "rbgl"
|
|
6
|
+
|
|
7
|
+
puts "Creating window..."
|
|
8
|
+
|
|
9
|
+
window = RBGL::GUI::Window.new(
|
|
10
|
+
width: 320,
|
|
11
|
+
height: 240,
|
|
12
|
+
title: "Simple Test",
|
|
13
|
+
backend: :cocoa
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
puts "Window created, starting render loop..."
|
|
17
|
+
|
|
18
|
+
frame = 0
|
|
19
|
+
window.run do |ctx, dt|
|
|
20
|
+
frame += 1
|
|
21
|
+
|
|
22
|
+
# Simple red background
|
|
23
|
+
ctx.clear(color: Larb::Color.red)
|
|
24
|
+
|
|
25
|
+
if frame % 60 == 0
|
|
26
|
+
puts "Frame: #{frame}, FPS: #{window.fps.round(1)}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Exit after 5 seconds
|
|
30
|
+
if frame > 300
|
|
31
|
+
puts "Stopping..."
|
|
32
|
+
window.stop
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
puts "Done"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module Engine
|
|
5
|
+
class VertexAttribute
|
|
6
|
+
attr_reader :name, :size, :offset
|
|
7
|
+
|
|
8
|
+
def initialize(name, size, offset = 0)
|
|
9
|
+
@name = name.to_sym
|
|
10
|
+
@size = size
|
|
11
|
+
@offset = offset
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class VertexLayout
|
|
16
|
+
attr_reader :attributes, :stride
|
|
17
|
+
|
|
18
|
+
def initialize(&block)
|
|
19
|
+
@attributes = {}
|
|
20
|
+
@offset = 0
|
|
21
|
+
instance_eval(&block) if block_given?
|
|
22
|
+
@stride = @offset
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def attribute(name, size)
|
|
26
|
+
@attributes[name.to_sym] = VertexAttribute.new(name, size, @offset)
|
|
27
|
+
@offset += size
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.position_only
|
|
31
|
+
new { attribute :position, 3 }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.position_color
|
|
35
|
+
new do
|
|
36
|
+
attribute :position, 3
|
|
37
|
+
attribute :color, 4
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.position_normal_uv
|
|
42
|
+
new do
|
|
43
|
+
attribute :position, 3
|
|
44
|
+
attribute :normal, 3
|
|
45
|
+
attribute :uv, 2
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.position_normal_uv_color
|
|
50
|
+
new do
|
|
51
|
+
attribute :position, 3
|
|
52
|
+
attribute :normal, 3
|
|
53
|
+
attribute :uv, 2
|
|
54
|
+
attribute :color, 4
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class VertexBuffer
|
|
60
|
+
attr_reader :data, :layout, :vertex_count
|
|
61
|
+
|
|
62
|
+
def initialize(layout)
|
|
63
|
+
@layout = layout
|
|
64
|
+
@data = []
|
|
65
|
+
@vertex_count = 0
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def add_vertex(**attributes)
|
|
69
|
+
vertex_data = []
|
|
70
|
+
@layout.attributes.each do |name, attr|
|
|
71
|
+
value = attributes[name]
|
|
72
|
+
raise "Missing attribute: #{name}" unless value
|
|
73
|
+
|
|
74
|
+
case value
|
|
75
|
+
when Larb::Vec2
|
|
76
|
+
vertex_data.concat([value.x, value.y])
|
|
77
|
+
when Larb::Vec3
|
|
78
|
+
vertex_data.concat([value.x, value.y, value.z])
|
|
79
|
+
when Larb::Vec4
|
|
80
|
+
vertex_data.concat([value.x, value.y, value.z, value.w])
|
|
81
|
+
when Larb::Color
|
|
82
|
+
vertex_data.concat(value.to_a)
|
|
83
|
+
when Array
|
|
84
|
+
vertex_data.concat(value)
|
|
85
|
+
when Numeric
|
|
86
|
+
vertex_data << value.to_f
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
@data.concat(vertex_data)
|
|
90
|
+
@vertex_count += 1
|
|
91
|
+
self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def add_vertices(*vertices)
|
|
95
|
+
vertices.each { |v| add_vertex(**v) }
|
|
96
|
+
self
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def get_vertex(index)
|
|
100
|
+
return nil if index < 0 || index >= @vertex_count
|
|
101
|
+
|
|
102
|
+
start = index * @layout.stride
|
|
103
|
+
vertex = {}
|
|
104
|
+
|
|
105
|
+
@layout.attributes.each do |name, attr|
|
|
106
|
+
offset = start + attr.offset
|
|
107
|
+
values = @data[offset, attr.size]
|
|
108
|
+
|
|
109
|
+
vertex[name] = case attr.size
|
|
110
|
+
when 1 then values[0]
|
|
111
|
+
when 2 then Larb::Vec2.new(*values)
|
|
112
|
+
when 3 then Larb::Vec3.new(*values)
|
|
113
|
+
when 4
|
|
114
|
+
if name == :color
|
|
115
|
+
Larb::Color.new(*values)
|
|
116
|
+
else
|
|
117
|
+
Larb::Vec4.new(*values)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
vertex
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def self.from_array(layout, data)
|
|
126
|
+
buffer = new(layout)
|
|
127
|
+
data.each { |vertex| buffer.add_vertex(**vertex) }
|
|
128
|
+
buffer
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class IndexBuffer
|
|
133
|
+
attr_reader :indices
|
|
134
|
+
|
|
135
|
+
def initialize(indices = [])
|
|
136
|
+
@indices = indices.map(&:to_i)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def add(*idx)
|
|
140
|
+
@indices.concat(idx.flatten.map(&:to_i))
|
|
141
|
+
self
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def triangle_count
|
|
145
|
+
@indices.size / 3
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def get_triangle(index)
|
|
149
|
+
start = index * 3
|
|
150
|
+
@indices[start, 3]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def each_triangle
|
|
154
|
+
return enum_for(:each_triangle) unless block_given?
|
|
155
|
+
|
|
156
|
+
(0...triangle_count).each { |i| yield get_triangle(i) }
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module Engine
|
|
5
|
+
class Context
|
|
6
|
+
attr_reader :framebuffer, :rasterizer
|
|
7
|
+
|
|
8
|
+
def initialize(width:, height:)
|
|
9
|
+
@framebuffer = Framebuffer.new(width, height)
|
|
10
|
+
@rasterizer = Rasterizer.new(@framebuffer)
|
|
11
|
+
@pipeline = nil
|
|
12
|
+
@uniforms = Uniforms.new
|
|
13
|
+
@vertex_buffer = nil
|
|
14
|
+
@index_buffer = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def bind_pipeline(pipeline)
|
|
18
|
+
@pipeline = pipeline
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def bind_vertex_buffer(buffer)
|
|
22
|
+
@vertex_buffer = buffer
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def bind_index_buffer(buffer)
|
|
26
|
+
@index_buffer = buffer
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def set_uniform(name, value)
|
|
30
|
+
@uniforms[name] = value
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def set_uniforms(hash)
|
|
34
|
+
hash.each { |k, v| @uniforms[k] = v }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def clear(color: Larb::Color.black, depth: Float::INFINITY)
|
|
38
|
+
@framebuffer.clear(color: color, depth: depth)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def draw_arrays(mode, first, count)
|
|
42
|
+
raise "No pipeline bound" unless @pipeline
|
|
43
|
+
raise "No vertex buffer bound" unless @vertex_buffer
|
|
44
|
+
|
|
45
|
+
vertices = (first...first + count).map { |i| process_vertex(i) }
|
|
46
|
+
|
|
47
|
+
case mode
|
|
48
|
+
when :triangles
|
|
49
|
+
(0...vertices.size).step(3) do |i|
|
|
50
|
+
draw_triangle(vertices[i], vertices[i + 1], vertices[i + 2])
|
|
51
|
+
end
|
|
52
|
+
when :lines
|
|
53
|
+
(0...vertices.size).step(2) do |i|
|
|
54
|
+
draw_line(vertices[i], vertices[i + 1])
|
|
55
|
+
end
|
|
56
|
+
when :points
|
|
57
|
+
vertices.each { |v| draw_point(v) }
|
|
58
|
+
when :triangle_strip
|
|
59
|
+
(0...vertices.size - 2).each do |i|
|
|
60
|
+
if i.even?
|
|
61
|
+
draw_triangle(vertices[i], vertices[i + 1], vertices[i + 2])
|
|
62
|
+
else
|
|
63
|
+
draw_triangle(vertices[i], vertices[i + 2], vertices[i + 1])
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
when :triangle_fan
|
|
67
|
+
(1...vertices.size - 1).each do |i|
|
|
68
|
+
draw_triangle(vertices[0], vertices[i], vertices[i + 1])
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def draw_elements(mode, count, offset = 0)
|
|
74
|
+
raise "No pipeline bound" unless @pipeline
|
|
75
|
+
raise "No vertex buffer bound" unless @vertex_buffer
|
|
76
|
+
raise "No index buffer bound" unless @index_buffer
|
|
77
|
+
|
|
78
|
+
indices = @index_buffer.indices[offset, count]
|
|
79
|
+
vertices = indices.map { |i| process_vertex(i) }
|
|
80
|
+
|
|
81
|
+
case mode
|
|
82
|
+
when :triangles
|
|
83
|
+
(0...vertices.size).step(3) do |i|
|
|
84
|
+
draw_triangle(vertices[i], vertices[i + 1], vertices[i + 2])
|
|
85
|
+
end
|
|
86
|
+
when :lines
|
|
87
|
+
(0...vertices.size).step(2) do |i|
|
|
88
|
+
draw_line(vertices[i], vertices[i + 1])
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def width
|
|
94
|
+
@framebuffer.width
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def height
|
|
98
|
+
@framebuffer.height
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def aspect_ratio
|
|
102
|
+
width.to_f / height
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def process_vertex(index)
|
|
108
|
+
input = @vertex_buffer.get_vertex(index)
|
|
109
|
+
input_io = ShaderIO.new
|
|
110
|
+
input.each { |k, v| input_io[k] = v }
|
|
111
|
+
|
|
112
|
+
@pipeline.vertex_shader.process(input_io, @uniforms)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def draw_triangle(v0, v1, v2)
|
|
116
|
+
return unless v0 && v1 && v2
|
|
117
|
+
return if clip_triangle?(v0, v1, v2)
|
|
118
|
+
|
|
119
|
+
@rasterizer.rasterize_triangle(
|
|
120
|
+
v0, v1, v2,
|
|
121
|
+
@pipeline.fragment_shader,
|
|
122
|
+
@uniforms,
|
|
123
|
+
cull_mode: @pipeline.cull_mode
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def draw_line(v0, v1)
|
|
128
|
+
return unless v0 && v1
|
|
129
|
+
|
|
130
|
+
@rasterizer.rasterize_line(v0, v1, @pipeline.fragment_shader, @uniforms)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def draw_point(v)
|
|
134
|
+
return unless v
|
|
135
|
+
|
|
136
|
+
@rasterizer.rasterize_point(v, @pipeline.fragment_shader, @uniforms)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def clip_triangle?(v0, v1, v2)
|
|
140
|
+
positions = [v0[:position], v1[:position], v2[:position]].map do |p|
|
|
141
|
+
if p.is_a?(Larb::Vec4)
|
|
142
|
+
p.perspective_divide
|
|
143
|
+
else
|
|
144
|
+
p
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
positions.all? { |p| p.x < -1 } ||
|
|
149
|
+
positions.all? { |p| p.x > 1 } ||
|
|
150
|
+
positions.all? { |p| p.y < -1 } ||
|
|
151
|
+
positions.all? { |p| p.y > 1 } ||
|
|
152
|
+
positions.all? { |p| p.z < -1 } ||
|
|
153
|
+
positions.all? { |p| p.z > 1 }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module Engine
|
|
5
|
+
class Framebuffer
|
|
6
|
+
attr_reader :width, :height, :color_buffer, :depth_buffer
|
|
7
|
+
|
|
8
|
+
def initialize(width, height)
|
|
9
|
+
@width = width
|
|
10
|
+
@height = height
|
|
11
|
+
@color_buffer = Array.new(width * height) { Larb::Color.black }
|
|
12
|
+
@depth_buffer = Array.new(width * height) { Float::INFINITY }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get_pixel(x, y)
|
|
16
|
+
return nil if x < 0 || x >= @width || y < 0 || y >= @height
|
|
17
|
+
|
|
18
|
+
@color_buffer[y * @width + x]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def set_pixel(x, y, color)
|
|
22
|
+
return if x < 0 || x >= @width || y < 0 || y >= @height
|
|
23
|
+
|
|
24
|
+
@color_buffer[y * @width + x] = color
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def get_depth(x, y)
|
|
28
|
+
return Float::INFINITY if x < 0 || x >= @width || y < 0 || y >= @height
|
|
29
|
+
|
|
30
|
+
@depth_buffer[y * @width + x]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def set_depth(x, y, depth)
|
|
34
|
+
return if x < 0 || x >= @width || y < 0 || y >= @height
|
|
35
|
+
|
|
36
|
+
@depth_buffer[y * @width + x] = depth
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def write_pixel(x, y, color, depth, depth_test: true)
|
|
40
|
+
return false if x < 0 || x >= @width || y < 0 || y >= @height
|
|
41
|
+
|
|
42
|
+
idx = y * @width + x
|
|
43
|
+
if !depth_test || depth < @depth_buffer[idx]
|
|
44
|
+
@color_buffer[idx] = color
|
|
45
|
+
@depth_buffer[idx] = depth
|
|
46
|
+
true
|
|
47
|
+
else
|
|
48
|
+
false
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def clear(color: Larb::Color.black, depth: Float::INFINITY)
|
|
53
|
+
@color_buffer.fill(color)
|
|
54
|
+
@depth_buffer.fill(depth)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def clear_color(color)
|
|
58
|
+
@color_buffer.fill(color)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def clear_depth(depth = Float::INFINITY)
|
|
62
|
+
@depth_buffer.fill(depth)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_ppm
|
|
66
|
+
ppm = "P3\n#{@width} #{@height}\n255\n"
|
|
67
|
+
@height.times do |y|
|
|
68
|
+
row = @width.times.map do |x|
|
|
69
|
+
c = @color_buffer[y * @width + x]
|
|
70
|
+
bytes = c.to_bytes
|
|
71
|
+
"#{bytes[0]} #{bytes[1]} #{bytes[2]}"
|
|
72
|
+
end
|
|
73
|
+
ppm += row.join(" ") + "\n"
|
|
74
|
+
end
|
|
75
|
+
ppm
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def to_ppm_binary
|
|
79
|
+
header = "P6\n#{@width} #{@height}\n255\n"
|
|
80
|
+
pixels = @color_buffer.flat_map { |c| c.to_bytes[0..2] }.pack("C*")
|
|
81
|
+
header + pixels
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def to_rgba_bytes
|
|
85
|
+
size = @color_buffer.size
|
|
86
|
+
bytes = Array.new(size * 4)
|
|
87
|
+
i = 0
|
|
88
|
+
size.times do |idx|
|
|
89
|
+
c = @color_buffer[idx]
|
|
90
|
+
bytes[i] = (c.r * 255).round.clamp(0, 255)
|
|
91
|
+
bytes[i + 1] = (c.g * 255).round.clamp(0, 255)
|
|
92
|
+
bytes[i + 2] = (c.b * 255).round.clamp(0, 255)
|
|
93
|
+
bytes[i + 3] = (c.a * 255).round.clamp(0, 255)
|
|
94
|
+
i += 4
|
|
95
|
+
end
|
|
96
|
+
bytes.pack("C*")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def to_bgra_bytes
|
|
100
|
+
size = @color_buffer.size
|
|
101
|
+
bytes = Array.new(size * 4)
|
|
102
|
+
i = 0
|
|
103
|
+
size.times do |idx|
|
|
104
|
+
c = @color_buffer[idx]
|
|
105
|
+
bytes[i] = (c.b * 255).round.clamp(0, 255)
|
|
106
|
+
bytes[i + 1] = (c.g * 255).round.clamp(0, 255)
|
|
107
|
+
bytes[i + 2] = (c.r * 255).round.clamp(0, 255)
|
|
108
|
+
bytes[i + 3] = (c.a * 255).round.clamp(0, 255)
|
|
109
|
+
i += 4
|
|
110
|
+
end
|
|
111
|
+
bytes.pack("C*")
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RBGL
|
|
4
|
+
module Engine
|
|
5
|
+
class Pipeline
|
|
6
|
+
attr_accessor :vertex_shader, :fragment_shader
|
|
7
|
+
attr_accessor :depth_test, :depth_write
|
|
8
|
+
attr_accessor :cull_mode
|
|
9
|
+
attr_accessor :blend_mode
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@vertex_shader = nil
|
|
13
|
+
@fragment_shader = nil
|
|
14
|
+
@depth_test = true
|
|
15
|
+
@depth_write = true
|
|
16
|
+
@cull_mode = :back
|
|
17
|
+
@blend_mode = :none
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.create(&block)
|
|
21
|
+
pipeline = new
|
|
22
|
+
pipeline.instance_eval(&block) if block_given?
|
|
23
|
+
pipeline
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def vertex(&block)
|
|
27
|
+
@vertex_shader = VertexShader.new(&block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def fragment(&block)
|
|
31
|
+
@fragment_shader = FragmentShader.new(&block)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|