Added a large portion of I-MIC-FPS's opengl rendering and model loading systems

This commit is contained in:
2020-07-15 21:19:31 -05:00
parent d7dbcf8511
commit 041cfcccaa
22 changed files with 1724 additions and 3 deletions

View File

@@ -0,0 +1,49 @@
module CyberarmEngine
class Light
DIRECTIONAL = 0
POINT = 1
SPOT = 2
attr_reader :light_id
attr_accessor :type, :ambient, :diffuse, :specular, :position, :direction, :intensity
def initialize(
id:,
type: Light::POINT,
ambient: Vector.new(0.5, 0.5, 0.5),
diffuse: Vector.new(1, 1, 1),
specular: Vector.new(0.2, 0.2, 0.2),
position: Vector.new(0, 0, 0),
direction: Vector.new(0, 0, 0),
intensity: 1
)
@light_id = id
@type = type
@ambient = ambient
@diffuse = diffuse
@specular = specular
@position = position
@direction = direction
@intensity = intensity
end
def draw
glLightfv(@light_id, GL_AMBIENT, convert(@ambient).pack("f*"))
glLightfv(@light_id, GL_DIFFUSE, convert(@diffuse, true).pack("f*"))
glLightfv(@light_id, GL_SPECULAR, convert(@specular, true).pack("f*"))
glLightfv(@light_id, GL_POSITION, convert(@position).pack("f*"))
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1)
glEnable(GL_LIGHTING)
glEnable(@light_id)
end
def convert(struct, apply_intensity = false)
if apply_intensity
return struct.to_a.compact.map{ |i| i * @intensity }
else
return struct.to_a.compact
end
end
end
end

View File

@@ -0,0 +1,42 @@
module CyberarmEngine
class OrthographicCamera
attr_accessor :position, :orientation, :zoom, :left, :right, :bottom, :top,
:min_view_distance, :max_view_distance
def initialize(
position:, orientation: Vector.new(0, 0, 0),
zoom: 1, left: 0, right:, bottom: 0, top:,
min_view_distance: 0.1, max_view_distance: 200.0
)
@position = position
@orientation = orientation
@zoom = zoom
@left, @right, @bottom, @top = left, right, bottom, top
@min_view_distance = min_view_distance
@max_view_distance = max_view_distance
end
# Immediate mode renderering fallback
def draw
glMatrixMode(GL_PROJECTION)
glLoadIdentity
glOrtho(@left, @right, @bottom, @top, @min_view_distance, @max_view_distance)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
glRotatef(@orientation.x, 1, 0, 0)
glRotatef(@orientation.y, 0, 1, 0)
glTranslatef(-@position.x, -@position.y, -@position.z)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity
end
def projection_matrix
Transform.orthographic(@left, @right, @bottom, @top, @min_view_distance, @max_view_distance)
end
def view_matrix
Transform.translate_3d(@position * -1) * Transform.rotate_3d(@orientation)
end
end
end

View File

@@ -0,0 +1,35 @@
module CyberarmEngine
class PerspectiveCamera
attr_accessor :position, :orientation, :aspect_ratio, :field_of_view
def initialize(position:, orientation: Vector.new(0, 0, 0), aspect_ratio:, field_of_view: 70.0, min_view_distance: 0.1, max_view_distance: 155.0)
@position = position
@orientation = orientation
@aspect_ratio = aspect_ratio
@field_of_view = field_of_view
@min_view_distance = min_view_distance
@max_view_distance = max_view_distance
end
def draw
glMatrixMode(GL_PROJECTION)
glLoadIdentity
gluPerspective(@field_of_view, @aspect_ratio, @min_view_distance, @max_view_distance)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
glRotatef(@orientation.x, 1, 0, 0)
glRotatef(@orientation.y, 0, 1, 0)
glTranslatef(-@position.x, -@position.y, -@position.z)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity
end
def projection_matrix
Transform.perspective(@field_of_view, @aspect_ratio, @min_view_distance, @max_view_distance)
end
def view_matrix
Transform.translate_3d(@position * -1) * Transform.rotate_3d(@orientation)
end
end
end

View File

