From: Alexis Laferrière Date: Sat, 23 Jan 2016 22:20:50 +0000 (-0500) Subject: lib/gamnit depth: intro particle systems X-Git-Tag: v0.8~7^2~7 X-Git-Url: http://nitlanguage.org lib/gamnit depth: intro particle systems Signed-off-by: Alexis Laferrière --- diff --git a/lib/gamnit/depth/depth.nit b/lib/gamnit/depth/depth.nit index c2328c6..b3cab23 100644 --- a/lib/gamnit/depth/depth.nit +++ b/lib/gamnit/depth/depth.nit @@ -17,6 +17,7 @@ module depth intrude import more_materials import more_models +import particles redef class App @@ -29,15 +30,12 @@ redef class App world_camera.near = 0.1 # Prepare programs - var program = versatile_program - program.compile_and_link - var gamnit_error = program.error - assert gamnit_error == null else print_error gamnit_error - - program = normals_program - normals_program.compile_and_link - gamnit_error = program.error - assert gamnit_error == null else print_error gamnit_error + var programs = [versatile_program, normals_program, explosion_program, smoke_program: GamnitProgram] + for program in programs do + program.compile_and_link + var gamnit_error = program.error + assert gamnit_error == null else print_error gamnit_error + end end redef fun frame_core_draw(display) do frame_core_depth display @@ -58,6 +56,11 @@ redef class App end end + # Toggle writing to the depth buffer for particles effects + glDepthMask false + for system in particle_systems do system.draw + glDepthMask true + frame_core_flat display end end diff --git a/lib/gamnit/depth/particles.nit b/lib/gamnit/depth/particles.nit new file mode 100644 index 0000000..e3be795 --- /dev/null +++ b/lib/gamnit/depth/particles.nit @@ -0,0 +1,322 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Particle effects +# +# Particles are managed by instances of `ParticleSystem` that +# are configured for a specific kind of particle. +# For instance, a particle system can be created for a max of 100 particles, +# with a smoke effect and a precise texture, as in: +# +# ~~~ +# var smoke = new ParticleSystem(100, app.smoke_program, new Texture("smoke.png")) +# ~~~ +# +# The system must be registered in `app.particle_systems` to be drawn on screen. +# +# Particles are added to a system with their configuration, as in: +# +# ~~~ +# var position = new Point3d[Float](0.0, 0.0, 0.0) +# var scale = 2.0 +# var time_to_live = 1.0 # in seconds +# smoke.add(position, scale, time_to_live) +# ~~~ +module particles + +import depth_core + +redef class App + + # Graphics program to display blowing up particles + var explosion_program = new ExplosionProgram + + # Graphics program to display particles slowly drifting upwards + var smoke_program = new SmokeProgram + + # Enabled particle emitters + # + # To be populated by the client program. + var particle_systems = new Array[ParticleSystem] +end + +# Particle system using `program` and `texture` to draw each particles +# +# Each instance draws a maximum of `n_particles`. +# If full, new particles replace the oldest particles. +# Expired particle are still sent to the CPU but should be discarded by the vertex shader. +class ParticleSystem + + # Maximum number of particles + var n_particles: Int + + private var total_particles = 0 + + # Program to draw the particles + var program: ParticleProgram + + # Texture to apply on particles, if any + var texture: nullable Texture + + # Clock used to set `ots` and `program::t` + # + # TODO control this value from the game logic to allow pausing and slowing time. + private var clock = new Clock + + # Coordinates of each particle effects + private var centers = new Array[Float] + + # Creation time of each particles + private var ots = new Array[Float] + + # Scale of each particles + private var scales = new Array[Float] + + # Time-to-live of each particle + private var ttls = new Array[Float] + + # Add a particle at `center` with `scale`, living for `ttl` from `time_offset` + # + # `time_offset` is added to the creation time, it can be used to delay the + # apparition of a particle using a positive value. + # + # See the doc of the precise class of `program`, or the general `ParticleProgram` + # for information on the effect of these parameters. + fun add(center: Point3d[Float], scale: Float, ttl: Float, time_offset: nullable Float) + do + var i = total_particles % n_particles + total_particles += 1 + + centers[i*3 ] = center.x + centers[i*3+1] = center.y + centers[i*3+2] = center.z + + ttls[i] = ttl + scales[i] = scale + + time_offset = time_offset or else 0.0 + ots[i] = clock.total.to_f + time_offset + end + + # Draw all particles of this emitter + fun draw + do + if ots.is_empty then return + + var program = program + program.use + + var texture = texture + if texture != null then + glActiveTexture gl_TEXTURE0 + glBindTexture(gl_TEXTURE_2D, texture.gl_texture) + program.use_texture.uniform true + program.texture.uniform 0 + else + program.use_texture.uniform false + end + + program.scale.array_enabled = true + program.scale.array(scales, 1) + + program.center.array_enabled = true + program.center.array(centers, 3) + + program.color.array_enabled = false + program.color.uniform(1.0, 1.0, 1.0, 1.0) + + program.ot.array_enabled = true + program.ot.array(ots, 1) + + program.ttl.array_enabled = true + program.ttl.array(ttls, 1) + + program.t.uniform clock.total.to_f + program.mvp.uniform app.world_camera.mvp_matrix + + glDrawArrays(gl_POINTS, 0, ots.length) + end +end + +# Particle drawing program using `gl_POINTS` +# +# This program should be subclassed to create custom particle effects. +# Either `vertex_shader_source` and `vertex_shader_core` can be refined. +abstract class ParticleProgram + super GamnitProgramFromSource + + redef var vertex_shader_source = """ + // Coordinates of particle effects + attribute vec4 center; + + // Particles color tint + attribute vec4 color; + varying vec4 v_color; + + // Per particle scaling + attribute float scale; + + // Model view projection matrix + uniform mat4 mvp; + + // Time-to-live of each particle + attribute float ttl; + + // Creation time of each particle + attribute float ot; + + // Current time + uniform float t; + + void main() + { + // Pass varyings to the fragment shader + v_color = color; + + float dt = t - ot; + float pt = dt/ttl; + + // Discard expired or not yet created particles + if (dt > ttl || dt < 0.0) { + gl_PointSize = 0.0; + return; + } + + {{{vertex_shader_core}}} + } + """ + + # Core GLSL code for `vertex_shader_source` + # + # Refine this function to easily tweak the position, size and color of particles. + # + # Reminder: Each execution of the vertex shader applies to a single particle. + # + # ## Input variables: + # * `center`: reference coordinates of the particle effect. + # This if often the center of the particle itself, + # but it can also be reference coordinates for a moving particle. + # * `mvp`: model-view-projection matrix. + # * `color`: color tint of the particle. + # + # * `t`: global seconds counter since the creation of this particle emitter. + # * `ot`: creation time of the particle, in seconds, in reference to `t`. + # * `dt`: seconds since creation of the particle. + # * `ttl`: time-to-live of the particle, in seconds. + # * `pt`: advancement of this particle in its lifetime, in `[0.0 .. 1.0]`. + # + # ## Output variables: + # * `gl_Position`: position of the particle in camera coordinates. + # * `gl_PointSize`: size of the particle in camera coordinates. + # Set to `0.0` to discard the particle. + # * `v_color`: tint applied to the particle. + # Assigned by default to the value of `color`. + # + # ## Reference implementation + # + # The default implementation apply the model-view-projection matrix on the position + # and scales according to the distance from the camera. + # Most particle effects should apply the same base logic as the default implementation. + # Here it is for reference: + # + # ~~~glsl + # gl_Position = center * mvp; + # gl_PointSize = scale / gl_Position.z; + # ~~~ + fun vertex_shader_core: String do return """ + gl_Position = center * mvp; + gl_PointSize = scale / gl_Position.z; + """ + + redef var fragment_shader_source = """ + precision mediump float; + + // Input from the vertex shader + varying vec4 v_color; + + // Does this particle use a texture? + uniform bool use_texture; + + // Texture to apply on this particle + uniform sampler2D texture; + + void main() + { + if (use_texture) { + gl_FragColor = texture2D(texture, gl_PointCoord) * v_color; + if (gl_FragColor.a <= 0.01) discard; + } else { + gl_FragColor = v_color; + } + } + """ @ glsl_fragment_shader + + # Coordinates of particle effects + var center = attributes["center"].as(AttributeVec4) is lazy + + # Should this program use the texture `texture`? + var use_texture = uniforms["use_texture"].as(UniformBool) is lazy + + # Visible texture unit + var texture = uniforms["texture"].as(UniformSampler2D) is lazy + + # Color tint per vertex + var color = attributes["color"].as(AttributeVec4) is lazy + + # Scaling per vertex + var scale = attributes["scale"].as(AttributeFloat) is lazy + + # Model view projection matrix + var mvp = uniforms["mvp"].as(UniformMat4) is lazy + + # Creation time of each particle + var ot = attributes["ot"].as(AttributeFloat) is lazy + + # Current time + var t = uniforms["t"].as(UniformFloat) is lazy + + # Time-to-live of each particle + var ttl = attributes["ttl"].as(AttributeFloat) is lazy +end + +# Graphics program to display blowing up particles +class ExplosionProgram + super ParticleProgram + + redef fun vertex_shader_core do return """ + gl_Position = center * mvp; + gl_PointSize = scale / gl_Position.z * pt; + + if (pt > 0.8) v_color.a = (1.0-pt)/0.2; + """ +end + +# Graphics program to display particles slowly drifting upwards +class SmokeProgram + super ParticleProgram + + redef fun vertex_shader_core do return """ + vec4 c = center; + c.y += dt * 1.0; + c.x += dt * 0.1; + + gl_Position = c * mvp; + gl_PointSize = scale / gl_Position.z * (pt+0.1); + + if (pt < 0.1) + v_color.a = pt / 0.1; + else + v_color.a = 1.0 - pt*0.9; + """ +end