lib/mittsu/opengl/plugins/sprite_plugin.rb
module Mittsu
class SpritePlugin
include OpenGL::Helper
VERTICES = [
-0.5, -0.5, 0.0, 0.0,
0.5, -0.5, 1.0, 0.0,
0.5, 0.5, 1.0, 1.0,
-0.5, 0.5, 0.0, 1.0
] # Float32Array
FACES = [
0, 1, 2,
0, 2, 3
] # Uint16Array
def initialize(renderer, sprites)
@renderer = renderer
@sprites = sprites
@program = nil
# for decomposing matrixWorld
@sprite_position = Vector3.new
@sprite_rotation = Quaternion.new
@sprite_scale = Vector3.new
end
def render(scene, camera)
return if @sprites.empty?
init if @program.nil?
setup_gl_for_render(camera)
setup_fog(scene)
update_positions_and_sort(camera)
render_all_sprites(scene)
GL.Enable(GL::CULL_FACE)
@renderer.reset_gl_state
end
private
def init
create_vertex_array_object
create_program
init_attributes
init_uniforms
# TODO: canvas texture??
end
def create_vertex_array_object
@vertex_array_object = GL.CreateVertexArray
GL.BindVertexArray(@vertex_array_object)
@vertex_buffer = GL.CreateBuffer
@element_buffer = GL.CreateBuffer
GL.BindBuffer(GL::ARRAY_BUFFER, @vertex_buffer)
GL.BufferData_easy(GL::ARRAY_BUFFER, VERTICES, GL::STATIC_DRAW)
GL.BindBuffer(GL::ELEMENT_ARRAY_BUFFER, @element_buffer)
GL.BufferData_easy(GL::ELEMENT_ARRAY_BUFFER, FACES, GL::STATIC_DRAW)
end
def create_program
@program = GL.CreateProgram
vertex_shader = OpenGL::Shader.new(GL::VERTEX_SHADER, File.read(File.join(__dir__, 'sprite_vertex.glsl')))
fragment_shader = OpenGL::Shader.new(GL::FRAGMENT_SHADER, File.read(File.join(__dir__, 'sprite_fragment.glsl')))
GL.AttachShader(@program, vertex_shader.shader)
GL.AttachShader(@program, fragment_shader.shader)
GL.LinkProgram(@program)
end
def init_attributes
@attributes = {
position: GL.GetAttribLocation(@program, 'position'),
uv: GL.GetAttribLocation(@program, 'uv')
}
end
def init_uniforms
@uniforms = {
uvOffset: GL.GetUniformLocation(@program, 'uvOffset'),
uvScale: GL.GetUniformLocation(@program, 'uvScale'),
rotation: GL.GetUniformLocation(@program, 'rotation'),
scale: GL.GetUniformLocation(@program, 'scale'),
color: GL.GetUniformLocation(@program, 'color'),
map: GL.GetUniformLocation(@program, 'map'),
opacity: GL.GetUniformLocation(@program, 'opacity'),
modelViewMatrix: GL.GetUniformLocation(@program, 'modelViewMatrix'),
projectionMatrix: GL.GetUniformLocation(@program, 'projectionMatrix'),
fogType: GL.GetUniformLocation(@program, 'fogType'),
fogDensity: GL.GetUniformLocation(@program, 'fogDensity'),
fogNear: GL.GetUniformLocation(@program, 'fogNear'),
fogFar: GL.GetUniformLocation(@program, 'fogFar'),
fogColor: GL.GetUniformLocation(@program, 'fogColor'),
alphaTest: GL.GetUniformLocation(@program, 'alphaTest')
}
end
def painter_sort_stable(a, b)
if a.z != b.z
b.z - a.z
else
b.id - a.id
end
end
def setup_gl_for_render(camera)
GL.UseProgram(@program)
GL.Disable(GL::CULL_FACE)
GL.Enable(GL::BLEND)
GL.BindVertexArray(@vertex_array_object)
GL.EnableVertexAttribArray(@attributes[:position])
GL.EnableVertexAttribArray(@attributes[:uv])
GL.BindBuffer(GL::ARRAY_BUFFER, @vertex_buffer)
GL.VertexAttribPointer(@attributes[:position], 2, GL::FLOAT, GL::FALSE, 2 * 8, 0)
GL.VertexAttribPointer(@attributes[:uv], 2, GL::FLOAT, GL::FALSE, 2 * 8, 8)
GL.BindBuffer(GL::ELEMENT_ARRAY_BUFFER, @element_buffer)
GL.UniformMatrix4fv(@uniforms[:projectionMatrix], 1, GL::FALSE, array_to_ptr_easy(camera.projection_matrix.elements))
GL.ActiveTexture(GL::TEXTURE0)
GL.Uniform1i(@uniforms[:map], 0)
end
def setup_fog(scene)
@old_fog_type = 0
@scene_fog_type = 0
fog = scene.fog
if fog
GL.Uniform3f(@uniforms[:fogColor], fog.color.r, fog.color.g, fog.color.b)
if fog.is_a?(Fog)
GL.Uniform1f(@uniforms[:fogNear], fog.near)
GL.Uniform1f(@uniforms[:fogFar], fog.far)
GL.Uniform1i(@uniforms[:fogType], 1)
@old_fog_type = 1
@scene_fog_type = 1
elsif fog.is_a?(FogExp2)
GL.Uniform1f(@uniforms[:fogDensity], fog.density)
GL.Uniform1i(@uniforms[:fogType], 2)
@old_fog_type = 2
@scene_fog_type = 2
end
else
GL.Uniform1i(@uniforms[:fogType], 0)
@old_fog_type = 0
@scene_fog_type = 0
end
end
def update_positions_and_sort(camera)
@sprites.each do |sprite|
sprite.model_view_matrix.multiply_matrices(camera.matrix_world_inverse, sprite.matrix_world)
sprite.z = -sprite.model_view_matrix.elements[14]
end
@sprites.sort!(&self.method(:painter_sort_stable))
end
def render_all_sprites(scene)
@sprites.each do |sprite|
material = sprite.material
set_fog_uniforms(material, scene)
set_uv_uniforms(material)
set_color_uniforms(material)
set_transform_uniforms(sprite)
set_blend_mode(material)
# set texture
if material.map && material.map.image && material.map.image.width
material.map.set(0, @renderer)
else
# TODO: canvas texture?
# texture.set(0, @renderer)
end
# draw elements
GL.DrawElements(GL::TRIANGLES, 6, GL::UNSIGNED_INT, 0) # GL::UNSIGNED_SHORT
end
end
def set_fog_uniforms(material, scene)
fog_type = 0
if scene.fog && material.fog
fog_type = @scene_fog_type
end
if @old_fog_type != fog_type
GL.Uniform1(@uniforms[:fogType], fog_type)
@old_fog_type = fog_type
end
end
def set_uv_uniforms(material)
if !material.map.nil?
GL.Uniform2f(@uniforms[:uvOffset], material.map.offset.x, material.map.offset.y)
GL.Uniform2f(@uniforms[:uvScale], material.map.repeat.x, material.map.repeat.y)
else
GL.Uniform2f(@uniforms[:uvOffset], 0.0, 0.0)
GL.Uniform2f(@uniforms[:uvScale], 1.0, 1.0)
end
end
def set_color_uniforms(material)
GL.Uniform1f(@uniforms[:opacity], material.opacity)
GL.Uniform3f(@uniforms[:color], material.color.r, material.color.g, material.color.b)
GL.Uniform1f(@uniforms[:alphaTest], material.alpha_test)
end
def set_transform_uniforms(sprite)
GL.UniformMatrix4fv(@uniforms[:modelViewMatrix], 1, GL::FALSE, array_to_ptr_easy(sprite.model_view_matrix.elements))
sprite.matrix_world.decompose(@sprite_position, @sprite_rotation, @sprite_scale)
GL.Uniform1f(@uniforms[:rotation], sprite.material.rotation)
GL.Uniform2fv(@uniforms[:scale], 1, array_to_ptr_easy([@sprite_scale.x, @sprite_scale.y]))
end
def set_blend_mode(material)
@renderer.state.set_blending(material.blending, material.blend_equation, material.blend_src, material.blend_dst)
@renderer.state.set_depth_test(material.depth_test)
@renderer.state.set_depth_write(material.depth_write)
end
end
end