1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # Shadow mapping using a depth texture
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:
23 # var sun = new ParallelLight
30 intrude import gamnit
::depth_core
34 # Resolution of the shadow texture, defaults to 4096 pixels
36 # TODO make configurable / ask the hardware for gl_MAX_TEXTURE_SIZE
37 var shadow_resolution
= 4096
39 # Are shadows supported by the current hardware configuration?
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")
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
51 private var shadow_depth_program
= new ShadowDepthProgram
53 private var perf_clock_shadow
= new Clock is lazy
55 redef fun create_gamnit
59 var program
= shadow_depth_program
60 program
.compile_and_link
61 var error
= program
.error
62 assert error
== null else print_error error
65 private var shadow_context
: ShadowContext = create_shadow_context
is lazy
67 private fun create_shadow_context
: ShadowContext
70 assert display
!= null
72 var context
= new ShadowContext
73 context
.prepare_once
(display
, shadow_resolution
)
77 # Update the depth texture from the light point of view
79 # This method updates `shadow_context.depth_texture`.
80 protected fun frame_core_shadow_prep
(display
: GamnitDisplay)
82 if not supports_shadows
then return
85 if not light
isa LightCastingShadows then return
87 perf_clock_shadow
.lapse
89 # Make sure there's no errors pending
90 assert glGetError
== gl_NO_ERROR
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
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
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
110 for actor
in actors
do
111 for leaf
in actor
.model
.leaves
do
112 leaf
.material
.draw_depth
(actor
, leaf
, camera
)
116 # Take down, bring back default values
117 glBindFramebuffer
(gl_FRAMEBUFFER
, shadow_context
.screen_framebuffer
)
118 glColorMask
(true, true, true, true)
120 perfs
["gamnit shadows prep"].add perf_clock_shadow
.lapse
124 # Debug: show light view in the bottom left of the screen
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
135 # Draw the light view in the bottom left of the screen, for debugging only
137 # The shadow depth texture is a square that can be deformed by this projection.
138 protected fun frame_core_shadow_debug
(display
: GamnitDisplay)
140 if not supports_shadows
then
141 print_error
"Error: Shadows are not supported by the current hardware configuration"
145 perf_clock_shadow
.lapse
147 var program
= shadow_debug_program
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
155 glActiveTexture gl_TEXTURE0
156 glBindTexture
(gl_TEXTURE_2D
, shadow_context
.depth_texture
)
157 program
.texture
.uniform
0
160 var sizeof_gl_float
= 4
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
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
173 glDrawArrays
(gl_TRIANGLE_STRIP
, 0, 4)
174 gl_error
= glGetError
175 assert gl_error
== gl_NO_ERROR
else print_error gl_error
178 glBindBuffer
(gl_ARRAY_BUFFER
, 0)
179 gl_error
= glGetError
180 assert gl_error
== gl_NO_ERROR
else print_error gl_error
182 sys
.perfs
["gamnit shadow debug"].add app
.perf_clock_shadow
.lapse
186 # Handles to reused GL buffers and texture
187 private class ShadowContext
189 # Real screen framebuffer
190 var screen_framebuffer
: Int = -1
192 # Framebuffer for the light point of view
193 var light_view_framebuffer
: Int = -1
195 # Depth attached to `light_view_framebuffer`
196 var depth_texture
: Int = -1
198 # Buffer name for vertex data
199 var buffer_array
: Int = -1
201 # Prepare all attributes once per resolution change
202 fun prepare_once
(display
: GamnitDisplay, shadow_resolution
: Int)
204 assert display
.gl_extensions
.has
("GL_OES_depth_texture")
206 # Set aside the real screen framebuffer name
207 var screen_framebuffer
= glGetIntegerv
(gl_FRAMEBUFFER_BINDING
, 0)
208 self.screen_framebuffer
= screen_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
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
224 resize
(display
, shadow_resolution
)
225 assert glCheckFramebufferStatus
(gl_FRAMEBUFFER
) == gl_FRAMEBUFFER_COMPLETE
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
235 var data
= new Array[Float]
236 data
.add_all
([-1.0, -1.0, 0.0,
241 data
.add_all
([0.0, 0.0,
245 var c_data
= new GLfloatArray.from
(data
)
246 glBufferData
(gl_ARRAY_BUFFER
, data
.length
*4, c_data
.native_array
, gl_STATIC_DRAW
)
248 glBindBuffer
(gl_ARRAY_BUFFER
, 0)
250 gl_error
= glGetError
251 assert gl_error
== gl_NO_ERROR
else print_error gl_error
254 # Init size or resize `depth_texture`
255 fun resize
(display
: GamnitDisplay, shadow_resolution
: Int)
257 glBindFramebuffer
(gl_FRAMEBUFFER
, light_view_framebuffer
)
258 var gl_error
= glGetError
259 assert gl_error
== gl_NO_ERROR
else print_error gl_error
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
272 # TODO support hardware shadows with GL ES 3.0 or GL_EXT_shadow_samplers
273 #glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_COMPARE_MODE, ...)
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
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
285 # Check if the framebuffer is complete and valid
286 assert glCheckFramebufferStatus
(gl_FRAMEBUFFER
) == gl_FRAMEBUFFER_COMPLETE
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
295 var destroyed
= false
299 if destroyed
then return
303 glDeleteBuffers
([buffer_array
])
304 var gl_error
= glGetError
305 assert gl_error
== gl_NO_ERROR
else print_error gl_error
308 # Free the array and framebuffer plus its attachments
309 glDeleteBuffers
([buffer_array
])
310 glDeleteFramebuffers
([light_view_framebuffer
])
311 glDeleteTextures
([depth_texture
])
316 # Optimized draw of `model`, a part of `actor`, from the view of `camera`
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)
322 var program
= app
.shadow_depth_program
324 program
.mvp
.uniform camera
.mvp_matrix
326 var mesh
= model
.mesh
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
332 program
.tex_coord
.array_enabled
= true
333 program
.tex_coord
.array
(mesh
.texture_coords
, 2)
335 program
.coord
.array_enabled
= true
336 program
.coord
.array
(mesh
.vertices
, 3)
338 program
.rotation
.uniform
new Matrix.gamnit_euler_rotation
(actor
.pitch
, actor
.yaw
, actor
.roll
)
340 if mesh
.indices
.is_empty
then
341 glDrawArrays
(mesh
.draw_mode
, 0, mesh
.vertices
.length
/3)
343 glDrawElements
(mesh
.draw_mode
, mesh
.indices
.length
, gl_UNSIGNED_SHORT
, mesh
.indices_c
.native_array
)
349 # Efficiently draw actors from the light view
350 class ShadowDepthProgram
351 super GamnitProgramFromSource
353 redef var vertex_shader_source
= """
354 // Vertex coordinates
355 attribute vec4 coord;
357 // Vertex translation
358 uniform vec4 translation;
363 // Vertex coordinates on textures
364 attribute vec2 tex_coord;
367 attribute vec3 normal;
369 // Model view projection matrix
373 uniform mat4 rotation;
375 // Output for the fragment shader
376 varying vec2 v_tex_coord;
380 vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
381 gl_Position = pos * mvp;
383 // Pass varyings to the fragment shader
384 v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
386 """ @ glsl_vertex_shader
388 redef var fragment_shader_source
= """
389 precision mediump float;
392 uniform bool use_map_diffuse;
393 uniform sampler2D map_diffuse;
395 varying vec2 v_tex_coord;
399 if (use_map_diffuse && texture2D(map_diffuse, v_tex_coord).a <= 0.01) {
403 """ @ glsl_fragment_shader
405 # Vertices coordinates
406 var coord
= attributes
["coord"].as(AttributeVec4) is lazy
408 # Should this program use the texture `map_diffuse`?
409 var use_map_diffuse
= uniforms
["use_map_diffuse"].as(UniformBool) is lazy
411 # Diffuse texture unit
412 var map_diffuse
= uniforms
["map_diffuse"].as(UniformSampler2D) is lazy
414 # Coordinates on the textures, per vertex
415 var tex_coord
= attributes
["tex_coord"].as(AttributeVec2) is lazy
418 var diffuse_color
= uniforms
["diffuse_color"].as(UniformVec4) is lazy
420 # Translation applied to each vertex
421 var translation
= uniforms
["translation"].as(UniformVec4) is lazy
424 var rotation
= uniforms
["rotation"].as(UniformMat4) is lazy
427 var scale
= uniforms
["scale"].as(UniformFloat) is lazy
429 # Model view projection matrix
430 var mvp
= uniforms
["mvp"].as(UniformMat4) is lazy
433 # Draw the camera point of view on screen
434 private class LightPointOfViewProgram
435 super GamnitProgramFromSource
437 redef var vertex_shader_source
= """
438 // Vertex coordinates
439 attribute vec3 coord;
441 // Vertex coordinates on textures
442 attribute vec2 tex_coord;
444 // Output to the fragment shader
445 varying vec2 v_coord;
449 gl_Position = vec4(coord, 1.0);
452 """ @ glsl_vertex_shader
454 redef var fragment_shader_source
= """
455 precision mediump float;
457 // Virtual screen texture / color attachment
458 uniform sampler2D texture0;
460 // Input from the vertex shader
461 varying vec2 v_coord;
465 gl_FragColor = texture2D(texture0, v_coord);
467 """ @ glsl_fragment_shader
469 # Vertices coordinates
470 var coord
= attributes
["coord"].as(AttributeVec3) is lazy
472 # Coordinates on the textures, per vertex
473 var tex_coord
= attributes
["tex_coord"].as(AttributeVec2) is lazy
476 var texture
= uniforms
["texture0"].as(UniformSampler2D) is lazy