@@ -0,0 +1,248 @@
module CyberarmEngine
class BoundingBoxRenderer
attr_reader :bounding_boxes, :vertex_count
def initialize
@bounding_boxes = {}
@vertex_count = 0
end
def render(entities)
entities.each do |entity|
create_bounding_box(entity,color = nil)
draw_bounding_boxes
end
(@bounding_boxes.keys - entities.map { |e| e.object_id }).each do |key|
@bounding_boxes.delete(key)
end
end
def create_bounding_box(entity, color = nil)
color ||= entity.debug_color
entity_id = entity.object_id
if @bounding_boxes[entity_id]
if @bounding_boxes[entity_id][:color] != color
@bounding_boxes[entity_id][:colors] = mesh_colors(color).pack("f*")
@bounding_boxes[entity_id][:color] = color
return
else
return
end
end
@bounding_boxes[entity_id] = {
entity: entity,
color: color,
objects: []
}
box = entity.normalize_bounding_box
normals = mesh_normals
colors = mesh_colors(color)
vertices = mesh_vertices(box)
@vertex_count+=vertices.size
@bounding_boxes[entity_id][:vertices_size] = vertices.size
@bounding_boxes[entity_id][:vertices] = vertices.pack("f*")
@bounding_boxes[entity_id][:normals] = normals.pack("f*")
@bounding_boxes[entity_id][:colors] = colors.pack("f*")
entity.model.objects.each do |mesh|
data = {}
box = mesh.bounding_box.normalize(entity)
normals = mesh_normals
colors = mesh_colors(mesh.debug_color)
vertices = mesh_vertices(box)
@vertex_count+=vertices.size
data[:vertices_size] = vertices.size
data[:vertices] = vertices.pack("f*")
data[:normals] = normals.pack("f*")
data[:colors] = colors.pack("f*")
@bounding_boxes[entity_id][:objects] << data
end
end
def mesh_normals
[
0,1,0,
0,1,0,
0,1,0,
0,1,0,
0,1,0,
0,1,0,
0,-1,0,
0,-1,0,
0,-1,0,
0,-1,0,
0,-1,0,
0,-1,0,
0,0,1,
0,0,1,
0,0,1,
0,0,1,
0,0,1,
0,0,1,
1,0,0,
1,0,0,
1,0,0,
1,0,0,
1,0,0,
1,0,0,
-1,0,0,
-1,0,0,
-1,0,0,
-1,0,0,
-1,0,0,
-1,0,0,
-1,0,0,
-1,0,0,
-1,0,0,
-1,0,0,
-1,0,0,
-1,0,0
]
end
def mesh_colors(color)
[
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue,
color.red, color.green, color.blue
]
end
def mesh_vertices(box)
[
box.min.x, box.max.y, box.max.z,
box.min.x, box.max.y, box.min.z,
box.max.x, box.max.y, box.min.z,
box.min.x, box.max.y, box.max.z,
box.max.x, box.max.y, box.max.z,
box.max.x, box.max.y, box.min.z,
box.max.x, box.min.y, box.min.z,
box.max.x, box.min.y, box.max.z,
box.min.x, box.min.y, box.max.z,
box.max.x, box.min.y, box.min.z,
box.min.x, box.min.y, box.min.z,
box.min.x, box.min.y, box.max.z,
box.min.x, box.max.y, box.max.z,
box.min.x, box.max.y, box.min.z,
box.min.x, box.min.y, box.min.z,
box.min.x, box.min.y, box.max.z,
box.min.x, box.min.y, box.min.z,
box.min.x, box.max.y, box.max.z,
box.max.x, box.max.y, box.max.z,
box.max.x, box.max.y, box.min.z,
box.max.x, box.min.y, box.min.z,
box.max.x, box.min.y, box.max.z,
box.max.x, box.min.y, box.min.z,
box.max.x, box.max.y, box.max.z,
box.min.x, box.max.y, box.max.z,
box.max.x, box.max.y, box.max.z,
box.max.x, box.min.y, box.max.z,
box.min.x, box.max.y, box.max.z,
box.max.x, box.min.y, box.max.z,
box.min.x, box.min.y, box.max.z,
box.max.x, box.min.y, box.min.z,
box.min.x, box.min.y, box.min.z,
box.min.x, box.max.y, box.min.z,
box.max.x, box.min.y, box.min.z,
box.min.x, box.max.y, box.min.z,
box.max.x, box.max.y, box.min.z
]
end
def draw_bounding_boxes
@bounding_boxes.each do |key, bounding_box|
glPushMatrix
glTranslatef(
bounding_box[:entity].position.x,
bounding_box[:entity].position.y,
bounding_box[:entity].position.z
)
draw_bounding_box(bounding_box)
@bounding_boxes[key][:objects].each {|o| draw_bounding_box(o)}
glPopMatrix
end
end
def draw_bounding_box(bounding_box)
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_COLOR_ARRAY)
glEnableClientState(GL_NORMAL_ARRAY)
glVertexPointer(3, GL_FLOAT, 0, bounding_box[:vertices])
glColorPointer(3, GL_FLOAT, 0, bounding_box[:colors])
glNormalPointer(GL_FLOAT, 0, bounding_box[:normals])
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
glDisable(GL_LIGHTING)
glDrawArrays(GL_TRIANGLES, 0, bounding_box[:vertices_size] / 3)
glEnable(GL_LIGHTING)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_COLOR_ARRAY)
glDisableClientState(GL_NORMAL_ARRAY)
end
end
end

