gamnit: intro basic shadow mapping
[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 on_create
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 perf_clock_shadow.lapse
88
89 # Make sure there's no errors pending
90 assert glGetError == gl_NO_ERROR
91
92 # Bind the framebuffer and make sure it is OK
93 glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.light_view_framebuffer)
94 assert glGetError == gl_NO_ERROR
95 assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
96
97 # Draw to fill the depth texture and only the depth
98 glViewport(0, 0, shadow_resolution, shadow_resolution)
99 glColorMask(false, false, false, false)
100 glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT
101 assert glGetError == gl_NO_ERROR
102
103 # Update light position
104 var camera = light.camera
105 camera.position.x = app.world_camera.position.x
106 camera.position.y = app.world_camera.position.y
107 camera.position.z = app.world_camera.position.z
108
109 # Draw all actors
110 for actor in actors do
111 for leaf in actor.model.leaves do
112 leaf.material.draw_depth(actor, leaf, camera)
113 end
114 end
115
116 # Take down, bring back default values
117 glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.screen_framebuffer)
118 glColorMask(true, true, true, true)
119
120 perfs["gamnit shadows prep"].add perf_clock_shadow.lapse
121 end
122
123 # ---
124 # Debug: show light view in the bottom left of the screen
125
126 # Lazy load the debugging program
127 private var shadow_debug_program: LightPointOfViewProgram is lazy do
128 var program = new LightPointOfViewProgram
129 program.compile_and_link
130 var error = program.error
131 assert error == null else print_error error
132 return program
133 end
134
135 # Draw the light view in the bottom left of the screen, for debugging only
136 #
137 # The shadow depth texture is a square that can be deformed by this projection.
138 protected fun frame_core_shadow_debug(display: GamnitDisplay)
139 do
140 if not supports_shadows then
141 print_error "Error: Shadows are not supported by the current hardware configuration"
142 return
143 end
144
145 perf_clock_shadow.lapse
146
147 var program = shadow_debug_program
148
149 glBindBuffer(gl_ARRAY_BUFFER, shadow_context.buffer_array)
150 glViewport(0, 0, display.width/3, display.height/3)
151 glClear gl_DEPTH_BUFFER_BIT
152 program.use
153
154 # Uniforms
155 glActiveTexture gl_TEXTURE0
156 glBindTexture(gl_TEXTURE_2D, shadow_context.depth_texture)
157 program.texture.uniform 0
158
159 # Attributes
160 var sizeof_gl_float = 4
161 var n_floats = 3
162 glEnableVertexAttribArray program.coord.location
163 glVertexAttribPointeri(program.coord.location, n_floats, gl_FLOAT, false, 0, 0)
164 var offset = 4 * n_floats * sizeof_gl_float
165
166 n_floats = 2
167 glEnableVertexAttribArray program.tex_coord.location
168 glVertexAttribPointeri(program.tex_coord.location, n_floats, gl_FLOAT, false, 0, offset)
169 var gl_error = glGetError
170 assert gl_error == gl_NO_ERROR else print_error gl_error
171
172 # Draw
173 glDrawArrays(gl_TRIANGLE_STRIP, 0, 4)
174 gl_error = glGetError
175 assert gl_error == gl_NO_ERROR else print_error gl_error
176
177 # Take down
178 glBindBuffer(gl_ARRAY_BUFFER, 0)
179 gl_error = glGetError
180 assert gl_error == gl_NO_ERROR else print_error gl_error
181
182 sys.perfs["gamnit shadow debug"].add app.perf_clock_shadow.lapse
183 end
184 end
185
186 # Handles to reused GL buffers and texture
187 private class ShadowContext
188
189 # Real screen framebuffer
190 var screen_framebuffer: Int = -1
191
192 # Framebuffer for the light point of view
193 var light_view_framebuffer: Int = -1
194
195 # Depth attached to `light_view_framebuffer`
196 var depth_texture: Int = -1
197
198 # Buffer name for vertex data
199 var buffer_array: Int = -1
200
201 # Prepare all attributes once per resolution change
202 fun prepare_once(display: GamnitDisplay, shadow_resolution: Int)
203 do
204 assert display.gl_extensions.has("GL_OES_depth_texture")
205
206 # Set aside the real screen framebuffer name
207 var screen_framebuffer = glGetIntegerv(gl_FRAMEBUFFER_BINDING, 0)
208 self.screen_framebuffer = screen_framebuffer
209
210 # Framebuffer
211 var framebuffer = glGenFramebuffers(1).first
212 glBindFramebuffer(gl_FRAMEBUFFER, framebuffer)
213 assert glIsFramebuffer(framebuffer)
214 self.light_view_framebuffer = framebuffer
215 var gl_error = glGetError
216 assert gl_error == gl_NO_ERROR else print_error gl_error
217
218 # Depth & texture/color
219 var textures = glGenTextures(1)
220 self.depth_texture = textures[0]
221 gl_error = glGetError
222 assert gl_error == gl_NO_ERROR else print_error gl_error
223
224 resize(display, shadow_resolution)
225 assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
226
227 # Array buffer
228 buffer_array = glGenBuffers(1).first
229 glBindBuffer(gl_ARRAY_BUFFER, buffer_array)
230 assert glIsBuffer(buffer_array)
231 gl_error = glGetError
232 assert gl_error == gl_NO_ERROR else print_error gl_error
233
234 ## coord
235 var data = new Array[Float]
236 data.add_all([-1.0, -1.0, 0.0,
237 1.0, -1.0, 0.0,
238 -1.0, 1.0, 0.0,
239 1.0, 1.0, 0.0])
240 ## tex_coord
241 data.add_all([0.0, 0.0,
242 1.0, 0.0,
243 0.0, 1.0,
244 1.0, 1.0])
245 var c_data = new GLfloatArray.from(data)
246 glBufferData(gl_ARRAY_BUFFER, data.length*4, c_data.native_array, gl_STATIC_DRAW)
247
248 glBindBuffer(gl_ARRAY_BUFFER, 0)
249
250 gl_error = glGetError
251 assert gl_error == gl_NO_ERROR else print_error gl_error
252 end
253
254 # Init size or resize `depth_texture`
255 fun resize(display: GamnitDisplay, shadow_resolution: Int)
256 do
257 glBindFramebuffer(gl_FRAMEBUFFER, light_view_framebuffer)
258 var gl_error = glGetError
259 assert gl_error == gl_NO_ERROR else print_error gl_error
260
261 # Depth texture
262 var depth_texture = self.depth_texture
263 glActiveTexture gl_TEXTURE0
264 glBindTexture(gl_TEXTURE_2D, depth_texture)
265 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
266 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_NEAREST)
267 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE)
268 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE)
269 gl_error = glGetError
270 assert gl_error == gl_NO_ERROR else print_error gl_error
271
272 # TODO support hardware shadows with GL ES 3.0 or GL_EXT_shadow_samplers
273 #glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_COMPARE_MODE, ...)
274
275 glTexImage2D(gl_TEXTURE_2D, 0, gl_DEPTH_COMPONENT,
276 shadow_resolution, shadow_resolution,
277 0, gl_DEPTH_COMPONENT, gl_UNSIGNED_SHORT, new Pointer.nul)
278 gl_error = glGetError
279 assert gl_error == gl_NO_ERROR else print_error gl_error
280
281 glFramebufferTexture2D(gl_FRAMEBUFFER, gl_DEPTH_ATTACHMENT, gl_TEXTURE_2D, depth_texture, 0)
282 gl_error = glGetError
283 assert gl_error == gl_NO_ERROR else print_error gl_error
284
285 # Check if the framebuffer is complete and valid
286 assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
287
288 # Take down
289 glBindTexture(gl_TEXTURE_2D, 0)
290 glBindFramebuffer(gl_FRAMEBUFFER, 0)
291 gl_error = glGetError
292 assert gl_error == gl_NO_ERROR else print_error gl_error
293 end
294
295 var destroyed = false
296
297 fun destroy
298 do
299 if destroyed then return
300 destroyed = true
301
302 # Free the buffer
303 glDeleteBuffers([buffer_array])
304 var gl_error = glGetError
305 assert gl_error == gl_NO_ERROR else print_error gl_error
306 buffer_array = -1
307
308 # Free the array and framebuffer plus its attachments
309 glDeleteBuffers([buffer_array])
310 glDeleteFramebuffers([light_view_framebuffer])
311 glDeleteTextures([depth_texture])
312 end
313 end
314
315 redef class Material
316 # Optimized draw of `model`, a part of `actor`, from the view of `camera`
317 #
318 # This drawing should only produce usable depth data. The default behavior,
319 # uses `shadow_depth_program`.
320 protected fun draw_depth(actor: Actor, model: LeafModel, camera: Camera)
321 do
322 var program = app.shadow_depth_program
323 program.use
324 program.mvp.uniform camera.mvp_matrix
325
326 var mesh = model.mesh
327
328 program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
329 program.scale.uniform actor.scale
330 program.use_map_diffuse.uniform false
331
332 program.tex_coord.array_enabled = true
333 program.tex_coord.array(mesh.texture_coords, 2)
334
335 program.coord.array_enabled = true
336 program.coord.array(mesh.vertices, 3)
337
338 program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
339
340 if mesh.indices.is_empty then
341 glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
342 else
343 glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
344 end
345 end
346
347 end
348
349 # Efficiently draw actors from the light view
350 class ShadowDepthProgram
351 super GamnitProgramFromSource
352
353 redef var vertex_shader_source = """
354 // Vertex coordinates
355 attribute vec4 coord;
356
357 // Vertex translation
358 uniform vec4 translation;
359
360 // Vertex scaling
361 uniform float scale;
362
363 // Vertex coordinates on textures
364 attribute vec2 tex_coord;
365
366 // Vertex normal
367 attribute vec3 normal;
368
369 // Model view projection matrix
370 uniform mat4 mvp;
371
372 // Rotation matrix
373 uniform mat4 rotation;
374
375 // Output for the fragment shader
376 varying vec2 v_tex_coord;
377
378 void main()
379 {
380 vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
381 gl_Position = pos * mvp;
382
383 // Pass varyings to the fragment shader
384 v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
385 }
386 """ @ glsl_vertex_shader
387
388 redef var fragment_shader_source = """
389 precision mediump float;
390
391 // Diffuse map
392 uniform bool use_map_diffuse;
393 uniform sampler2D map_diffuse;
394
395 varying vec2 v_tex_coord;
396
397 void main()
398 {
399 if (use_map_diffuse && texture2D(map_diffuse, v_tex_coord).a <= 0.01) {
400 discard;
401 }
402 }
403 """ @ glsl_fragment_shader
404
405 # Vertices coordinates
406 var coord = attributes["coord"].as(AttributeVec4) is lazy
407
408 # Should this program use the texture `map_diffuse`?
409 var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy
410
411 # Diffuse texture unit
412 var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy
413
414 # Coordinates on the textures, per vertex
415 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
416
417 # Diffuse color
418 var diffuse_color = uniforms["diffuse_color"].as(UniformVec4) is lazy
419
420 # Translation applied to each vertex
421 var translation = uniforms["translation"].as(UniformVec4) is lazy
422
423 # Rotation matrix
424 var rotation = uniforms["rotation"].as(UniformMat4) is lazy
425
426 # Scaling per vertex
427 var scale = uniforms["scale"].as(UniformFloat) is lazy
428
429 # Model view projection matrix
430 var mvp = uniforms["mvp"].as(UniformMat4) is lazy
431 end
432
433 # Draw the camera point of view on screen
434 private class LightPointOfViewProgram
435 super GamnitProgramFromSource
436
437 redef var vertex_shader_source = """
438 // Vertex coordinates
439 attribute vec3 coord;
440
441 // Vertex coordinates on textures
442 attribute vec2 tex_coord;
443
444 // Output to the fragment shader
445 varying vec2 v_coord;
446
447 void main()
448 {
449 gl_Position = vec4(coord, 1.0);
450 v_coord = tex_coord;
451 }
452 """ @ glsl_vertex_shader
453
454 redef var fragment_shader_source = """
455 precision mediump float;
456
457 // Virtual screen texture / color attachment
458 uniform sampler2D texture0;
459
460 // Input from the vertex shader
461 varying vec2 v_coord;
462
463 void main()
464 {
465 gl_FragColor = texture2D(texture0, v_coord);
466 }
467 """ @ glsl_fragment_shader
468
469 # Vertices coordinates
470 var coord = attributes["coord"].as(AttributeVec3) is lazy
471
472 # Coordinates on the textures, per vertex
473 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
474
475 # Visible texture
476 var texture = uniforms["texture0"].as(UniformSampler2D) is lazy
477 end