Merge: gamnit: new services and a lot of bug fixes and performance improvements
[nit.git] / lib / gamnit / depth / more_materials.nit
index 27bdd9a..6674c04 100644 (file)
@@ -17,37 +17,52 @@ module more_materials
 
 intrude import depth_core
 intrude import flat
+intrude import shadow
+import more_lights
 
-# Simple material with static colors used for debugging or display abstract objects
-class SmoothMaterial
-       super Material
-
+redef class Material
        # Get the default blueish material
-       init default do init(
+       new do return new SmoothMaterial(
                [0.0, 0.0, 0.3, 1.0],
                [0.0, 0.0, 0.6, 1.0],
                [1.0, 1.0, 1.0, 1.0])
+end
+
+# Simple material with static colors
+class SmoothMaterial
+       super Material
 
        # Ambient color, always visible
-       var ambient_color: Array[Float]
+       #
+       # The RGB values should be premultiplied by the alpha value.
+       var ambient_color: Array[Float] is writable
 
        # Diffuse color when covered by a light source
-       var diffuse_color: Array[Float]
+       #
+       # The RGB values should be premultiplied by the alpha value.
+       var diffuse_color: Array[Float] is writable
 
        # Specular color affecting reflections
-       var specular_color: Array[Float]
+       #
+       # The RGB values should be premultiplied by the alpha value.
+       var specular_color: Array[Float] is writable
 
-       redef fun draw(actor, model)
+       redef fun draw(actor, model, camera)
        do
-               var program = app.versatile_program
+               var program = app.blinn_phong_program
                program.use
+               program.mvp.uniform camera.mvp_matrix
 
                var mesh = model.mesh
 
                # Actor specs
+               glDisableVertexAttribArray program.translation.location
+               glDisableVertexAttribArray program.scale.location
+
                program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
                program.scale.uniform actor.scale
-               program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0)
+               program.alpha.uniform actor.alpha
+               program.rotation = new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
 
                # From mesh
                program.coord.array_enabled = true
@@ -62,23 +77,67 @@ class SmoothMaterial
                program.use_map_specular.uniform false
                program.tex_coord.array_enabled = false
 
-               # Lights
-               program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
-
                # Camera
-               program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_camera.position.z)
+               program.camera.uniform(camera.position.x, camera.position.y, camera.position.z)
 
                # Colors from the material
-               program.ambient_color.uniform(ambient_color[0], ambient_color[1], ambient_color[2], ambient_color[3]*actor.alpha)
-               program.diffuse_color.uniform(diffuse_color[0], diffuse_color[1], diffuse_color[2], diffuse_color[3]*actor.alpha)
-               program.specular_color.uniform(specular_color[0], specular_color[1], specular_color[2], specular_color[3]*actor.alpha)
+               program.ambient_color.uniform(ambient_color[0], ambient_color[1],
+                                             ambient_color[2], ambient_color[3])
+               program.diffuse_color.uniform(diffuse_color[0], diffuse_color[1],
+                                             diffuse_color[2], diffuse_color[3])
+               program.specular_color.uniform(specular_color[0], specular_color[1],
+                                              specular_color[2], specular_color[3])
+
+               setup_lights(camera, program)
 
                # Execute draw
                if mesh.indices.is_empty then
-                       glDrawArrays(gl_TRIANGLES, 0, mesh.vertices.length/3)
+                       glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
                else
-                       glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+                       glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
                end
+
+               assert glGetError == gl_NO_ERROR
+       end
+
+       private fun setup_lights(camera: Camera, program: BlinnPhongProgram)
+       do
+               # TODO use a list of lights
+
+               # Light, for Lambert and Blinn-Phong
+               var light = app.light
+               if light isa ParallelLight then
+                       program.light_kind.uniform 1
+
+                       # Vector parallel to the light source
+                       program.light_center.uniform(
+                               -light.pitch.sin * light.yaw.sin,
+                               light.pitch.cos,
+                               -light.yaw.cos)
+               else if light isa PointLight then
+                       program.light_kind.uniform 2
+
+                       # Position of the light source
+                       program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
+               else
+                       program.light_kind.uniform 0
+               end
+
+               # Draw projected shadows?
+               if not light isa LightCastingShadows or not app.shadow_depth_texture_available then
+                       program.use_shadows.uniform false
+                       return
+               else program.use_shadows.uniform true
+
+               # Light point of view
+               program.light_mvp.uniform light.camera.mvp_matrix
+
+               # Depth texture
+               glActiveTexture gl_TEXTURE4
+               glBindTexture(gl_TEXTURE_2D, app.shadow_context.depth_texture)
+               program.depth_texture.uniform 4
+               program.depth_texture_size.uniform app.shadow_resolution.to_f
+               program.depth_texture_taps.uniform 2 # TODO make configurable
        end
 end
 