View File

@@ -0,0 +1,162 @@
module CyberarmEngine
class GBuffer
attr_reader :screen_vbo, :vertices, :uvs
def initialize(width:, height:)
@width, @height = width, height
@framebuffer = nil
@buffers = [:position, :diffuse, :normal, :texcoord]
@textures = {}
@screen_vbo = nil
@ready = false
@vertices = [
-1.0, -1.0, 0,
1.0, -1.0, 0,
-1.0, 1.0, 0,
-1.0, 1.0, 0,
1.0, -1.0, 0,
1.0, 1.0, 0,
].freeze
@uvs = [
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1
].freeze
create_framebuffer
create_screen_vbo
end
def create_framebuffer
buffer = ' ' * 4
glGenFramebuffers(1, buffer)
@framebuffer = buffer.unpack('L2').first
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, @framebuffer)
create_textures
status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
if status != GL_FRAMEBUFFER_COMPLETE
message = ""
case status
when GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
message = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"
when GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
message = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"
when GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER
message = "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"
when GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER
message = "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"
when GL_FRAMEBUFFER_UNSUPPORTED
message = "GL_FRAMEBUFFER_UNSUPPORTED"
else
message = "Unknown error!"
end
puts "Incomplete framebuffer: #{status}\nError: #{message}"
else
@ready = true
end
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)
end
def create_textures
@buffers.size.times do |i|
buffer = ' ' * 4
glGenTextures(1, buffer)
texture_id = buffer.unpack('L2').first
@textures[@buffers[i]] = texture_id
glBindTexture(GL_TEXTURE_2D, texture_id)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, @width, @height, 0, GL_RGBA, GL_FLOAT, nil)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, texture_id, 0)
end
buffer = ' ' * 4
glGenTextures(1, buffer)
texture_id = buffer.unpack('L2').first
@textures[:depth] = texture_id
glBindTexture(GL_TEXTURE_2D, texture_id)
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, @width, @height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nil)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture_id, 0)
draw_buffers = [ GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 ]
glDrawBuffers(draw_buffers.size, draw_buffers.pack("I*"))
end
def create_screen_vbo
buffer = ' ' * 4
glGenVertexArrays(1, buffer)
@screen_vbo = buffer.unpack('L2').first
buffer = " " * 4
glGenBuffers(1, buffer)
@positions_buffer_id = buffer.unpack('L2').first
buffer = " " * 4
glGenBuffers(1, buffer)
@uvs_buffer_id = buffer.unpack('L2').first
glBindVertexArray(@screen_vbo)
glBindBuffer(GL_ARRAY_BUFFER, @positions_buffer_id)
glBufferData(GL_ARRAY_BUFFER, @vertices.size * Fiddle::SIZEOF_FLOAT, @vertices.pack("f*"), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nil)
glBindBuffer(GL_ARRAY_BUFFER, @uvs_buffer_id)
glBufferData(GL_ARRAY_BUFFER, @uvs.size * Fiddle::SIZEOF_FLOAT, @uvs.pack("f*"), GL_STATIC_DRAW);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, nil)
glEnableVertexAttribArray(0)
glEnableVertexAttribArray(1)
glBindVertexArray(0)
end
def bind_for_writing
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, @framebuffer)
end
def bind_for_reading
glBindFramebuffer(GL_READ_FRAMEBUFFER, @framebuffer)
end
def set_read_buffer(buffer)
glReadBuffer(GL_COLOR_ATTACHMENT0 + @textures.keys.index(buffer))
end
def set_read_buffer_depth
glReadBuffer(GL_DEPTH_ATTACHMENT)
end
def unbind_framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0)
end
def texture(type)
@textures[type]
end
def clean_up
glDeleteFramebuffers(1, [@framebuffer].pack("L"))
glDeleteTextures(@textures.values.size, @textures.values.pack("L*"))
glDeleteBuffers(2, [@positions_buffer_id, @uvs_buffer_id].pack("L*"))
glDeleteVertexArrays(1, [@screen_vbo].pack("L"))
gl_error?
end
end
end

