X-Git-Url: http://nitlanguage.org diff --git a/lib/gamnit/depth/more_materials.nit b/lib/gamnit/depth/more_materials.nit index 983b0fc..6674c04 100644 --- a/lib/gamnit/depth/more_materials.nit +++ b/lib/gamnit/depth/more_materials.nit @@ -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 - program.translation.uniform(actor.center.x, -actor.center.y, actor.center.z, 0.0) + 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,14 +154,18 @@ 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 - var need_tex_coord = false + # One of the textures used, if any + var sample_used_texture = null var texture = ambient_texture if texture != null then @@ -110,7 +173,7 @@ class TexturedMaterial glBindTexture(gl_TEXTURE_2D, texture.gl_texture) program.use_map_ambient.uniform true program.map_ambient.uniform 0 - need_tex_coord = true + sample_used_texture = texture else program.use_map_ambient.uniform false end @@ -121,7 +184,7 @@ class TexturedMaterial glBindTexture(gl_TEXTURE_2D, texture.gl_texture) program.use_map_diffuse.uniform true program.map_diffuse.uniform 1 - need_tex_coord = true + sample_used_texture = texture else program.use_map_diffuse.uniform false end @@ -132,35 +195,79 @@ class TexturedMaterial glBindTexture(gl_TEXTURE_2D, texture.gl_texture) program.use_map_specular.uniform true program.map_specular.uniform 2 - need_tex_coord = true + sample_used_texture = texture else program.use_map_specular.uniform false end - program.translation.uniform(actor.center.x, -actor.center.y, actor.center.z, 0.0) - program.scale.uniform actor.scale + 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.tex_coord.array_enabled = need_tex_coord - program.tex_coord.array(mesh.texture_coords, 2) + 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 RootTexture then + # Coordinates are directly valid + program.tex_coord.array(mesh.texture_coords, 2) + else + # Correlate texture coordinates from the substexture and the mesh. + # This is slow, but should be cached on the GPU. + var xa = sample_used_texture.offset_left + var xd = sample_used_texture.offset_right - xa + var ya = sample_used_texture.offset_top + var yd = sample_used_texture.offset_bottom - ya + + 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] = 1.0 - (ya + yd * mesh.texture_coords[i*2+1]) + end + + program.tex_coord.array(tex_coords, 2) + end + end 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 @@ -172,17 +279,17 @@ 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 # TODO apply normal map - program.translation.uniform(actor.center.x, -actor.center.y, actor.center.z, 0.0) + program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0) program.scale.uniform actor.scale program.tex_coord.array_enabled = true @@ -190,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 = """ @@ -212,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; @@ -223,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; @@ -237,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 @@ -260,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; @@ -288,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 @@ -318,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`? @@ -327,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 @@ -342,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; @@ -379,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