@@ -95,11 +154,14 @@ class TexturedMaterial
        # Texture applied to the specular color
        var specular_texture: nullable Texture = null is writable
 
-       redef fun draw(actor, model)
+       # Bump map TODO
+       private var normals_texture: nullable Texture = null is writable
+
+       redef fun draw(actor, model, camera)
        do
                var mesh = model.mesh
 
-               var program = app.versatile_program
+               var program = app.blinn_phong_program
                program.use
 
                # One of the textures used, if any
@@ -138,13 +200,29 @@ class TexturedMaterial
                        program.use_map_specular.uniform false
                end
 
+               texture = normals_texture
+               if texture != null then
+                       glActiveTexture gl_TEXTURE3
+                       glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
+                       program.use_map_bump.uniform true
+                       program.map_bump.uniform 3
+                       sample_used_texture = texture
+               else
+                       program.use_map_bump.uniform false
+               end
+
+               glDisableVertexAttribArray program.translation.location
+               glDisableVertexAttribArray program.scale.location
+
+               program.mvp.uniform camera.mvp_matrix
                program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
                program.scale.uniform actor.scale
+               program.alpha.uniform actor.alpha
 
                # If using a texture, set `texture_coords`
                program.tex_coord.array_enabled = sample_used_texture != null
                if sample_used_texture != null then
-                       if sample_used_texture isa GamnitRootTexture then
+                       if sample_used_texture isa RootTexture then
                                # Coordinates are directly valid
                                program.tex_coord.array(mesh.texture_coords, 2)
                        else
@@ -158,7 +236,7 @@ class TexturedMaterial
                                var tex_coords = new Array[Float].with_capacity(mesh.texture_coords.length)
                                for i in [0..mesh.texture_coords.length/2[ do
                                        tex_coords[i*2]   = xa + xd * mesh.texture_coords[i*2]
-                                       tex_coords[i*2+1] = ya + yd * mesh.texture_coords[i*2+1]
+                                       tex_coords[i*2+1] = 1.0 - (ya + yd * mesh.texture_coords[i*2+1])
                                end
 
                                program.tex_coord.array(tex_coords, 2)
@@ -167,22 +245,29 @@ class TexturedMaterial
 
                program.coord.array_enabled = true
                program.coord.array(mesh.vertices, 3)
-               program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0)
 
-               program.ambient_color.uniform(ambient_color[0], ambient_color[1], ambient_color[2], ambient_color[3]*actor.alpha)
-               program.diffuse_color.uniform(diffuse_color[0], diffuse_color[1], diffuse_color[2], diffuse_color[3]*actor.alpha)
-               program.specular_color.uniform(specular_color[0], specular_color[1], specular_color[2], specular_color[3]*actor.alpha)
+               program.rotation = new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
+
+               program.ambient_color.uniform(ambient_color[0], ambient_color[1],
+                                             ambient_color[2], ambient_color[3])
+               program.diffuse_color.uniform(diffuse_color[0], diffuse_color[1],
+                                             diffuse_color[2], diffuse_color[3])
+               program.specular_color.uniform(specular_color[0], specular_color[1],
+                                              specular_color[2], specular_color[3])
 
                program.normal.array_enabled = true
                program.normal.array(mesh.normals, 3)
 
-               program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
-               program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_camera.position.z)
+               # Light
+               setup_lights(camera, program)
+
+               # Camera
+               program.camera.uniform(camera.position.x, camera.position.y, camera.position.z)
 
                if mesh.indices.is_empty then
-                       glDrawArrays(gl_TRIANGLES, 0, mesh.vertices.length/3)
+                       glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
                else
-                       glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+                       glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
                end
        end
 end
@@ -194,11 +279,11 @@ end
 class NormalsMaterial
        super Material
 
-       redef fun draw(actor, model)
+       redef fun draw(actor, model, camera)
        do
                var program = app.normals_program
                program.use
-               program.mvp.uniform app.world_camera.mvp_matrix
+               program.mvp.uniform camera.mvp_matrix
 
                var mesh = model.mesh
 
@@ -212,21 +297,22 @@ class NormalsMaterial
 
                program.coord.array_enabled = true
                program.coord.array(mesh.vertices, 3)