View File

@@ -0,0 +1,285 @@
module CyberarmEngine
class OpenGLRenderer
@@immediate_mode_warning = false
attr_accessor :show_wireframe
def initialize(width:, height:, show_wireframe: false)
@width, @height = width, height
@show_wireframe = show_wireframe
@g_buffer = GBuffer.new(width: @width, height: @height)
end
def canvas_size_changed
@g_buffer.unbind_framebuffer
@g_buffer.clean_up
@g_buffer = GBuffer.new(width: @width, height: @height)
end
def render(camera, lights, entities)
glViewport(0, 0, @width, @height)
glEnable(GL_DEPTH_TEST)
if Shader.available?("g_buffer") && Shader.available?("lighting")
@g_buffer.bind_for_writing
gl_error?
glClearColor(0.0, 0.0, 0.0, 0.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
Shader.use("g_buffer") do |shader|
gl_error?
entities.each do |entity|
next unless entity.visible && entity.renderable
shader.uniform_transform("projection", camera.projection_matrix)
shader.uniform_transform("view", camera.view_matrix)
shader.uniform_transform("model", entity.model_matrix)
shader.uniform_vec3("cameraPosition", camera.position)
gl_error?
draw_model(entity.model, shader)
entity.draw
end
end
@g_buffer.unbind_framebuffer
gl_error?
@g_buffer.bind_for_reading
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)
lighting(lights)
gl_error?
post_processing
gl_error?
# render_framebuffer
gl_error?
@g_buffer.unbind_framebuffer
gl_error?
else
puts "Shaders are disabled or failed to compile, using immediate mode for rendering..." unless @@immediate_mode_warning
@@immediate_mode_warning = true
gl_error?
lights.each(&:draw)
camera.draw
glEnable(GL_NORMALIZE)
entities.each do |entity|
next unless entity.visible && entity.renderable
glPushMatrix
glTranslatef(entity.position.x, entity.position.y, entity.position.z)
glScalef(entity.scale.x, entity.scale.y, entity.scale.z)
glRotatef(entity.orientation.x, 1.0, 0, 0)
glRotatef(entity.orientation.y, 0, 1.0, 0)
glRotatef(entity.orientation.z, 0, 0, 1.0)
gl_error?
draw_mesh(entity.model)
entity.draw
glPopMatrix
end
end
gl_error?
end
def copy_g_buffer_to_screen
@g_buffer.set_read_buffer(:position)
glBlitFramebuffer(0, 0, @g_buffer.width, @g_buffer.height,
0, 0, @g_buffer.width / 2, @g_buffer.height / 2,
GL_COLOR_BUFFER_BIT, GL_LINEAR)
@g_buffer.set_read_buffer(:diffuse)
glBlitFramebuffer(0, 0, @g_buffer.width, @g_buffer.height,
0, @g_buffer.height / 2, @g_buffer.width / 2, @g_buffer.height,
GL_COLOR_BUFFER_BIT, GL_LINEAR)
@g_buffer.set_read_buffer(:normal)
glBlitFramebuffer(0, 0, @g_buffer.width, @g_buffer.height,
@g_buffer.width / 2, @g_buffer.height / 2, @g_buffer.width, @g_buffer.height,
GL_COLOR_BUFFER_BIT, GL_LINEAR)
@g_buffer.set_read_buffer(:texcoord)
glBlitFramebuffer(0, 0, @g_buffer.width, @g_buffer.height,
@g_buffer.width / 2, 0, @g_buffer.width, @g_buffer.height / 2,
GL_COLOR_BUFFER_BIT, GL_LINEAR)
end
def lighting(lights)
Shader.use("lighting") do |shader|
glBindVertexArray(@g_buffer.screen_vbo)
glDisable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:diffuse))
shader.uniform_integer("diffuse", 0)
glActiveTexture(GL_TEXTURE1)
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:position))
shader.uniform_integer("position", 1)
glActiveTexture(GL_TEXTURE2)
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:texcoord))
shader.uniform_integer("texcoord", 2)
glActiveTexture(GL_TEXTURE3)
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:normal))
shader.uniform_integer("normal", 3)
glActiveTexture(GL_TEXTURE4)
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:depth))
shader.uniform_integer("depth", 4)
lights.each_with_index do |light, i|
shader.uniform_integer("light[0].type", light.type);
shader.uniform_vec3("light[0].direction", light.direction)
shader.uniform_vec3("light[0].position", light.position)
shader.uniform_vec3("light[0].diffuse", light.diffuse)
shader.uniform_vec3("light[0].ambient", light.ambient)
shader.uniform_vec3("light[0].specular", light.specular)
glDrawArrays(GL_TRIANGLES, 0, @g_buffer.vertices.size)
end
glBindVertexArray(0)
end
end
def post_processing
end
def render_framebuffer
if Shader.available?("lighting")
Shader.use("lighting") do |shader|
glBindVertexArray(@g_buffer.screen_vbo)
glDisable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, @g_buffer.texture(:diffuse))
shader.uniform_integer("diffuse_texture", 0)
glDrawArrays(GL_TRIANGLES, 0, @g_buffer.vertices.size)
glBindVertexArray(0)
end
end
end
def draw_model(model, shader)
glBindVertexArray(model.vertex_array_id)
glEnableVertexAttribArray(0)
glEnableVertexAttribArray(1)
glEnableVertexAttribArray(2)
if model.has_texture?
glEnableVertexAttribArray(3)
glEnableVertexAttribArray(4)
end
if @show_wireframe
glLineWidth(2)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
Shader.active_shader.uniform_boolean("disableLighting", true)
glDrawArrays(GL_TRIANGLES, 0, model.faces.count * 3)
Shader.active_shader.uniform_boolean("disableLighting", false)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
glLineWidth(1)
end
offset = 0
model.objects.each do |object|
shader.uniform_boolean("hasTexture", object.has_texture?)
if object.has_texture?
glBindTexture(GL_TEXTURE_2D, object.materials.find { |mat| mat.texture_id }.texture_id)
else
glBindTexture(GL_TEXTURE_2D, 0)
end
glDrawArrays(GL_TRIANGLES, offset, object.faces.count * 3)
offset += object.faces.count * 3
end
if model.has_texture?
glDisableVertexAttribArray(4)
glDisableVertexAttribArray(3)
glBindTexture(GL_TEXTURE_2D, 0)
end
glDisableVertexAttribArray(2)
glDisableVertexAttribArray(1)
glDisableVertexAttribArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
end
def draw_mesh(model)
model.objects.each_with_index do |o, i|
glEnable(GL_COLOR_MATERIAL)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
glShadeModel(GL_FLAT) unless o.faces.first[4]
glShadeModel(GL_SMOOTH) if o.faces.first[4]
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_COLOR_ARRAY)
glEnableClientState(GL_NORMAL_ARRAY)
if o.has_texture?
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, o.materials.find { |mat| mat.texture_id }.texture_id)
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
glTexCoordPointer(3, GL_FLOAT, 0, o.flattened_uvs)
end
glVertexPointer(4, GL_FLOAT, 0, o.flattened_vertices)
glColorPointer(3, GL_FLOAT, 0, o.flattened_materials)
glNormalPointer(GL_FLOAT, 0, o.flattened_normals)
if @show_wireframe # This is kinda expensive
glDisable(GL_LIGHTING)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
glPolygonOffset(2, 0.5)
glLineWidth(3)
glDrawArrays(GL_TRIANGLES, 0, o.flattened_vertices_size/4)
glLineWidth(1)
glPolygonOffset(0, 0)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
glEnable(GL_LIGHTING)
glDrawArrays(GL_TRIANGLES, 0, o.flattened_vertices_size/4)
else
glDrawArrays(GL_TRIANGLES, 0, o.flattened_vertices_size/4)
end
# glBindBuffer(GL_ARRAY_BUFFER, 0)
glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_COLOR_ARRAY)
glDisableClientState(GL_NORMAL_ARRAY)
if o.has_texture?
glDisableClientState(GL_TEXTURE_COORD_ARRAY)
glDisable(GL_TEXTURE_2D)
end
glDisable(GL_COLOR_MATERIAL)
end
end
end
end

