From: Alexis Laferrière Date: Sat, 19 Aug 2017 18:57:19 +0000 (-0400) Subject: gamnit: intro basic shadow mapping X-Git-Url: http://nitlanguage.org gamnit: intro basic shadow mapping Signed-off-by: Alexis Laferrière --- diff --git a/lib/gamnit/depth/depth.nit b/lib/gamnit/depth/depth.nit index 1fe507a..1230c4a 100644 --- a/lib/gamnit/depth/depth.nit +++ b/lib/gamnit/depth/depth.nit @@ -21,6 +21,7 @@ import more_models import model_dimensions import particles import selection +import shadow redef class App @@ -49,10 +50,18 @@ redef class App # Draw all elements of `actors` and then call `frame_core_flat` protected fun frame_core_depth(display: GamnitDisplay) do + frame_core_depth_clock.lapse + + # Compute shadows + if light isa LightCastingShadows then + frame_core_shadow_prep display + perfs["gamnit depth shadows"].add frame_core_depth_clock.lapse + end + glViewport(0, 0, display.width, display.height) frame_core_dynamic_resolution_before display + perfs["gamnit depth dynres"].add frame_core_depth_clock.lapse - frame_core_depth_clock.lapse for actor in actors do for leaf in actor.model.leaves do leaf.material.draw(actor, leaf, app.world_camera) @@ -73,6 +82,9 @@ redef class App perfs["gamnit depth ui_sprites"].add frame_core_depth_clock.lapse frame_core_dynamic_resolution_after display + + # Debug, show the light point of view + #frame_core_shadow_debug display end private var frame_core_depth_clock = new Clock diff --git a/lib/gamnit/depth/depth_core.nit b/lib/gamnit/depth/depth_core.nit index 67ac1c0..56e0575 100644 --- a/lib/gamnit/depth/depth_core.nit +++ b/lib/gamnit/depth/depth_core.nit @@ -203,23 +203,32 @@ class Mesh end # Source of light -# -# Instances of this class define a light source position and type. -class Light - - # TODO point light, spotlight, directional light, etc. +abstract class Light # Center of this light source in world coordinates var position = new Point3d[Float](0.0, 1000.0, 0.0) end +# Basic light projected from a single point +class PointLight + super Light +end + +# Source of light casting shadows +abstract class LightCastingShadows + super Light + + # View from the camera, used for shadow mapping, implemented by sub-classes + fun camera: Camera is abstract +end + redef class App # Live actors to be drawn on screen var actors = new Array[Actor] # Single light of the scene - var light = new Light + var light: Light = new PointLight is writable # TODO move `actors & light` to a scene object # TODO support more than 1 light diff --git a/lib/gamnit/depth/more_lights.nit b/lib/gamnit/depth/more_lights.nit new file mode 100644 index 0000000..7637e02 --- /dev/null +++ b/lib/gamnit/depth/more_lights.nit @@ -0,0 +1,75 @@ +# 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. + +# More implementations of `Light` +module more_lights + +import depth_core + +# TODO +#class PointLight +#class Spotlight + +# Sun-like light projecting parallel rays +class ParallelLight + super LightCastingShadows + + # Angle to the light source, around the X axis + var pitch = 0.0 is writable + + # Angle to the light source, around the Y axis + var yaw = 0.0 is writable + + # Depth texture width, in world coordinates + var width = 100.0 is writable + + # Depth texture height, in world coordinates + var height = 100.0 is writable + + # Viewport depth, centered on `app.world_camera` + var depth = 500.0 is writable + + redef var camera = new ParallelLightCamera(app.display.as(not null), self) is lazy +end + +private class ParallelLightCamera + super Camera + + var light: ParallelLight + + # Rotation matrix produced by the current rotation of the camera + fun rotation_matrix: Matrix + do + var view = new Matrix.identity(4) + view.rotate(light.yaw, 0.0, 1.0, 0.0) + view.rotate(light.pitch, 1.0, 0.0, 0.0) + return view + end + + redef fun mvp_matrix + do + # TODO cache + + var near = -light.depth/2.0 + var far = light.depth/2.0 + + var view = new Matrix.identity(4) + view.translate(-position.x, -position.y, -position.z) + view = view * rotation_matrix + var projection = new Matrix.orthogonal(-light.width/2.0, light.width/2.0, + -light.height/2.0, light.height/2.0, + near, far) + return view * projection + end +end diff --git a/lib/gamnit/depth/more_materials.nit b/lib/gamnit/depth/more_materials.nit index 9843b37..d153954 100644 --- a/lib/gamnit/depth/more_materials.nit +++ b/lib/gamnit/depth/more_materials.nit @@ -17,6 +17,8 @@ module more_materials intrude import depth_core intrude import flat +intrude import shadow +import more_lights redef class Material # Get the default blueish material @@ -71,9 +73,6 @@ 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(camera.position.x, camera.position.y, camera.position.z) @@ -86,6 +85,8 @@ class SmoothMaterial program.specular_color.uniform(specular_color[0]*a, specular_color[1]*a, specular_color[2]*a, specular_color[3]*a) + setup_lights(actor, model, camera, program) + # Execute draw if mesh.indices.is_empty then glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3) @@ -93,6 +94,46 @@ class SmoothMaterial glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array) end end + + private fun setup_lights(actor: Actor, model: LeafModel, 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 # Material with potential `diffuse_texture` and `specular_texture` @@ -182,8 +223,6 @@ 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 @@ -211,7 +250,8 @@ class TexturedMaterial 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) + # Light + setup_lights(actor, model, camera, program) # Camera program.camera.uniform(camera.position.x, camera.position.y, camera.position.z) @@ -283,13 +323,15 @@ class BlinnPhongProgram // Vertex normal attribute vec3 normal; - // Model view projection matrix + // Camera model view projection matrix uniform mat4 mvp; uniform mat4 rotation; // Lights config + uniform int light_kind; uniform vec3 light_center; + uniform mat4 light_mvp; // Coordinates of the camera uniform vec3 camera; @@ -299,17 +341,28 @@ class BlinnPhongProgram varying vec3 v_normal; varying vec4 v_to_light; varying vec4 v_to_camera; + varying vec4 v_depth_pos; void main() { 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).xyz; - v_to_light = normalize(vec4(light_center, 1.0) - pos); 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); + } } """ @ glsl_vertex_shader @@ -321,6 +374,7 @@ class BlinnPhongProgram varying vec3 v_normal; varying vec4 v_to_light; varying vec4 v_to_camera; + varying vec4 v_depth_pos; // Colors uniform vec4 ambient_color; @@ -347,6 +401,60 @@ class BlinnPhongProgram uniform bool use_map_normal; uniform sampler2D map_normal; + // Shadow + uniform int light_kind; + uniform bool use_shadows; + uniform sampler2D depth_texture; + uniform float depth_texture_size; + uniform int depth_texture_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_texture_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_texture_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() { // Normal @@ -360,28 +468,44 @@ class BlinnPhongProgram 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); + if (light_kind == 0) { + // No light, show diffuse and ambient - vec4 diffuse = lambert * diffuse_color; - if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord); + vec4 diffuse = 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 - } + 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 specular = s * specular_color; - if (use_map_specular) specular *= texture2D(map_specular, v_tex_coord).x; + 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; + if (use_map_specular) specular *= texture2D(map_specular, v_tex_coord).x; + + gl_FragColor = ambient + diffuse + specular; + } - gl_FragColor = ambient + diffuse + specular; if (gl_FragColor.a < 0.01) discard; - //gl_FragColor = vec4(normalize(normal).rgb, 1.0); // Debug + //gl_FragColor = vec4(normalize(normal).rgb, 1.0); // Debug normals } """ @ glsl_fragment_shader @@ -427,9 +551,27 @@ class BlinnPhongProgram # 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_texture_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_texture_taps"].as(UniformInt) is lazy + # Camera position var camera = uniforms["camera"].as(UniformVec3) is lazy @@ -442,7 +584,7 @@ class BlinnPhongProgram # Scaling per vertex var scale = uniforms["scale"].as(UniformFloat) is lazy - # Model view projection matrix + # Camera model view projection matrix var mvp = uniforms["mvp"].as(UniformMat4) is lazy end diff --git a/lib/gamnit/depth/shadow.nit b/lib/gamnit/depth/shadow.nit new file mode 100644 index 0000000..213b461 --- /dev/null +++ b/lib/gamnit/depth/shadow.nit @@ -0,0 +1,477 @@ +# 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. + +# Shadow mapping using a depth texture +# +# The default light does not cast any shadows. It can be changed to a +# `ParallelLight` in client games to cast sun-like shadows: +# +# ~~~ +# import more_lights +# +# var sun = new ParallelLight +# sun.pitch = 0.25*pi +# sun.yaw = 0.25*pi +# app.light = sun +# ~~~ +module shadow + +intrude import gamnit::depth_core + +redef class App + + # Resolution of the shadow texture, defaults to 4096 pixels + # + # TODO make configurable / ask the hardware for gl_MAX_TEXTURE_SIZE + var shadow_resolution = 4096 + + # Are shadows supported by the current hardware configuration? + # + # The implementation may change in the future, but it currently relies on + # the GL extension `GL_EOS_depth_texture`. + var supports_shadows: Bool is lazy do + return display.as(not null).gl_extensions.has("GL_OES_depth_texture") + end + + # Is `shadow_context.depth_texture` ready to be used? + fun shadow_depth_texture_available: Bool + do return supports_shadows and shadow_context.depth_texture != -1 + + private var shadow_depth_program = new ShadowDepthProgram + + private var perf_clock_shadow = new Clock is lazy + + redef fun on_create + do + super + + var program = shadow_depth_program + program.compile_and_link + var error = program.error + assert error == null else print_error error + end + + private var shadow_context: ShadowContext = create_shadow_context is lazy + + private fun create_shadow_context: ShadowContext + do + var display = display + assert display != null + + var context = new ShadowContext + context.prepare_once(display, shadow_resolution) + return context + end + + # Update the depth texture from the light point of view + # + # This method updates `shadow_context.depth_texture`. + protected fun frame_core_shadow_prep(display: GamnitDisplay) + do + if not supports_shadows then return + + var light = app.light + if not light isa LightCastingShadows then return + + perf_clock_shadow.lapse + + # Make sure there's no errors pending + assert glGetError == gl_NO_ERROR + + # Bind the framebuffer and make sure it is OK + glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.light_view_framebuffer) + assert glGetError == gl_NO_ERROR + assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE + + # Draw to fill the depth texture and only the depth + glViewport(0, 0, shadow_resolution, shadow_resolution) + glColorMask(false, false, false, false) + glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT + assert glGetError == gl_NO_ERROR + + # Update light position + var camera = light.camera + camera.position.x = app.world_camera.position.x + camera.position.y = app.world_camera.position.y + camera.position.z = app.world_camera.position.z + + # Draw all actors + for actor in actors do + for leaf in actor.model.leaves do + leaf.material.draw_depth(actor, leaf, camera) + end + end + + # Take down, bring back default values + glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.screen_framebuffer) + glColorMask(true, true, true, true) + + perfs["gamnit shadows prep"].add perf_clock_shadow.lapse + end + + # --- + # Debug: show light view in the bottom left of the screen + + # Lazy load the debugging program + private var shadow_debug_program: LightPointOfViewProgram is lazy do + var program = new LightPointOfViewProgram + program.compile_and_link + var error = program.error + assert error == null else print_error error + return program + end + + # Draw the light view in the bottom left of the screen, for debugging only + # + # The shadow depth texture is a square that can be deformed by this projection. + protected fun frame_core_shadow_debug(display: GamnitDisplay) + do + if not supports_shadows then + print_error "Error: Shadows are not supported by the current hardware configuration" + return + end + + perf_clock_shadow.lapse + + var program = shadow_debug_program + + glBindBuffer(gl_ARRAY_BUFFER, shadow_context.buffer_array) + glViewport(0, 0, display.width/3, display.height/3) + glClear gl_DEPTH_BUFFER_BIT + program.use + + # Uniforms + glActiveTexture gl_TEXTURE0 + glBindTexture(gl_TEXTURE_2D, shadow_context.depth_texture) + program.texture.uniform 0 + + # Attributes + var sizeof_gl_float = 4 + var n_floats = 3 + glEnableVertexAttribArray program.coord.location + glVertexAttribPointeri(program.coord.location, n_floats, gl_FLOAT, false, 0, 0) + var offset = 4 * n_floats * sizeof_gl_float + + n_floats = 2 + glEnableVertexAttribArray program.tex_coord.location + glVertexAttribPointeri(program.tex_coord.location, n_floats, gl_FLOAT, false, 0, offset) + var gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + # Draw + glDrawArrays(gl_TRIANGLE_STRIP, 0, 4) + gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + # Take down + glBindBuffer(gl_ARRAY_BUFFER, 0) + gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + sys.perfs["gamnit shadow debug"].add app.perf_clock_shadow.lapse + end +end + +# Handles to reused GL buffers and texture +private class ShadowContext + + # Real screen framebuffer + var screen_framebuffer: Int = -1 + + # Framebuffer for the light point of view + var light_view_framebuffer: Int = -1 + + # Depth attached to `light_view_framebuffer` + var depth_texture: Int = -1 + + # Buffer name for vertex data + var buffer_array: Int = -1 + + # Prepare all attributes once per resolution change + fun prepare_once(display: GamnitDisplay, shadow_resolution: Int) + do + assert display.gl_extensions.has("GL_OES_depth_texture") + + # Set aside the real screen framebuffer name + var screen_framebuffer = glGetIntegerv(gl_FRAMEBUFFER_BINDING, 0) + self.screen_framebuffer = screen_framebuffer + + # Framebuffer + var framebuffer = glGenFramebuffers(1).first + glBindFramebuffer(gl_FRAMEBUFFER, framebuffer) + assert glIsFramebuffer(framebuffer) + self.light_view_framebuffer = framebuffer + var gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + # Depth & texture/color + var textures = glGenTextures(1) + self.depth_texture = textures[0] + gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + resize(display, shadow_resolution) + assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE + + # Array buffer + buffer_array = glGenBuffers(1).first + glBindBuffer(gl_ARRAY_BUFFER, buffer_array) + assert glIsBuffer(buffer_array) + gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + ## coord + var data = new Array[Float] + data.add_all([-1.0, -1.0, 0.0, + 1.0, -1.0, 0.0, + -1.0, 1.0, 0.0, + 1.0, 1.0, 0.0]) + ## tex_coord + data.add_all([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]) + var c_data = new GLfloatArray.from(data) + glBufferData(gl_ARRAY_BUFFER, data.length*4, c_data.native_array, gl_STATIC_DRAW) + + glBindBuffer(gl_ARRAY_BUFFER, 0) + + gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + end + + # Init size or resize `depth_texture` + fun resize(display: GamnitDisplay, shadow_resolution: Int) + do + glBindFramebuffer(gl_FRAMEBUFFER, light_view_framebuffer) + var gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + # Depth texture + var depth_texture = self.depth_texture + glActiveTexture gl_TEXTURE0 + glBindTexture(gl_TEXTURE_2D, depth_texture) + glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR) + glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_NEAREST) + glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE) + glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE) + gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + # TODO support hardware shadows with GL ES 3.0 or GL_EXT_shadow_samplers + #glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_COMPARE_MODE, ...) + + glTexImage2D(gl_TEXTURE_2D, 0, gl_DEPTH_COMPONENT, + shadow_resolution, shadow_resolution, + 0, gl_DEPTH_COMPONENT, gl_UNSIGNED_SHORT, new Pointer.nul) + gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + glFramebufferTexture2D(gl_FRAMEBUFFER, gl_DEPTH_ATTACHMENT, gl_TEXTURE_2D, depth_texture, 0) + gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + # Check if the framebuffer is complete and valid + assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE + + # Take down + glBindTexture(gl_TEXTURE_2D, 0) + glBindFramebuffer(gl_FRAMEBUFFER, 0) + gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + end + + var destroyed = false + + fun destroy + do + if destroyed then return + destroyed = true + + # Free the buffer + glDeleteBuffers([buffer_array]) + var gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + buffer_array = -1 + + # Free the array and framebuffer plus its attachments + glDeleteBuffers([buffer_array]) + glDeleteFramebuffers([light_view_framebuffer]) + glDeleteTextures([depth_texture]) + end +end + +redef class Material + # Optimized draw of `model`, a part of `actor`, from the view of `camera` + # + # This drawing should only produce usable depth data. The default behavior, + # uses `shadow_depth_program`. + protected fun draw_depth(actor: Actor, model: LeafModel, camera: Camera) + do + var program = app.shadow_depth_program + program.use + program.mvp.uniform camera.mvp_matrix + + var mesh = model.mesh + + program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0) + program.scale.uniform actor.scale + program.use_map_diffuse.uniform false + + program.tex_coord.array_enabled = true + program.tex_coord.array(mesh.texture_coords, 2) + + program.coord.array_enabled = true + program.coord.array(mesh.vertices, 3) + + program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll) + + if mesh.indices.is_empty then + glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3) + else + glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array) + end + end + +end + +# Efficiently draw actors from the light view +class ShadowDepthProgram + super GamnitProgramFromSource + + redef var vertex_shader_source = """ + // Vertex coordinates + attribute vec4 coord; + + // Vertex translation + uniform vec4 translation; + + // Vertex scaling + uniform float scale; + + // Vertex coordinates on textures + attribute vec2 tex_coord; + + // Vertex normal + attribute vec3 normal; + + // Model view projection matrix + uniform mat4 mvp; + + // Rotation matrix + uniform mat4 rotation; + + // Output for the fragment shader + varying vec2 v_tex_coord; + + 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); + } + """ @ glsl_vertex_shader + + redef var fragment_shader_source = """ + precision mediump float; + + // Diffuse map + uniform bool use_map_diffuse; + uniform sampler2D map_diffuse; + + varying vec2 v_tex_coord; + + void main() + { + if (use_map_diffuse && texture2D(map_diffuse, v_tex_coord).a <= 0.01) { + discard; + } + } + """ @ glsl_fragment_shader + + # Vertices coordinates + var coord = attributes["coord"].as(AttributeVec4) is lazy + + # Should this program use the texture `map_diffuse`? + var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy + + # Diffuse texture unit + var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy + + # Coordinates on the textures, per vertex + var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy + + # Diffuse color + var diffuse_color = uniforms["diffuse_color"].as(UniformVec4) is lazy + + # Translation applied to each vertex + var translation = uniforms["translation"].as(UniformVec4) is lazy + + # Rotation matrix + var rotation = uniforms["rotation"].as(UniformMat4) is lazy + + # Scaling per vertex + var scale = uniforms["scale"].as(UniformFloat) is lazy + + # Model view projection matrix + var mvp = uniforms["mvp"].as(UniformMat4) is lazy +end + +# Draw the camera point of view on screen +private class LightPointOfViewProgram + super GamnitProgramFromSource + + redef var vertex_shader_source = """ + // Vertex coordinates + attribute vec3 coord; + + // Vertex coordinates on textures + attribute vec2 tex_coord; + + // Output to the fragment shader + varying vec2 v_coord; + + void main() + { + gl_Position = vec4(coord, 1.0); + v_coord = tex_coord; + } + """ @ glsl_vertex_shader + + redef var fragment_shader_source = """ + precision mediump float; + + // Virtual screen texture / color attachment + uniform sampler2D texture0; + + // Input from the vertex shader + varying vec2 v_coord; + + void main() + { + gl_FragColor = texture2D(texture0, v_coord); + } + """ @ glsl_fragment_shader + + # Vertices coordinates + var coord = attributes["coord"].as(AttributeVec3) is lazy + + # Coordinates on the textures, per vertex + var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy + + # Visible texture + var texture = uniforms["texture0"].as(UniformSampler2D) is lazy +end