Merge: gamnit: new services and a lot of bug fixes and performance improvements
[nit.git] / lib / gamnit / depth / shadow.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Shadow mapping using a depth texture
16 #
17 # The default light does not cast any shadows. It can be changed to a
18 # `ParallelLight` in client games to cast sun-like shadows:
19 #
20 # ~~~
21 # import more_lights
22 #
23 # var sun = new ParallelLight
24 # sun.pitch = 0.25*pi
25 # sun.yaw = 0.25*pi
26 # app.light = sun
27 # ~~~
28 module shadow
29
30 intrude import gamnit::depth_core
31
32 redef class App
33
34 # Resolution of the shadow texture, defaults to 4096 pixels
35 #
36 # TODO make configurable / ask the hardware for gl_MAX_TEXTURE_SIZE
37 var shadow_resolution = 4096
38
39 # Are shadows supported by the current hardware configuration?
40 #
41 # The implementation may change in the future, but it currently relies on
42 # the GL extension `GL_EOS_depth_texture`.
43 var supports_shadows: Bool is lazy do
44 return display.as(not null).gl_extensions.has("GL_OES_depth_texture")
45 end
46
47 # Is `shadow_context.depth_texture` ready to be used?
48 fun shadow_depth_texture_available: Bool
49 do return supports_shadows and shadow_context.depth_texture != -1
50
51 private var shadow_depth_program = new ShadowDepthProgram
52
53 private var perf_clock_shadow = new Clock is lazy
54
55 redef fun create_gamnit
56 do
57 super
58
59 var program = shadow_depth_program
60 program.compile_and_link
61 var error = program.error
62 assert error == null else print_error error
63 end
64
65 private var shadow_context: ShadowContext = create_shadow_context is lazy
66
67 private fun create_shadow_context: ShadowContext
68 do
69 var display = display
70 assert display != null
71
72 var context = new ShadowContext
73 context.prepare_once(display, shadow_resolution)
74 return context
75 end
76
77 # Update the depth texture from the light point of view
78 #
79 # This method updates `shadow_context.depth_texture`.
80 protected fun frame_core_shadow_prep(display: GamnitDisplay)
81 do
82 if not supports_shadows then return
83
84 var light = app.light
85 if not light isa LightCastingShadows then return
86
87 # Make sure there's no errors pending
88 assert glGetError == gl_NO_ERROR
89
90 # Bind the framebuffer and make sure it is OK
91 glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.light_view_framebuffer)
92 assert glGetError == gl_NO_ERROR
93 assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
94
95 # Draw to fill the depth texture and only the depth
96 glViewport(0, 0, shadow_resolution, shadow_resolution)
97 glColorMask(false, false, false, false)
98 glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT
99 assert glGetError == gl_NO_ERROR
100
101 # Update light position
102 var camera = light.camera
103 camera.position.x = app.world_camera.position.x
104 camera.position.y = app.world_camera.position.y
105 camera.position.z = app.world_camera.position.z
106
107 # Draw all actors
108 for actor in actors do
109 for leaf in actor.model.leaves do
110 leaf.material.draw_depth(actor, leaf, camera)
111 end
112 end
113
114 # Take down, bring back default values
115 glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.screen_framebuffer)
116 glColorMask(true, true, true, true)
117 end
118
119 # ---
120 # Debug: show light view in the bottom left of the screen
121
122 # Lazy load the debugging program
123 private var shadow_debug_program: LightPointOfViewProgram is lazy do
124 var program = new LightPointOfViewProgram
125 program.compile_and_link
126 var error = program.error
127 assert error == null else print_error error
128 return program
129 end
130
131 # Draw the light view in the bottom left of the screen, for debugging only
132 #
133 # The shadow depth texture is a square that can be deformed by this projection.
134 protected fun frame_core_shadow_debug(display: GamnitDisplay)
135 do
136 if not supports_shadows then
137 print_error "Error: Shadows are not supported by the current hardware configuration"
138 return
139 end
140
141 perf_clock_shadow.lapse
142
143 var program = shadow_debug_program
144
145 glBindBuffer(gl_ARRAY_BUFFER, shadow_context.buffer_array)
146 glViewport(0, 0, display.width/3, display.height/3)
147 glClear gl_DEPTH_BUFFER_BIT
148 program.use
149
150 # Uniforms
151 glActiveTexture gl_TEXTURE0
152 glBindTexture(gl_TEXTURE_2D, shadow_context.depth_texture)
153 program.texture.uniform 0
154
155 # Attributes
156 var sizeof_gl_float = 4
157 var n_floats = 3
158 glEnableVertexAttribArray program.coord.location
159 glVertexAttribPointeri(program.coord.location, n_floats, gl_FLOAT, false, 0, 0)
160 var offset = 4 * n_floats * sizeof_gl_float
161
162 n_floats = 2
163 glEnableVertexAttribArray program.tex_coord.location
164 glVertexAttribPointeri(program.tex_coord.location, n_floats, gl_FLOAT, false, 0, offset)
165 var gl_error = glGetError
166 assert gl_error == gl_NO_ERROR else print_error gl_error
167
168 # Draw
169 glDrawArrays(gl_TRIANGLE_STRIP, 0, 4)
170 gl_error = glGetError
171 assert gl_error == gl_NO_ERROR else print_error gl_error
172
173 # Take down
174 glBindBuffer(gl_ARRAY_BUFFER, 0)
175 gl_error = glGetError
176 assert gl_error == gl_NO_ERROR else print_error gl_error
177
178 sys.perfs["gamnit shadow debug"].add app.perf_clock_shadow.lapse
179 end
180 end
181
182 # Handles to reused GL buffers and texture
183 private class ShadowContext
184
185 # Real screen framebuffer
186 var screen_framebuffer: Int = -1
187
188 # Framebuffer for the light point of view
189 var light_view_framebuffer: Int = -1
190
191 # Depth attached to `light_view_framebuffer`
192 var depth_texture: Int = -1
193
194 # Buffer name for vertex data
195 var buffer_array: Int = -1
196
197 # Prepare all attributes once per resolution change
198 fun prepare_once(display: GamnitDisplay, shadow_resolution: Int)
199 do
200 assert display.gl_extensions.has("GL_OES_depth_texture")
201
202 # Set aside the real screen framebuffer name
203 var screen_framebuffer = glGetIntegerv(gl_FRAMEBUFFER_BINDING, 0)
204 self.screen_framebuffer = screen_framebuffer
205
206 # Framebuffer
207 var framebuffer = glGenFramebuffers(1).first
208 glBindFramebuffer(gl_FRAMEBUFFER, framebuffer)
209 assert glIsFramebuffer(framebuffer)
210 self.light_view_framebuffer = framebuffer
211 var gl_error = glGetError
212 assert gl_error == gl_NO_ERROR else print_error gl_error
213
214 # Depth & texture/color
215 var textures = glGenTextures(1)
216 self.depth_texture = textures[0]
217 gl_error = glGetError
218 assert gl_error == gl_NO_ERROR else print_error gl_error
219
220 resize(display, shadow_resolution)
221 assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
222
223 # Array buffer
224 buffer_array = glGenBuffers(1).first
225 glBindBuffer(gl_ARRAY_BUFFER, buffer_array)
226 assert glIsBuffer(buffer_array)
227 gl_error = glGetError
228 assert gl_error == gl_NO_ERROR else print_error gl_error
229
230 ## coord
231 var data = new Array[Float]
232 data.add_all([-1.0, -1.0, 0.0,
233 1.0, -1.0, 0.0,
234 -1.0, 1.0, 0.0,
235 1.0, 1.0, 0.0])
236 ## tex_coord
237 data.add_all([0.0, 0.0,
238 1.0, 0.0,
239 0.0, 1.0,
240 1.0, 1.0])
241 var c_data = new GLfloatArray.from(data)
242 glBufferData(gl_ARRAY_BUFFER, data.length*4, c_data.native_array, gl_STATIC_DRAW)
243
244 glBindBuffer(gl_ARRAY_BUFFER, 0)
245
246 gl_error = glGetError
247 assert gl_error == gl_NO_ERROR else print_error gl_error
248 end
249
250 # Init size or resize `depth_texture`
251 fun resize(display: GamnitDisplay, shadow_resolution: Int)
252 do
253 glBindFramebuffer(gl_FRAMEBUFFER, light_view_framebuffer)
254 var gl_error = glGetError
255 assert gl_error == gl_NO_ERROR else print_error gl_error
256
257 # Depth texture
258 var depth_texture = self.depth_texture
259 glActiveTexture gl_TEXTURE0
260 glBindTexture(gl_TEXTURE_2D, depth_texture)
261 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
262 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_NEAREST)
263 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
264 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
265 gl_error = glGetError
266 assert gl_error == gl_NO_ERROR else print_error gl_error
267
268 # TODO support hardware shadows with GL ES 3.0 or GL_EXT_shadow_samplers
269 #glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_COMPARE_MODE, ...)
270
271 glTexImage2D(gl_TEXTURE_2D, 0, gl_DEPTH_COMPONENT,
272 shadow_resolution, shadow_resolution,
273 0, gl_DEPTH_COMPONENT, gl_UNSIGNED_SHORT, new Pointer.nul)
274 gl_error = glGetError
275 assert gl_error == gl_NO_ERROR else print_error gl_error
276
277 glFramebufferTexture2D(gl_FRAMEBUFFER, gl_DEPTH_ATTACHMENT, gl_TEXTURE_2D, depth_texture, 0)
278 gl_error = glGetError
279 assert gl_error == gl_NO_ERROR else print_error gl_error
280
281 # Check if the framebuffer is complete and valid
282 assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
283
284 # Take down
285 glBindTexture(gl_TEXTURE_2D, 0)
286 glBindFramebuffer(gl_FRAMEBUFFER, 0)
287 gl_error = glGetError
288 assert gl_error == gl_NO_ERROR else print_error gl_error
289 end
290
291 var destroyed = false
292
293 fun destroy
294 do
295 if destroyed then return
296 destroyed = true
297
298 # Free the buffer
299 glDeleteBuffers([buffer_array])
300 var gl_error = glGetError
301 assert gl_error == gl_NO_ERROR else print_error gl_error
302 buffer_array = -1
303
304 # Free the array and framebuffer plus its attachments
305 glDeleteBuffers([buffer_array])
306 glDeleteFramebuffers([light_view_framebuffer])
307 glDeleteTextures([depth_texture])
308 end
309 end
310
311 redef class Material
312 # Optimized draw of `model`, a part of `actor`, from the view of `camera`
313 #
314 # This drawing should only produce usable depth data. The default behavior,
315 # uses `shadow_depth_program`.
316 protected fun draw_depth(actor: Actor, model: LeafModel, camera: Camera)
317 do
318 var program = app.shadow_depth_program
319 program.use
320 program.mvp.uniform camera.mvp_matrix
321
322 var mesh = model.mesh
323
324 program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
325 program.scale.uniform actor.scale
326 program.use_map_diffuse.uniform false
327
328 program.tex_coord.array_enabled = true
329 program.tex_coord.array(mesh.texture_coords, 2)
330
331 program.coord.array_enabled = true
332 program.coord.array(mesh.vertices, 3)
333
334 program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
335
336 if mesh.indices.is_empty then
337 glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
338 else
339 glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
340 end
341 end
342
343 end
344
345 # Efficiently draw actors from the light view
346 class ShadowDepthProgram
347 super GamnitProgramFromSource
348
349 redef var vertex_shader_source = """
350 // Vertex coordinates
351 attribute vec4 coord;
352
353 // Vertex translation
354 uniform vec4 translation;
355
356 // Vertex scaling
357 uniform float scale;
358
359 // Vertex coordinates on textures
360 attribute vec2 tex_coord;
361
362 // Vertex normal
363 attribute vec3 normal;
364
365 // Model view projection matrix
366 uniform mat4 mvp;
367
368 // Rotation matrix
369 uniform mat4 rotation;
370
371 // Output for the fragment shader
372 varying vec2 v_tex_coord;
373
374 void main()
375 {
376 vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
377 gl_Position = pos * mvp;
378
379 // Pass varyings to the fragment shader
380 v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
381 }
382 """ @ glsl_vertex_shader
383
384 redef var fragment_shader_source = """
385 precision mediump float;
386
387 // Diffuse map
388 uniform bool use_map_diffuse;
389 uniform sampler2D map_diffuse;
390
391 varying vec2 v_tex_coord;
392
393 void main()
394 {
395 if (use_map_diffuse && texture2D(map_diffuse, v_tex_coord).a <= 0.01) {
396 discard;
397 }
398 }
399 """ @ glsl_fragment_shader
400
401 # Vertices coordinates
402 var coord = attributes["coord"].as(AttributeVec4) is lazy
403
404 # Should this program use the texture `map_diffuse`?
405 var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy
406
407 # Diffuse texture unit
408 var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy
409
410 # Coordinates on the textures, per vertex
411 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
412
413 # Diffuse color
414 var diffuse_color = uniforms["diffuse_color"].as(UniformVec4) is lazy
415
416 # Translation applied to each vertex
417 var translation = uniforms["translation"].as(UniformVec4) is lazy
418
419 # Rotation matrix
420 var rotation = uniforms["rotation"].as(UniformMat4) is lazy
421
422 # Scaling per vertex
423 var scale = uniforms["scale"].as(UniformFloat) is lazy
424
425 # Model view projection matrix
426 var mvp = uniforms["mvp"].as(UniformMat4) is lazy
427 end
428
429 # Draw the camera point of view on screen
430 private class LightPointOfViewProgram
431 super GamnitProgramFromSource
432
433 redef var vertex_shader_source = """
434 // Vertex coordinates
435 attribute vec3 coord;
436
437 // Vertex coordinates on textures
438 attribute vec2 tex_coord;
439
440 // Output to the fragment shader
441 varying vec2 v_coord;
442
443 void main()
444 {
445 gl_Position = vec4(coord, 1.0);
446 v_coord = tex_coord;
447 }
448 """ @ glsl_vertex_shader
449
450 redef var fragment_shader_source = """
451 precision mediump float;
452
453 // Virtual screen texture / color attachment
454 uniform sampler2D texture0;
455
456 // Input from the vertex shader
457 varying vec2 v_coord;
458
459 void main()
460 {
461 gl_FragColor = texture2D(texture0, v_coord);
462 }
463 """ @ glsl_fragment_shader
464
465 # Vertices coordinates
466 var coord = attributes["coord"].as(AttributeVec3) is lazy
467
468 # Coordinates on the textures, per vertex
469 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
470
471 # Visible texture
472 var texture = uniforms["texture0"].as(UniformSampler2D) is lazy
473 end