View File

@@ -0,0 +1,22 @@
module CyberarmEngine
class Renderer
attr_reader :opengl_renderer, :bounding_box_renderer
def initialize
@bounding_box_renderer = BoundingBoxRenderer.new
@opengl_renderer = OpenGLRenderer.new(width: $window.width, height: $window.height)
end
def draw(camera, lights, entities)
@opengl_renderer.render(camera, lights, entities)
@bounding_box_renderer.render(entities) if @show_bounding_boxes
end
def canvas_size_changed
@opengl_renderer.canvas_size_changed
end
def finalize # cleanup
end
end
end

View File

@@ -0,0 +1,398 @@
module CyberarmEngine
# Ref: https://github.com/vaiorabbit/ruby-opengl/blob/master/sample/OrangeBook/brick.rb
class Shader
include OpenGL
@@shaders = {} # Cache for {Shader} instances
PREPROCESSOR_CHARACTER = "@".freeze # magic character for preprocessor phase of {Shader} compilation
# add instance of {Shader} to cache
#
# @param name [String]
# @param instance [Shader]
def self.add(name, instance)
@@shaders[name] = instance
end
# removes {Shader} from cache and cleans up
#
# @param name [String]
def self.delete(name)
shader = @@shaders.dig(name)
if shader
@@shaders.delete(name)
if shader.compiled?
glDeleteProgram(shader.program)
end
end
end
##
# runs _block_ using {Shader} with _name_
#
# @example
#
# CyberarmEngine::Shader.use("blur") do |shader|
# shader.uniform_float("radius", 20.0)
# # OpenGL Code that uses shader
# end
#
# @param name [String] name of {Shader} to use
# @return [void]
def self.use(name, &block)
shader = @@shaders.dig(name)
if shader
shader.use(&block)
else
raise ArgumentError, "Shader '#{name}' not found!"
end
end
# returns whether {Shader} with _name_ is in cache
#
# @param name [String]
# @return [Boolean]
def self.available?(name)
@@shaders.dig(name).is_a?(Shader)
end
# returns instance of {Shader}, if it exists
#
# @param name [String]
# @return [Shader?]
def self.get(name)
@@shaders.dig(name)
end
# returns currently active {Shader}, if one is active
#
# @return [Shader?]
def self.active_shader
@active_shader
end
# sets currently active {Shader}
#
# @param instance [Shader] instance of {Shader} to set as active
def self.active_shader=(instance)
@active_shader = instance
end
# stops using currently active {Shader}
def self.stop
shader = Shader.active_shader
if shader
shader.stop
else
raise ArgumentError, "No active shader to stop!"
end
end
# returns location of OpenGL Shader uniform
#
# @param variable [String]
def self.attribute_location(variable)
raise RuntimeError, "No active shader!" unless Shader.active_shader
Shader.active_shader.attribute_location(variable)
end
# sets _variable_ to _value_
#
# @param variable [String]
# @param value
def self.set_uniform(variable, value)
raise RuntimeError, "No active shader!" unless Shader.active_shader
Shader.active_shader.set_uniform(variable, value)
end
attr_reader :name, :program
def initialize(name:, includes_dir: nil, vertex: "shaders/default.vert", fragment:)
raise "Shader name can not be blank" if name.length == 0
@name = name
@includes_dir = includes_dir
@compiled = false
@program = nil
@error_buffer_size = 1024 * 8
@variable_missing = {}
@data = {shaders: {}}
unless shader_files_exist?(vertex: vertex, fragment: fragment)
raise ArgumentError, "Shader files not found: #{vertex} or #{fragment}"
end
create_shader(type: :vertex, source: File.read(vertex))
create_shader(type: :fragment, source: File.read(fragment))
compile_shader(type: :vertex)
compile_shader(type: :fragment)
link_shaders
@data[:shaders].each { |key, id| glDeleteShader(id) }
# Only add shader if it successfully compiles
if @compiled
puts "compiled!"
puts "Compiled shader: #{@name}"
Shader.add(@name, self)
else
glDeleteProgram(@program)
warn "FAILED to compile shader: #{@name}", ""
end
end
# whether vertex and fragment files exist on disk
#
# @return [Boolean]
def shader_files_exist?(vertex:, fragment:)
File.exist?(vertex) && File.exist?(fragment)
end
# creates an OpenGL Shader of _type_ using _source_
#
# @param type [Symbol] valid values are: :vertex, :fragment
# @param source [String] source code for shader
def create_shader(type:, source:)
_shader = nil
case type
when :vertex
_shader = glCreateShader(GL_VERTEX_SHADER)
when :fragment
_shader = glCreateShader(GL_FRAGMENT_SHADER)
else
raise ArgumentError, "Unsupported shader type: #{type.inspect}"
end
processed_source = preprocess_source(source: source)
_source = [processed_source].pack("p")
_size = [processed_source.length].pack("I")
glShaderSource(_shader, 1, _source, _size)
@data[:shaders][type] =_shader
end
# evaluates shader preprocessors
#
# currently supported preprocessors:
#
# @include "file/path" "another/file/path" # => Replace line with contents of file; Shader includes_dir must be specified in constructor
#
# @example
# # Example Vertex Shader #
# # #version 330 core
# # @include "material_struct"
# # void main() {
# # gl_Position = vec4(1, 1, 1, 1);
# # }
#
# Shader.new(name: "model_renderer", includes_dir: "path/to/includes", vertex: "path/to/vertex_shader.glsl")
#
# @param source shader source code
def preprocess_source(source:)
lines = source.lines
lines.each_with_index do |line, i|
if line.start_with?(PREPROCESSOR_CHARACTER)
preprocessor = line.strip.split(" ")
lines.delete(line)
case preprocessor.first
when "@include"
raise ArgumentError, "Shader preprocessor include directory was not given for shader #{@name}" unless @includes_dir
preprocessor[1..preprocessor.length - 1].join.scan(/"([^"]*)"/).flatten.each do |file|
source = File.read("#{@includes_dir}/#{file}.glsl")
lines.insert(i, source)
end
else
warn "Unsupported preprocessor #{preprocessor.first} for #{@name}"
end
end
end
lines.join
end
# compile OpenGL Shader of _type_
#
# @return [Boolean] whether compilation succeeded
def compile_shader(type:)
_compiled = false
_shader = @data[:shaders][type]
raise ArgumentError, "No shader for #{type.inspect}" unless _shader
glCompileShader(_shader)
buffer = ' '
glGetShaderiv(_shader, GL_COMPILE_STATUS, buffer)
compiled = buffer.unpack('L')[0]
if compiled == 0
log = ' ' * @error_buffer_size
glGetShaderInfoLog(_shader, @error_buffer_size, nil, log)
puts "Shader Error: Program \"#{@name}\""
puts " #{type.to_s.capitalize} Shader InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n"
puts " Shader Compiled status: #{compiled}"
puts " NOTE: assignment of uniforms in shaders is illegal!"
puts
else
_compiled = true
end
return _compiled
end
# link compiled OpenGL Shaders in to a OpenGL Program
#
# @note linking must succeed or shader cannot be used
#
# @return [Boolean] whether linking succeeded
def link_shaders
@program = glCreateProgram
@data[:shaders].values.each do |_shader|
glAttachShader(@program, _shader)
end
glLinkProgram(@program)
buffer = ' '
glGetProgramiv(@program, GL_LINK_STATUS, buffer)
linked = buffer.unpack('L')[0]
if linked == 0
log = ' ' * @error_buffer_size
glGetProgramInfoLog(@program, @error_buffer_size, nil, log)
puts "Shader Error: Program \"#{@name}\""
puts " Program InfoLog:", " #{log.strip.split("\n").join("\n ")}\n\n"
end
@compiled = linked == 0 ? false : true
end
# Returns the location of a uniform _variable_
#
# @param variable [String]
# @return [Integer] location of uniform
def variable(variable)
loc = glGetUniformLocation(@program, variable)
if (loc == -1)
puts "Shader Error: Program \"#{@name}\" has no such uniform named \"#{variable}\"", " Is it used in the shader? GLSL may have optimized it out.", " Is it miss spelled?" unless @variable_missing[variable]
@variable_missing[variable] = true
end
return loc
end
# @see Shader.use Shader.use
def use(&block)
return unless compiled?
raise "Another shader is already in use! #{Shader.active_shader.name.inspect}" if Shader.active_shader
Shader.active_shader=self
glUseProgram(@program)
if block
block.call(self)
stop
end
end
# stop using shader, if shader is active
def stop
Shader.active_shader = nil if Shader.active_shader == self
glUseProgram(0)
end
# @return [Boolean] whether {Shader} successfully compiled
def compiled?
@compiled
end
# returns location of a uniform _variable_
#
# @note Use {#variable} for friendly error handling
# @see #variable Shader#variable
#
# @param variable [String]
# @return [Integer]
def attribute_location(variable)
glGetUniformLocation(@program, variable)
end
# send {Transform} to {Shader}
#
# @param variable [String]
# @param value [Transform]
# @param location [Integer]
# @return [void]
def uniform_transform(variable, value, location = nil)
attr_loc = location ? location : attribute_location(variable)
glUniformMatrix4fv(attr_loc, 1, GL_FALSE, value.to_gl.pack("F16"))
end
# send Boolean to {Shader}
#
# @param variable [String]
# @param value [Boolean]
# @param location [Integer]
# @return [void]
def uniform_boolean(variable, value, location = nil)
attr_loc = location ? location : attribute_location(variable)
glUniform1i(attr_loc, value ? 1 : 0)
end
# send Integer to {Shader}
# @param variable [String]
# @param value [Integer]
# @param location [Integer]
# @return [void]
def uniform_integer(variable, value, location = nil)
attr_loc = location ? location : attribute_location(variable)
glUniform1i(attr_loc, value)
end
# send Float to {Shader}
#
# @param variable [String]
# @param value [Float]
# @param location [Integer]
# @return [void]
def uniform_float(variable, value, location = nil)
attr_loc = location ? location : attribute_location(variable)
glUniform1f(attr_loc, value)
end
# send {Vector} (x, y, z) to {Shader}
#
# @param variable [String]
# @param value [Vector]
# @param location [Integer]
# @return [void]
def uniform_vec3(variable, value, location = nil)
attr_loc = location ? location : attribute_location(variable)
glUniform3f(attr_loc, *value.to_a[0..2])
end
# send {Vector} to {Shader}
#
# @param variable [String]
# @param value [Vector]
# @param location [Integer]
# @return [void]
def uniform_vec4(variable, value, location = nil)
attr_loc = location ? location : attribute_location(variable)
glUniform4f(attr_loc, *value.to_a)
end
end
end

