3rb 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 (100) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/3rb.gemspec +29 -0
  4. data/CHANGELOG.md +12 -0
  5. data/LICENSE +21 -0
  6. data/README.md +321 -0
  7. data/Rakefile +13 -0
  8. data/examples/01_hello_cube.rb +29 -0
  9. data/examples/02_basic_geometries.rb +56 -0
  10. data/examples/03_materials.rb +61 -0
  11. data/examples/04_lighting.rb +63 -0
  12. data/examples/05_animation.rb +79 -0
  13. data/examples/06_custom_shader.rb +92 -0
  14. data/examples/07_scene_graph.rb +74 -0
  15. data/examples/08_orbit_controls.rb +50 -0
  16. data/examples/09_3d_chart.rb +71 -0
  17. data/examples/10_procedural_terrain.rb +140 -0
  18. data/examples/11_particle_system.rb +68 -0
  19. data/examples/12_model_loader.rb +73 -0
  20. data/examples/13_game_prototype.rb +145 -0
  21. data/examples/14_utah_teapot.rb +291 -0
  22. data/examples/15_stanford_bunny.rb +200 -0
  23. data/examples/16_cornell_box.rb +373 -0
  24. data/examples/17_weird_fractal4.rb +130 -0
  25. data/examples/18_platonic_solids.rb +268 -0
  26. data/lib/3rb/animation/animation_clip.rb +287 -0
  27. data/lib/3rb/animation/animation_mixer.rb +366 -0
  28. data/lib/3rb/cameras/camera.rb +50 -0
  29. data/lib/3rb/cameras/orthographic_camera.rb +92 -0
  30. data/lib/3rb/cameras/perspective_camera.rb +103 -0
  31. data/lib/3rb/controls/orbit_controls.rb +341 -0
  32. data/lib/3rb/core/buffer_attribute.rb +172 -0
  33. data/lib/3rb/core/group.rb +9 -0
  34. data/lib/3rb/core/object3d.rb +298 -0
  35. data/lib/3rb/core/scene.rb +78 -0
  36. data/lib/3rb/dsl/helpers.rb +57 -0
  37. data/lib/3rb/dsl/scene_builder.rb +288 -0
  38. data/lib/3rb/ffi/glfw.rb +61 -0
  39. data/lib/3rb/ffi/opengl.rb +137 -0
  40. data/lib/3rb/ffi/platform.rb +65 -0
  41. data/lib/3rb/geometries/box_geometry.rb +101 -0
  42. data/lib/3rb/geometries/buffer_geometry.rb +345 -0
  43. data/lib/3rb/geometries/cone_geometry.rb +29 -0
  44. data/lib/3rb/geometries/cylinder_geometry.rb +149 -0
  45. data/lib/3rb/geometries/plane_geometry.rb +75 -0
  46. data/lib/3rb/geometries/sphere_geometry.rb +93 -0
  47. data/lib/3rb/geometries/torus_geometry.rb +77 -0
  48. data/lib/3rb/lights/ambient_light.rb +9 -0
  49. data/lib/3rb/lights/directional_light.rb +57 -0
  50. data/lib/3rb/lights/hemisphere_light.rb +26 -0
  51. data/lib/3rb/lights/light.rb +27 -0
  52. data/lib/3rb/lights/point_light.rb +68 -0
  53. data/lib/3rb/lights/rect_area_light.rb +35 -0
  54. data/lib/3rb/lights/spot_light.rb +88 -0
  55. data/lib/3rb/loaders/gltf_loader.rb +304 -0
  56. data/lib/3rb/loaders/loader.rb +94 -0
  57. data/lib/3rb/loaders/obj_loader.rb +186 -0
  58. data/lib/3rb/loaders/texture_loader.rb +55 -0
  59. data/lib/3rb/materials/basic_material.rb +70 -0
  60. data/lib/3rb/materials/lambert_material.rb +102 -0
  61. data/lib/3rb/materials/material.rb +114 -0
  62. data/lib/3rb/materials/phong_material.rb +106 -0
  63. data/lib/3rb/materials/shader_material.rb +104 -0
  64. data/lib/3rb/materials/standard_material.rb +106 -0
  65. data/lib/3rb/math/color.rb +246 -0
  66. data/lib/3rb/math/euler.rb +156 -0
  67. data/lib/3rb/math/math_utils.rb +132 -0
  68. data/lib/3rb/math/matrix3.rb +269 -0
  69. data/lib/3rb/math/matrix4.rb +501 -0
  70. data/lib/3rb/math/quaternion.rb +337 -0
  71. data/lib/3rb/math/vector2.rb +216 -0
  72. data/lib/3rb/math/vector3.rb +366 -0
  73. data/lib/3rb/math/vector4.rb +233 -0
  74. data/lib/3rb/native/gl.rb +382 -0
  75. data/lib/3rb/native/native.rb +55 -0
  76. data/lib/3rb/native/window.rb +111 -0
  77. data/lib/3rb/native.rb +9 -0
  78. data/lib/3rb/objects/line.rb +116 -0
  79. data/lib/3rb/objects/mesh.rb +40 -0
  80. data/lib/3rb/objects/points.rb +71 -0
  81. data/lib/3rb/renderers/opengl_renderer.rb +567 -0
  82. data/lib/3rb/renderers/renderer.rb +60 -0
  83. data/lib/3rb/renderers/shader_lib.rb +100 -0
  84. data/lib/3rb/textures/cube_texture.rb +26 -0
  85. data/lib/3rb/textures/data_texture.rb +35 -0
  86. data/lib/3rb/textures/render_target.rb +125 -0
  87. data/lib/3rb/textures/texture.rb +190 -0
  88. data/lib/3rb/version.rb +5 -0
  89. data/lib/3rb.rb +86 -0
  90. data/shaders/basic.frag +19 -0
  91. data/shaders/basic.vert +15 -0
  92. data/shaders/common/lights.glsl +53 -0
  93. data/shaders/common/uniforms.glsl +9 -0
  94. data/shaders/lambert.frag +37 -0
  95. data/shaders/lambert.vert +22 -0
  96. data/shaders/phong.frag +51 -0
  97. data/shaders/phong.vert +28 -0
  98. data/shaders/standard.frag +92 -0
  99. data/shaders/standard.vert +28 -0
  100. metadata +155 -0
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sunrb
4
+ class PointsMaterial < Material
5
+ attr_accessor :color, :map
6
+ attr_accessor :size, :size_attenuation
7
+ attr_accessor :alpha_map
8
+
9
+ def initialize(color: 0xffffff, size: 1, size_attenuation: true)
10
+ super()
11
+ @color = color.is_a?(Color) ? color : Color.new(color)
12
+ @map = nil
13
+ @size = size.to_f
14
+ @size_attenuation = size_attenuation
15
+ @alpha_map = nil
16
+ @lights = false
17
+ end
18
+
19
+ def copy(source)
20
+ super
21
+ @color = source.color.clone
22
+ @map = source.map
23
+ @size = source.size
24
+ @size_attenuation = source.size_attenuation
25
+ @alpha_map = source.alpha_map
26
+ self
27
+ end
28
+
29
+ def to_h
30
+ super.merge(
31
+ color: @color.get_hex,
32
+ size: @size,
33
+ size_attenuation: @size_attenuation
34
+ )
35
+ end
36
+ end
37
+
38
+ class Points < Object3D
39
+ attr_accessor :geometry, :material
40
+
41
+ def initialize(geometry = nil, material = nil)
42
+ super()
43
+ @geometry = geometry || BufferGeometry.new
44
+ @material = material || PointsMaterial.new
45
+ end
46
+
47
+ def copy(source, recursive = true)
48
+ super
49
+ @material = source.material.clone
50
+ @geometry = source.geometry.clone
51
+ self
52
+ end
53
+
54
+ def clone(recursive = true)
55
+ new_points = super
56
+ new_points.geometry = @geometry.clone
57
+ new_points.material = @material.clone
58
+ new_points
59
+ end
60
+
61
+ def raycast(_raycaster, _intersects)
62
+ end
63
+
64
+ def to_h
65
+ super.merge(
66
+ geometry: @geometry.uuid,
67
+ material: @material.uuid
68
+ )
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,567 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sunrb
4
+ class OpenGLRenderer < Renderer
5
+ attr_reader :window, :info
6
+
7
+ def initialize(width: 800, height: 600, title: "Sunrb", antialias: false)
8
+ super(width: width, height: height)
9
+
10
+ @title = title
11
+ @antialias = antialias
12
+ @window = nil
13
+ @running = false
14
+ @last_time = 0.0
15
+
16
+ @info = {
17
+ memory: { geometries: 0, textures: 0 },
18
+ render: { calls: 0, triangles: 0, points: 0, lines: 0 }
19
+ }
20
+
21
+ @frame_callback = nil
22
+ @key_callback = nil
23
+ @mouse_callback = nil
24
+ @mouse_move_callback = nil
25
+ @mouse_button_callback = nil
26
+ @resize_callback = nil
27
+
28
+ @last_mouse_pos = [0.0, 0.0]
29
+ @last_mouse_buttons = [false, false, false]
30
+
31
+ @programs = {}
32
+ @shader_cache = {}
33
+ @default_texture = nil
34
+
35
+ init_window if native_available?
36
+ end
37
+
38
+ def native_available?
39
+ defined?(Sunrb::Native) ? true : false
40
+ end
41
+
42
+ def run(&block)
43
+ raise GLError, "Native extension not available" unless native_available?
44
+
45
+ @running = true
46
+ @last_time = Native.get_time
47
+
48
+ while @running && !@window.should_close?
49
+ current_time = Native.get_time
50
+ delta = current_time - @last_time
51
+ @last_time = current_time
52
+
53
+ Native.poll_events
54
+
55
+ handle_input
56
+
57
+ block&.call(delta)
58
+
59
+ @frame_callback&.call(delta)
60
+
61
+ @window.swap_buffers
62
+ end
63
+
64
+ dispose
65
+ end
66
+
67
+ def on_frame(&block)
68
+ @frame_callback = block
69
+ end
70
+
71
+ def on_key(&block)
72
+ @key_callback = block
73
+ end
74
+
75
+ def on_mouse(&block)
76
+ @mouse_callback = block
77
+ end
78
+
79
+ def on_mouse_move(&block)
80
+ @mouse_move_callback = block
81
+ end
82
+
83
+ def on_mouse_button(&block)
84
+ @mouse_button_callback = block
85
+ end
86
+
87
+ def on_resize(&block)
88
+ @resize_callback = block
89
+ end
90
+
91
+ def stop
92
+ @running = false
93
+ end
94
+
95
+ def render(scene, camera)
96
+ return unless native_available?
97
+
98
+ reset_info
99
+
100
+ @window.make_current
101
+
102
+ if @auto_clear
103
+ clear(
104
+ color: @auto_clear_color,
105
+ depth: @auto_clear_depth,
106
+ stencil: @auto_clear_stencil
107
+ )
108
+ end
109
+
110
+ scene.update_matrix_world
111
+ camera.update_matrix_world
112
+
113
+ render_scene(scene, camera)
114
+ end
115
+
116
+ def clear(color: true, depth: true, stencil: true)
117
+ return unless native_available?
118
+
119
+ gl = Native::GL
120
+
121
+ if color
122
+ gl.clear_color(@clear_color.r, @clear_color.g, @clear_color.b, @clear_alpha)
123
+ end
124
+
125
+ bits = 0
126
+ bits |= gl::COLOR_BUFFER_BIT if color
127
+ bits |= gl::DEPTH_BUFFER_BIT if depth
128
+ bits |= gl::STENCIL_BUFFER_BIT if stencil
129
+
130
+ gl.clear(bits)
131
+ end
132
+
133
+ def set_size(width, height)
134
+ super
135
+ return unless native_available? && @window
136
+
137
+ fb_width, fb_height = @window.size
138
+ Native::GL.viewport(0, 0, fb_width, fb_height)
139
+ end
140
+
141
+ def dispose
142
+ return unless native_available?
143
+
144
+ @programs.each_value { |p| Native::GL.delete_program(p) }
145
+ @programs.clear
146
+
147
+ Native::GL.delete_textures([@default_texture]) if @default_texture
148
+ @default_texture = nil
149
+
150
+ @window&.destroy
151
+ @window = nil
152
+
153
+ Native.terminate_glfw
154
+ end
155
+
156
+ def get_context
157
+ return nil unless native_available?
158
+
159
+ {
160
+ vendor: Native::GL.get_string(Native::GL::VENDOR),
161
+ renderer: Native::GL.get_string(Native::GL::RENDERER),
162
+ version: Native::GL.get_string(Native::GL::VERSION)
163
+ }
164
+ end
165
+
166
+ private
167
+
168
+ def init_window
169
+ Native.init_glfw
170
+ @window = Native::Window.new(@width, @height, @title)
171
+ setup_gl_state
172
+ end
173
+
174
+ def setup_gl_state
175
+ gl = Native::GL
176
+
177
+ gl.enable(gl::DEPTH_TEST)
178
+ gl.depth_func(gl::LEQUAL)
179
+
180
+ gl.enable(gl::CULL_FACE)
181
+ gl.cull_face(gl::BACK)
182
+ gl.front_face(gl::CCW)
183
+
184
+ gl.enable(gl::BLEND)
185
+ gl.blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA)
186
+
187
+ fb_width, fb_height = @window.size
188
+ gl.viewport(0, 0, fb_width, fb_height)
189
+
190
+ create_default_texture
191
+ end
192
+
193
+ def create_default_texture
194
+ gl = Native::GL
195
+
196
+ @default_texture = gl.gen_textures(1).first
197
+ gl.bind_texture(gl::TEXTURE_2D, @default_texture)
198
+ gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST)
199
+ gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST)
200
+ gl.tex_image_2d(gl::TEXTURE_2D, 0, gl::RGBA, 1, 1, 0, gl::RGBA, gl::UNSIGNED_BYTE, [255, 255, 255, 255])
201
+ gl.bind_texture(gl::TEXTURE_2D, 0)
202
+ end
203
+
204
+ def handle_input
205
+ if @window.get_key(Native::KEY_ESCAPE) == Native::PRESS
206
+ @key_callback&.call(:escape, :press)
207
+ stop
208
+ end
209
+
210
+ if @mouse_move_callback
211
+ current_pos = @window.cursor_pos
212
+ if current_pos != @last_mouse_pos
213
+ @mouse_move_callback.call(current_pos[0], current_pos[1])
214
+ @last_mouse_pos = current_pos
215
+ end
216
+ end
217
+
218
+ if @mouse_button_callback
219
+ [0, 1, 2].each do |button|
220
+ pressed = @window.get_mouse_button(button) == Native::PRESS
221
+ if pressed != @last_mouse_buttons[button]
222
+ action = pressed ? 1 : 0
223
+ @mouse_button_callback.call(button, action, 0)
224
+ @last_mouse_buttons[button] = pressed
225
+ end
226
+ end
227
+ end
228
+ end
229
+
230
+ def reset_info
231
+ @info[:render][:calls] = 0
232
+ @info[:render][:triangles] = 0
233
+ @info[:render][:points] = 0
234
+ @info[:render][:lines] = 0
235
+ end
236
+
237
+ def render_scene(scene, camera)
238
+ view_matrix = camera.matrix_world_inverse
239
+ projection_matrix = camera.projection_matrix
240
+
241
+ render_objects = []
242
+ scene.traverse do |object|
243
+ render_objects << object if object.is_a?(Mesh) && object.visible
244
+ end
245
+
246
+ lights = collect_lights(scene)
247
+
248
+ render_objects.each do |mesh|
249
+ render_mesh(mesh, camera, view_matrix, projection_matrix, lights)
250
+ end
251
+ end
252
+
253
+ def collect_lights(scene)
254
+ lights = { ambient: Color.new(0), directional: [] }
255
+
256
+ scene.traverse do |object|
257
+ case object
258
+ when AmbientLight
259
+ lights[:ambient] = lights[:ambient] + object.color * object.intensity
260
+ when DirectionalLight
261
+ dir = Vector3.new(0, 0, -1)
262
+ dir.apply_quaternion(object.quaternion)
263
+ lights[:directional] << {
264
+ direction: dir,
265
+ color: object.color * object.intensity
266
+ }
267
+ end
268
+ end
269
+
270
+ lights
271
+ end
272
+
273
+ def render_mesh(mesh, camera, view_matrix, projection_matrix, lights)
274
+ geometry = mesh.geometry
275
+ material = mesh.material
276
+
277
+ return unless geometry && material
278
+
279
+ program = get_or_create_program(material)
280
+ return unless program
281
+
282
+ gl = Native::GL
283
+ gl.use_program(program)
284
+
285
+ gl.active_texture(gl::TEXTURE0)
286
+ gl.bind_texture(gl::TEXTURE_2D, @default_texture)
287
+
288
+ gpu_geometry = get_or_create_geometry(geometry)
289
+ return unless gpu_geometry
290
+
291
+ gl.bind_vertex_array(gpu_geometry[:vao])
292
+
293
+ model_matrix = mesh.matrix_world
294
+ model_view_matrix = view_matrix * model_matrix
295
+ normal_matrix = model_view_matrix.clone.invert!.transpose!
296
+
297
+ set_uniform_matrix4(program, "modelViewMatrix", model_view_matrix)
298
+ set_uniform_matrix4(program, "projectionMatrix", projection_matrix)
299
+ set_uniform_matrix4(program, "modelMatrix", model_matrix)
300
+ set_uniform_matrix4(program, "viewMatrix", view_matrix)
301
+ set_uniform_matrix3(program, "normalMatrix", normal_matrix.to_matrix3)
302
+
303
+ camera_pos = Vector3.new
304
+ camera_pos.set_from_matrix_position(camera.matrix_world)
305
+ set_uniform_vec3(program, "cameraPosition", camera_pos)
306
+
307
+ set_material_uniforms(program, material, lights)
308
+
309
+ wireframe = material.respond_to?(:wireframe) && material.wireframe
310
+ if wireframe
311
+ gl.polygon_mode(gl::FRONT_AND_BACK, gl::LINE)
312
+ linewidth = material.respond_to?(:wireframe_linewidth) ? material.wireframe_linewidth : 1
313
+ gl.line_width(linewidth)
314
+ end
315
+
316
+ if geometry.index
317
+ gl.draw_elements(gl::TRIANGLES, geometry.index.count, gl::UNSIGNED_INT, 0)
318
+ else
319
+ position_attr = geometry.get_attribute(:position)
320
+ vertex_count = position_attr ? position_attr.count : 0
321
+ gl.draw_arrays(gl::TRIANGLES, 0, vertex_count)
322
+ end
323
+
324
+ gl.polygon_mode(gl::FRONT_AND_BACK, gl::FILL) if wireframe
325
+
326
+ gl.bind_vertex_array(0)
327
+
328
+ @info[:render][:calls] += 1
329
+ end
330
+
331
+ def get_or_create_program(material)
332
+ if material.is_a?(ShaderMaterial)
333
+ key = material.object_id
334
+ return @programs[key] if @programs[key]
335
+
336
+ vertex_source = material.vertex_shader
337
+ fragment_source = material.fragment_shader
338
+ return nil unless vertex_source && fragment_source
339
+
340
+ program = compile_program(vertex_source, fragment_source)
341
+ @programs[key] = program if program
342
+ return program
343
+ end
344
+
345
+ key = material.class.name
346
+
347
+ return @programs[key] if @programs[key]
348
+
349
+ shader_data = case material
350
+ when MeshBasicMaterial then ShaderLib.basic
351
+ when MeshLambertMaterial then ShaderLib.lambert
352
+ when MeshPhongMaterial then ShaderLib.phong
353
+ when MeshStandardMaterial then ShaderLib.standard
354
+ else ShaderLib.basic
355
+ end
356
+
357
+ vertex_source = shader_data[:vertex_shader]
358
+ fragment_source = shader_data[:fragment_shader]
359
+
360
+ return nil unless vertex_source && fragment_source
361
+
362
+ program = compile_program(vertex_source, fragment_source)
363
+ @programs[key] = program if program
364
+
365
+ program
366
+ end
367
+
368
+ def compile_program(vertex_source, fragment_source)
369
+ gl = Native::GL
370
+
371
+ vertex_shader = gl.create_shader(gl::VERTEX_SHADER)
372
+ gl.shader_source(vertex_shader, vertex_source)
373
+ gl.compile_shader(vertex_shader)
374
+
375
+ unless gl.get_shader_iv(vertex_shader, gl::COMPILE_STATUS) == 1
376
+ log = gl.get_shader_info_log(vertex_shader)
377
+ warn "Vertex shader compile error: #{log}"
378
+ gl.delete_shader(vertex_shader)
379
+ return nil
380
+ end
381
+
382
+ fragment_shader = gl.create_shader(gl::FRAGMENT_SHADER)
383
+ gl.shader_source(fragment_shader, fragment_source)
384
+ gl.compile_shader(fragment_shader)
385
+
386
+ unless gl.get_shader_iv(fragment_shader, gl::COMPILE_STATUS) == 1
387
+ log = gl.get_shader_info_log(fragment_shader)
388
+ warn "Fragment shader compile error: #{log}"
389
+ gl.delete_shader(vertex_shader)
390
+ gl.delete_shader(fragment_shader)
391
+ return nil
392
+ end
393
+
394
+ program = gl.create_program
395
+ gl.attach_shader(program, vertex_shader)
396
+ gl.attach_shader(program, fragment_shader)
397
+ gl.link_program(program)
398
+
399
+ unless gl.get_program_iv(program, gl::LINK_STATUS) == 1
400
+ log = gl.get_program_info_log(program)
401
+ warn "Program link error: #{log}"
402
+ gl.delete_shader(vertex_shader)
403
+ gl.delete_shader(fragment_shader)
404
+ gl.delete_program(program)
405
+ return nil
406
+ end
407
+
408
+ gl.delete_shader(vertex_shader)
409
+ gl.delete_shader(fragment_shader)
410
+
411
+ program
412
+ end
413
+
414
+ def get_or_create_geometry(geometry)
415
+ @gpu_geometries ||= {}
416
+
417
+ geo_id = geometry.object_id
418
+ return @gpu_geometries[geo_id] if @gpu_geometries[geo_id]
419
+
420
+ position_attr = geometry.get_attribute(:position)
421
+ return nil unless position_attr
422
+
423
+ gl = Native::GL
424
+
425
+ vao = gl.gen_vertex_arrays(1).first
426
+ gl.bind_vertex_array(vao)
427
+
428
+ vbo = gl.gen_buffers(1).first
429
+ gl.bind_buffer(gl::ARRAY_BUFFER, vbo)
430
+ gl.buffer_data(gl::ARRAY_BUFFER, position_attr.array, gl::STATIC_DRAW)
431
+ gl.vertex_attrib_pointer(0, 3, gl::FLOAT, false, 0, 0)
432
+ gl.enable_vertex_attrib_array(0)
433
+
434
+ normal_attr = geometry.get_attribute(:normal)
435
+ if normal_attr
436
+ normal_vbo = gl.gen_buffers(1).first
437
+ gl.bind_buffer(gl::ARRAY_BUFFER, normal_vbo)
438
+ gl.buffer_data(gl::ARRAY_BUFFER, normal_attr.array, gl::STATIC_DRAW)
439
+ gl.vertex_attrib_pointer(1, 3, gl::FLOAT, false, 0, 0)
440
+ gl.enable_vertex_attrib_array(1)
441
+ end
442
+
443
+ uv_attr = geometry.get_attribute(:uv)
444
+ if uv_attr
445
+ uv_vbo = gl.gen_buffers(1).first
446
+ gl.bind_buffer(gl::ARRAY_BUFFER, uv_vbo)
447
+ gl.buffer_data(gl::ARRAY_BUFFER, uv_attr.array, gl::STATIC_DRAW)
448
+ gl.vertex_attrib_pointer(2, 2, gl::FLOAT, false, 0, 0)
449
+ gl.enable_vertex_attrib_array(2)
450
+ end
451
+
452
+ ebo = nil
453
+ if geometry.index
454
+ ebo = gl.gen_buffers(1).first
455
+ gl.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, ebo)
456
+ gl.buffer_data_uint(gl::ELEMENT_ARRAY_BUFFER, geometry.index.array, gl::STATIC_DRAW)
457
+ end
458
+
459
+ gl.bind_vertex_array(0)
460
+
461
+ @gpu_geometries[geo_id] = { vao: vao, vbo: vbo, ebo: ebo }
462
+ end
463
+
464
+ def set_uniform_matrix4(program, name, matrix)
465
+ gl = Native::GL
466
+ location = gl.get_uniform_location(program, name)
467
+ return if location < 0
468
+
469
+ gl.uniform_matrix4fv(location, false, matrix.elements)
470
+ end
471
+
472
+ def set_uniform_matrix3(program, name, matrix)
473
+ gl = Native::GL
474
+ location = gl.get_uniform_location(program, name)
475
+ return if location < 0
476
+
477
+ gl.uniform_matrix3fv(location, false, matrix.elements)
478
+ end
479
+
480
+ def set_uniform_vec3(program, name, vec)
481
+ gl = Native::GL
482
+ location = gl.get_uniform_location(program, name)
483
+ return if location < 0
484
+
485
+ if vec.is_a?(Color)
486
+ gl.uniform_3f(location, vec.r, vec.g, vec.b)
487
+ else
488
+ gl.uniform_3f(location, vec.x, vec.y, vec.z)
489
+ end
490
+ end
491
+
492
+ def set_uniform_vec2(program, name, vec)
493
+ gl = Native::GL
494
+ location = gl.get_uniform_location(program, name)
495
+ return if location < 0
496
+
497
+ gl.uniform_2f(location, vec.x, vec.y)
498
+ end
499
+
500
+ def set_uniform_float(program, name, value)
501
+ gl = Native::GL
502
+ location = gl.get_uniform_location(program, name)
503
+ return if location < 0
504
+
505
+ gl.uniform_1f(location, value)
506
+ end
507
+
508
+ def set_uniform_int(program, name, value)
509
+ gl = Native::GL
510
+ location = gl.get_uniform_location(program, name)
511
+ return if location < 0
512
+
513
+ gl.uniform_1i(location, value)
514
+ end
515
+
516
+ def set_material_uniforms(program, material, lights)
517
+ if material.is_a?(ShaderMaterial)
518
+ set_shader_material_uniforms(program, material)
519
+ return
520
+ end
521
+
522
+ color = material.color || Color.new(0xffffff)
523
+ set_uniform_vec3(program, "diffuse", color)
524
+ set_uniform_float(program, "opacity", material.respond_to?(:opacity) ? material.opacity : 1.0)
525
+ set_uniform_int(program, "useMap", 0)
526
+
527
+ if material.respond_to?(:emissive) && material.emissive
528
+ set_uniform_vec3(program, "emissive", material.emissive)
529
+ end
530
+
531
+ if material.respond_to?(:roughness)
532
+ set_uniform_float(program, "roughness", material.roughness)
533
+ end
534
+
535
+ if material.respond_to?(:metalness)
536
+ set_uniform_float(program, "metalness", material.metalness)
537
+ end
538
+
539
+ set_uniform_vec3(program, "ambientLightColor", lights[:ambient])
540
+
541
+ if lights[:directional].any?
542
+ light = lights[:directional].first
543
+ set_uniform_vec3(program, "directionalLightDirection", light[:direction])
544
+ set_uniform_vec3(program, "directionalLightColor", light[:color])
545
+ end
546
+ end
547
+
548
+ def set_shader_material_uniforms(program, material)
549
+ return unless material.uniforms
550
+
551
+ material.uniforms.each do |name, value|
552
+ case value
553
+ when Float, Integer
554
+ set_uniform_float(program, name.to_s, value.to_f)
555
+ when Vector2
556
+ set_uniform_vec2(program, name.to_s, value)
557
+ when Vector3
558
+ set_uniform_vec3(program, name.to_s, value)
559
+ when Color
560
+ set_uniform_vec3(program, name.to_s, value)
561
+ when Matrix4
562
+ set_uniform_matrix4(program, name.to_s, value)
563
+ end
564
+ end
565
+ end
566
+ end
567
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sunrb
4
+ class Renderer
5
+ attr_accessor :auto_clear, :auto_clear_color, :auto_clear_depth, :auto_clear_stencil
6
+ attr_accessor :sort_objects
7
+ attr_reader :clear_color, :clear_alpha
8
+ attr_reader :width, :height, :pixel_ratio
9
+
10
+ def initialize(width: 800, height: 600)
11
+ @width = width
12
+ @height = height
13
+ @pixel_ratio = 1.0
14
+
15
+ @clear_color = Color.new(0, 0, 0)
16
+ @clear_alpha = 1.0
17
+
18
+ @auto_clear = true
19
+ @auto_clear_color = true
20
+ @auto_clear_depth = true
21
+ @auto_clear_stencil = true
22
+
23
+ @sort_objects = true
24
+ end
25
+
26
+ def set_size(width, height)
27
+ @width = width
28
+ @height = height
29
+ end
30
+
31
+ def set_pixel_ratio(value)
32
+ @pixel_ratio = value
33
+ end
34
+
35
+ def set_clear_color(color, alpha = 1.0)
36
+ @clear_color = color.is_a?(Color) ? color : Color.new(color)
37
+ @clear_alpha = alpha
38
+ end
39
+
40
+ def get_size
41
+ [@width, @height]
42
+ end
43
+
44
+ def get_drawing_buffer_size
45
+ [(@width * @pixel_ratio).to_i, (@height * @pixel_ratio).to_i]
46
+ end
47
+
48
+ def render(_scene, _camera)
49
+ raise NotImplementedError, "Subclasses must implement render"
50
+ end
51
+
52
+ def clear(_color: true, _depth: true, _stencil: true)
53
+ raise NotImplementedError, "Subclasses must implement clear"
54
+ end
55
+
56
+ def dispose
57
+ raise NotImplementedError, "Subclasses must implement dispose"
58
+ end
59
+ end
60
+ end