-               program.rotation.uniform new Matrix.rotation(actor.rotation, 0.0, 1.0, 0.0)
+
+               program.rotation = new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
 
                program.normal.array_enabled = true
                program.normal.array(mesh.normals, 3)
 
                if mesh.indices.is_empty then
-                       glDrawArrays(gl_TRIANGLES, 0, mesh.vertices.length/3)
+                       glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
                else
-                       glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
+                       glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
                end
        end
 end
 
-# Graphic program to display 3D models with Lamber diffuse lighting
-class LambertProgram
+# Graphic program to display 3D models with Blinn-Phong specular lighting
+class BlinnPhongProgram
        super GamnitProgramFromSource
 
        redef var vertex_shader_source = """
@@ -234,10 +320,12 @@ class LambertProgram
                attribute vec4 coord;
 
                // Vertex translation
-               uniform vec4 translation;
+               attribute vec4 translation;
 
                // Vertex scaling
-               uniform float scale;
+               attribute float scale;
+
+               attribute float alpha;
 
                // Vertex coordinates on textures
                attribute vec2 tex_coord;
@@ -245,13 +333,24 @@ class LambertProgram
                // Vertex normal
                attribute vec3 normal;
 
-               // Model view projection matrix
+               // Camera model view projection matrix
                uniform mat4 mvp;
 
-               uniform mat4 rotation;
+               // Actor rotation
+               attribute vec4 rotation_row0;
+               attribute vec4 rotation_row1;
+               attribute vec4 rotation_row2;
+               attribute vec4 rotation_row3;
+
+               mat4 rotation()
+               {
+                       return mat4(rotation_row0, rotation_row1, rotation_row2, rotation_row3);
+               }
 
                // Lights config
+               uniform lowp int light_kind;
                uniform vec3 light_center;
+               uniform mat4 light_mvp;
 
                // Coordinates of the camera
                uniform vec3 camera;
@@ -259,20 +358,34 @@ class LambertProgram
                // Output for the fragment shader
                varying vec2 v_tex_coord;
                varying vec3 v_normal;
-               varying vec4 v_light_center;
-               varying vec4 v_camera;
+               varying vec4 v_to_light;
+               varying vec4 v_to_camera;
+               varying vec4 v_depth_pos;
+               varying float v_alpha;
 
                void main()
                {
+                       mat4 rotation = rotation();
+                       vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
+                       gl_Position = pos * mvp;
+                       v_depth_pos = (pos * light_mvp) * 0.5 + 0.5;
+
                        // Pass varyings to the fragment shader
                        v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
-                       v_normal = normalize(vec4(normal, 0.0) * rotation * mvp).xyz;
-
-                       gl_Position = (vec4(coord.xyz * scale, 1.0) * rotation + translation) * mvp;
-
-                       // TODO compute v_light_center and v_camera on the CPU side and pass as uniforms
-                       v_light_center = vec4(light_center, 0.0) * mvp;
-                       v_camera = vec4(camera, 0.0) * mvp;
+                       v_normal = normalize(vec4(normal, 0.0) * rotation).xyz;
+                       v_to_camera = normalize(vec4(camera, 1.0) - pos);
+
+                       if (light_kind == 0) {
+                               // No light
+                       } else if (light_kind == 1) {
+                               // Parallel
+                               v_to_light = normalize(vec4(light_center, 1.0));
+                       } else {
+                               // Point light (and others?)
+                               v_to_light = normalize(vec4(light_center, 1.0) - pos);
+                       }
+
+                       v_alpha = alpha;
                }
                """ @ glsl_vertex_shader
 
@@ -282,8 +395,10 @@ class LambertProgram
                // Input from the vertex shader
                varying vec2 v_tex_coord;
                varying vec3 v_normal;
-               varying vec4 v_light_center;
-               varying vec4 v_camera;
+               varying vec4 v_to_light;
+               varying vec4 v_to_camera;
+               varying vec4 v_depth_pos;
+               varying float v_alpha;
 
                // Colors
                uniform vec4 ambient_color;
@@ -310,21 +425,111 @@ class LambertProgram
                uniform bool use_map_normal;
                uniform sampler2D map_normal;
 