View File

@@ -0,0 +1,67 @@
module CyberarmEngine
class Texture
DEFAULT_TEXTURE = "#{CYBERARM_ENGINE_ROOT_PATH}/assets/textures/default.png"
CACHE = {}
def self.release_textures
CACHE.values.each do |id|
glDeleteTextures(id)
end
end
def self.from_cache(path, retro)
return CACHE.dig("#{path}?retro=#{retro}")
end
attr_reader :id
def initialize(path: nil, image: nil, retro: false)
raise "keyword :path or :image must be provided!" if path.nil? && image.nil?
@retro = retro
@path = path
if @path
unless File.exist?(@path)
warn "Missing texture at: #{@path}"
@retro = true # override retro setting
@path = DEFAULT_TEXTURE
end
if texture = Texture.from_cache(@path, @retro)
@id = texture.id
return
end
image = load_image(@path)
@id = create_from_image(image)
else
@id = create_from_image(image)
end
end
def load_image(path)
CACHE["#{path}?retro=#{@retro}"] = self
Gosu::Image.new(path, retro: @retro)
end
def create_from_image(image)
array_of_pixels = image.to_blob
tex_names_buf = ' ' * 4
glGenTextures(1, tex_names_buf)
texture_id = tex_names_buf.unpack('L2').first
glBindTexture(GL_TEXTURE_2D, texture_id)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, array_of_pixels)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) if @retro
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) unless @retro
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
glGenerateMipmap(GL_TEXTURE_2D)
gl_error?
return texture_id
end
end
end