gamnit: leave it up to the clients to set premultiplied colors in Materials
[nit.git] / lib / gamnit / depth / more_materials.nit
index aa9600c..3b320f9 100644 (file)
@@ -18,23 +18,31 @@ module more_materials
 intrude import depth_core
 intrude import flat
 
-# 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
+       #
+       # 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
+       #
+       # The RGB values should be premultiplied by the alpha value.
        var diffuse_color: Array[Float] is writable
 
        # Specular color affecting reflections
+       #
+       # The RGB values should be premultiplied by the alpha value.
        var specular_color: Array[Float] is writable
 
        redef fun draw(actor, model)
@@ -47,7 +55,7 @@ class SmoothMaterial
                # Actor specs
                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.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
 
                # From mesh
                program.coord.array_enabled = true
@@ -69,15 +77,19 @@ class SmoothMaterial
                program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_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)
+               var a = actor.alpha
+               program.ambient_color.uniform(ambient_color[0]*a, ambient_color[1]*a,
+                                             ambient_color[2]*a, ambient_color[3]*a)
+               program.diffuse_color.uniform(diffuse_color[0]*a, diffuse_color[1]*a,
+                                             diffuse_color[2]*a, diffuse_color[3]*a)
+               program.specular_color.uniform(specular_color[0]*a, specular_color[1]*a,
+                                              specular_color[2]*a, specular_color[3]*a)
 
                # 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
        end
 end
@@ -95,6 +107,9 @@ class TexturedMaterial
        # Texture applied to the specular color
        var specular_texture: nullable Texture = null is writable
 
+       # Bump map TODO
+       private var normals_texture: nullable Texture = null is writable
+
        redef fun draw(actor, model)
        do
                var mesh = model.mesh
@@ -138,13 +153,24 @@ 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
+
                program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
                program.scale.uniform actor.scale
 
                # 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
@@ -154,11 +180,13 @@ class TexturedMaterial
                                var xd = sample_used_texture.offset_right - xa
                                var ya = sample_used_texture.offset_top
                                var yd = sample_used_texture.offset_bottom - ya
+                               xd *= 0.999
+                               yd *= 0.999
 
                                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,11 +195,16 @@ 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.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
+
+               var a = actor.alpha
+               program.ambient_color.uniform(ambient_color[0]*a, ambient_color[1]*a,
+                                             ambient_color[2]*a, ambient_color[3]*a)
+               program.diffuse_color.uniform(diffuse_color[0]*a, diffuse_color[1]*a,
+                                             diffuse_color[2]*a, diffuse_color[3]*a)
+               program.specular_color.uniform(specular_color[0]*a, specular_color[1]*a,
+                                              specular_color[2]*a, specular_color[3]*a)
 
                program.normal.array_enabled = true
                program.normal.array(mesh.normals, 3)
@@ -180,9 +213,9 @@ class TexturedMaterial
                program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_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
@@ -212,21 +245,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.uniform 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 = """
@@ -259,20 +293,19 @@ 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;
 
                void main()
                {
+                       vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
+                       gl_Position = pos * mvp;
+
                        // 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_light = normalize(vec4(light_center, 1.0) - pos);
+                       v_to_camera = normalize(vec4(camera, 1.0) - pos);
                }
                """ @ glsl_vertex_shader
 
@@ -282,8 +315,8 @@ 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;
 
                // Colors
                uniform vec4 ambient_color;
@@ -312,19 +345,39 @@ class LambertProgram
 
                void main()
                {
-                       // Lambert diffusion
-                       vec3 light_dir = normalize(v_light_center.xyz);
-                       float lambert = max(dot(light_dir, v_normal), 0.0);
-
-                       if (use_map_ambient)
-                               gl_FragColor = ambient_color * texture2D(map_ambient, v_tex_coord);
-                       else
-                               gl_FragColor = ambient_color;
-
-                       if (use_map_diffuse)
-                               gl_FragColor += lambert * diffuse_color * texture2D(map_diffuse, v_tex_coord);
-                       else
-                               gl_FragColor += lambert * diffuse_color;
+                       // Normal
+                       vec3 normal = v_normal;
+                       if (use_map_bump) {
+                               // TODO
+                               vec3 bump = 2.0 * texture2D(map_bump, v_tex_coord).rgb - 1.0;
+                       }
+
+                       // Ambient light
+                       vec4 ambient = ambient_color;
+                       if (use_map_ambient) ambient *= texture2D(map_ambient, v_tex_coord);
+
+                       // 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) {
+                               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
+                       }
+
+                       vec4 specular = s * specular_color;
+                       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
                }
                """ @ glsl_fragment_shader
 
@@ -349,6 +402,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
 
@@ -384,8 +443,10 @@ class LambertProgram
 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 +462,7 @@ class NormalProgram
 end
 
 redef class App
-       private var versatile_program = new LambertProgram is lazy
+       private var versatile_program = new BlinnPhongProgram is lazy
 
        private var normals_program = new NormalProgram is lazy
 end