+               // Shadow
+               uniform lowp int light_kind;
+               uniform bool use_shadows;
+               uniform sampler2D depth_texture;
+               uniform float depth_size;
+               uniform int depth_taps;
+
+               // Shadow effect on the diffuse colors of the fragment at offset `x, y`
+               float shadow_lookup(vec2 depth_coord, float x, float y) {
+                       float tap_width = 1.0;
+                       float pixel_size = tap_width/depth_size;
+
+                       vec2 offset = vec2(x * pixel_size * v_depth_pos.w,
+                                          y * pixel_size * v_depth_pos.w);
+                       depth_coord += offset;
+
+                       float depth = v_depth_pos.z/v_depth_pos.w;
+                       //vec2 depth_coord = v_depth_pos.xy/v_depth_pos.w;
+                       if (depth_coord.x < 0.0 || depth_coord.x > 1.0 || depth_coord.y < 0.0 || depth_coord.y > 1.0) {
+                               // Out of the shadow map texture
+                               //gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // debug, red out of the light view
+                               return 1.0;
+                       }
+
+                       float shadow_depth = texture2D(depth_texture, depth_coord).r;
+                       float bias = 0.0001;
+                       if (shadow_depth == 1.0) {
+                               // Too far to be in depth texture
+                               return 1.0;
+                       } else if (shadow_depth <= depth - bias) {
+                               // In a shadow
+                               //gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); // debug, blue shadows
+                               return 0.2; // TODO replace with a configurable ambient light
+                       }
+
+                       //gl_FragColor = vec4(0.0, 1.0-(shadow_depth-depth), 0.0, 1.0); // debug, green lit surfaces
+                       return 1.0;
+               }
+
+               // Shadow effect on the diffuse colors of the fragment
+               float shadow() {
+                       if (!use_shadows) return 1.0;
+
+                       vec2 depth_coord = v_depth_pos.xy/v_depth_pos.w;
+
+                       float taps = float(depth_taps);
+                       float tap_step = 2.00/taps;
+                       float sum = 0.0;
+                       for (float x = -1.0; x <= 0.99; x += tap_step)
+                               for (float y = -1.0; y <= 0.99; y += tap_step)
+                                       sum += shadow_lookup(depth_coord, x, y);
+                       return sum / taps / taps;
+               }
+
                void main()
                {
-                       // Lambert diffusion
-                       vec3 light_dir = normalize(v_light_center.xyz);
-                       float lambert = max(dot(light_dir, v_normal), 0.0);
+                       // Normal
+                       vec3 normal = v_normal;
+                       if (use_map_bump) {
+                               // TODO
+                               vec3 bump = 2.0 * texture2D(map_bump, v_tex_coord).rgb - 1.0;
+                       }
 
-                       if (use_map_ambient)
-                               gl_FragColor = ambient_color + texture2D(map_ambient, v_tex_coord);
-                       else
-                               gl_FragColor = ambient_color;
+                       // Ambient light
+                       vec4 ambient = ambient_color * v_alpha;
+                       if (use_map_ambient) ambient *= texture2D(map_ambient, v_tex_coord);
 
-                       if (use_map_diffuse)
-                               gl_FragColor += lambert * diffuse_color * texture2D(map_diffuse, v_tex_coord);
-                       else
-                               gl_FragColor += lambert * diffuse_color;
+                       if (light_kind == 0) {
+                               // No light, show diffuse and ambient
+
+                               vec4 diffuse = diffuse_color * v_alpha;
+                               if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
+
+                               gl_FragColor = ambient + diffuse;
+                       } else {
+                               // Parallel light or point light (1 or 2)
+
+                               // Diffuse Lambert light
+                               vec3 to_light = v_to_light.xyz;
+                               float lambert = clamp(dot(normal, to_light), 0.0, 1.0);
+
+                               vec4 diffuse = lambert * diffuse_color;
+                               if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
+
+                               // Specular Phong light
+                               float s = 0.0;
+                               if (lambert > 0.0) {
+                                       // In light
+                                       vec3 l = reflect(-to_light, normal);
+                                       s = clamp(dot(l, v_to_camera.xyz), 0.0, 1.0);
+                                       s = pow(s, 8.0); // TODO make this `shininess` a material attribute
+
+                                       // Shadows
+                                       diffuse *= shadow();
+                               }
+
+                               vec4 specular = s * specular_color * v_alpha;
+                               if (use_map_specular) specular *= texture2D(map_specular, v_tex_coord).x;
+
+                               gl_FragColor = ambient + diffuse + specular;
+                       }
+
+                       if (gl_FragColor.a < 0.01) discard;
+
+                       //gl_FragColor = vec4(normalize(normal).rgb, 1.0); // Debug normals
                }
                """ @ glsl_fragment_shader
 
@@ -340,7 +545,7 @@ class LambertProgram
        # Should this program use the texture `map_diffuse`?
        var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy
 
-       # Diffuser texture unit
+       # Diffuse texture unit
        var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy
 
        # Should this program use the texture `map_specular`?
@@ -349,6 +554,12 @@ class LambertProgram
        # Specularity texture unit
        var map_specular = uniforms["map_specular"].as(UniformSampler2D) is lazy
 
+       # Should this program use the texture `map_bump`?
+       var use_map_bump = uniforms["use_map_bump"].as(UniformBool) is lazy
+
+       # Bump texture unit
+       var map_bump = uniforms["map_bump"].as(UniformSampler2D) is lazy
+
        # Normal per vertex
        var normal = attributes["normal"].as(AttributeVec3) is lazy
 
@@ -364,28 +575,75 @@ class LambertProgram
        # Specular color
        var specular_color = uniforms["specular_color"].as(UniformVec4) is lazy
 
-       # Center position of the light
+       # Kind of lights: 0 -> no light, 1 -> parallel, 2 -> point
+       var light_kind = uniforms["light_kind"].as(UniformInt) is lazy
+
+       # Center position of the light *or* vector to parallel light source
        var light_center = uniforms["light_center"].as(UniformVec3) is lazy
 
+       # Light model view projection matrix
+       var light_mvp = uniforms["light_mvp"].as(UniformMat4) is lazy
+
+       # Should shadow be drawn? Would use `depth_texture` and `light_mvp`.
+       var use_shadows = uniforms["use_shadows"].as(UniformBool) is lazy
+
+       # Diffuse texture unit
+       var depth_texture = uniforms["depth_texture"].as(UniformSampler2D) is lazy
+
+       # Size, in pixels, of `depth_texture`
+       var depth_texture_size = uniforms["depth_size"].as(UniformFloat) is lazy
+
+       # Times to tap the `depth_texture`, square root (set to 3 for a total of 9 taps)
+       var depth_texture_taps = uniforms["depth_taps"].as(UniformInt) is lazy
+
        # Camera position
        var camera = uniforms["camera"].as(UniformVec3) is lazy
 
        # Translation applied to each vertex
-       var translation = uniforms["translation"].as(UniformVec4) is lazy
+       var translation = attributes["translation"].as(AttributeVec4) is lazy # TODO attribute
+
+       # Set `mat` at the uniform rotation matrix
+       fun rotation=(mat: Matrix)
+       do
+               var i = 0
+               for r in [rotation_row0, rotation_row1, rotation_row2, rotation_row3] do
+                       if r.is_active then
+                               glDisableVertexAttribArray r.location
+                               r.uniform(mat[0, i], mat[1, i], mat[2, i], mat[3, i])
+                       end
+                       i += 1
+               end
+               var gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+       end
+
+       # Rotation matrix, row0
+       var rotation_row0 = attributes["rotation_row0"].as(AttributeVec4) is lazy
+
+       # Rotation matrix, row 1
+       var rotation_row1 = attributes["rotation_row1"].as(AttributeVec4) is lazy
 
-       # Rotation matrix
-       var rotation = uniforms["rotation"].as(UniformMat4) is lazy
+       # Rotation matrix, row 2
+       var rotation_row2 = attributes["rotation_row2"].as(AttributeVec4) is lazy
+
+       # Rotation matrix, row 3
+       var rotation_row3 = attributes["rotation_row3"].as(AttributeVec4) is lazy
+
+       # Scaling per vertex
+       var scale = attributes["scale"].as(AttributeFloat) is lazy
 
        # Scaling per vertex
-       var scale = uniforms["scale"].as(UniformFloat) is lazy
+       var alpha = attributes["alpha"].as(AttributeFloat) is lazy
 
-       # Model view projection matrix
+       # Camera model view projection matrix
        var mvp = uniforms["mvp"].as(UniformMat4) is lazy
 end
 
 # Program to color objects from their normal vectors
+#
+# May be used in place of `BlinnPhongProgram` for debugging or effect.
 class NormalProgram
-       super LambertProgram
+       super BlinnPhongProgram
 
        redef var fragment_shader_source = """
                precision mediump float;
@@ -401,7 +659,7 @@ class NormalProgram
 end
 
 redef class App
-       private var versatile_program = new LambertProgram is lazy
+       private var blinn_phong_program = new BlinnPhongProgram is lazy
 
        private var normals_program = new NormalProgram is lazy
 end