gamnit: intro basic shadow mapping
[nit.git] / lib / gamnit / depth / more_materials.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 # Various material implementations
16 module more_materials
17
18 intrude import depth_core
19 intrude import flat
20 intrude import shadow
21 import more_lights
22
23 redef class Material
24 # Get the default blueish material
25 new do return new SmoothMaterial(
26 [0.0, 0.0, 0.3, 1.0],
27 [0.0, 0.0, 0.6, 1.0],
28 [1.0, 1.0, 1.0, 1.0])
29 end
30
31 # Simple material with static colors
32 class SmoothMaterial
33 super Material
34
35 # Ambient color, always visible
36 #
37 # The RGB values should be premultiplied by the alpha value.
38 var ambient_color: Array[Float] is writable
39
40 # Diffuse color when covered by a light source
41 #
42 # The RGB values should be premultiplied by the alpha value.
43 var diffuse_color: Array[Float] is writable
44
45 # Specular color affecting reflections
46 #
47 # The RGB values should be premultiplied by the alpha value.
48 var specular_color: Array[Float] is writable
49
50 redef fun draw(actor, model, camera)
51 do
52 var program = app.versatile_program
53 program.use
54 program.mvp.uniform camera.mvp_matrix
55
56 var mesh = model.mesh
57
58 # Actor specs
59 program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
60 program.scale.uniform actor.scale
61 program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
62
63 # From mesh
64 program.coord.array_enabled = true
65 program.coord.array(mesh.vertices, 3)
66
67 program.normal.array_enabled = true
68 program.normal.array(mesh.normals, 3)
69
70 # No textures
71 program.use_map_ambient.uniform false
72 program.use_map_diffuse.uniform false
73 program.use_map_specular.uniform false
74 program.tex_coord.array_enabled = false
75
76 # Camera
77 program.camera.uniform(camera.position.x, camera.position.y, camera.position.z)
78
79 # Colors from the material
80 var a = actor.alpha
81 program.ambient_color.uniform(ambient_color[0]*a, ambient_color[1]*a,
82 ambient_color[2]*a, ambient_color[3]*a)
83 program.diffuse_color.uniform(diffuse_color[0]*a, diffuse_color[1]*a,
84 diffuse_color[2]*a, diffuse_color[3]*a)
85 program.specular_color.uniform(specular_color[0]*a, specular_color[1]*a,
86 specular_color[2]*a, specular_color[3]*a)
87
88 setup_lights(actor, model, camera, program)
89
90 # Execute draw
91 if mesh.indices.is_empty then
92 glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
93 else
94 glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
95 end
96 end
97
98 private fun setup_lights(actor: Actor, model: LeafModel, camera: Camera, program: BlinnPhongProgram)
99 do
100 # TODO use a list of lights
101
102 # Light, for Lambert and Blinn-Phong
103 var light = app.light
104 if light isa ParallelLight then
105 program.light_kind.uniform 1
106
107 # Vector parallel to the light source
108 program.light_center.uniform(
109 -light.pitch.sin * light.yaw.sin,
110 light.pitch.cos,
111 -light.yaw.cos)
112 else if light isa PointLight then
113 program.light_kind.uniform 2
114
115 # Position of the light source
116 program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
117 else
118 program.light_kind.uniform 0
119 end
120
121 # Draw projected shadows?
122 if not light isa LightCastingShadows or not app.shadow_depth_texture_available then
123 program.use_shadows.uniform false
124 return
125 else program.use_shadows.uniform true
126
127 # Light point of view
128 program.light_mvp.uniform light.camera.mvp_matrix
129
130 # Depth texture
131 glActiveTexture gl_TEXTURE4
132 glBindTexture(gl_TEXTURE_2D, app.shadow_context.depth_texture)
133 program.depth_texture.uniform 4
134 program.depth_texture_size.uniform app.shadow_resolution.to_f
135 program.depth_texture_taps.uniform 2 # TODO make configurable
136 end
137 end
138
139 # Material with potential `diffuse_texture` and `specular_texture`
140 class TexturedMaterial
141 super SmoothMaterial
142
143 # Texture applied to the ambient_color
144 var ambient_texture: nullable Texture = null is writable
145
146 # Texture applied to the diffuse color
147 var diffuse_texture: nullable Texture = null is writable
148
149 # Texture applied to the specular color
150 var specular_texture: nullable Texture = null is writable
151
152 # Bump map TODO
153 private var normals_texture: nullable Texture = null is writable
154
155 redef fun draw(actor, model, camera)
156 do
157 var mesh = model.mesh
158
159 var program = app.versatile_program
160 program.use
161
162 # One of the textures used, if any
163 var sample_used_texture = null
164
165 var texture = ambient_texture
166 if texture != null then
167 glActiveTexture gl_TEXTURE0
168 glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
169 program.use_map_ambient.uniform true
170 program.map_ambient.uniform 0
171 sample_used_texture = texture
172 else
173 program.use_map_ambient.uniform false
174 end
175
176 texture = diffuse_texture
177 if texture != null then
178 glActiveTexture gl_TEXTURE1
179 glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
180 program.use_map_diffuse.uniform true
181 program.map_diffuse.uniform 1
182 sample_used_texture = texture
183 else
184 program.use_map_diffuse.uniform false
185 end
186
187 texture = specular_texture
188 if texture != null then
189 glActiveTexture gl_TEXTURE2
190 glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
191 program.use_map_specular.uniform true
192 program.map_specular.uniform 2
193 sample_used_texture = texture
194 else
195 program.use_map_specular.uniform false
196 end
197
198 texture = normals_texture
199 if texture != null then
200 glActiveTexture gl_TEXTURE3
201 glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
202 program.use_map_bump.uniform true
203 program.map_bump.uniform 3
204 sample_used_texture = texture
205 else
206 program.use_map_bump.uniform false
207 end
208
209 program.mvp.uniform camera.mvp_matrix
210 program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
211 program.scale.uniform actor.scale
212
213 # If using a texture, set `texture_coords`
214 program.tex_coord.array_enabled = sample_used_texture != null
215 if sample_used_texture != null then
216 if sample_used_texture isa RootTexture then
217 # Coordinates are directly valid
218 program.tex_coord.array(mesh.texture_coords, 2)
219 else
220 # Correlate texture coordinates from the substexture and the mesh.
221 # This is slow, but should be cached on the GPU.
222 var xa = sample_used_texture.offset_left
223 var xd = sample_used_texture.offset_right - xa
224 var ya = sample_used_texture.offset_top
225 var yd = sample_used_texture.offset_bottom - ya
226
227 var tex_coords = new Array[Float].with_capacity(mesh.texture_coords.length)
228 for i in [0..mesh.texture_coords.length/2[ do
229 tex_coords[i*2] = xa + xd * mesh.texture_coords[i*2]
230 tex_coords[i*2+1] = 1.0 - (ya + yd * mesh.texture_coords[i*2+1])
231 end
232
233 program.tex_coord.array(tex_coords, 2)
234 end
235 end
236
237 program.coord.array_enabled = true
238 program.coord.array(mesh.vertices, 3)
239
240 program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
241
242 var a = actor.alpha
243 program.ambient_color.uniform(ambient_color[0]*a, ambient_color[1]*a,
244 ambient_color[2]*a, ambient_color[3]*a)
245 program.diffuse_color.uniform(diffuse_color[0]*a, diffuse_color[1]*a,
246 diffuse_color[2]*a, diffuse_color[3]*a)
247 program.specular_color.uniform(specular_color[0]*a, specular_color[1]*a,
248 specular_color[2]*a, specular_color[3]*a)
249
250 program.normal.array_enabled = true
251 program.normal.array(mesh.normals, 3)
252
253 # Light
254 setup_lights(actor, model, camera, program)
255
256 # Camera
257 program.camera.uniform(camera.position.x, camera.position.y, camera.position.z)
258
259 if mesh.indices.is_empty then
260 glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
261 else
262 glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
263 end
264 end
265 end
266
267 # Simple material using the normals of the surface as color
268 #
269 # Each axis composing the normals are translated to color values.
270 # This material is useful for debugging normals or display models in a colorful way.
271 class NormalsMaterial
272 super Material
273
274 redef fun draw(actor, model, camera)
275 do
276 var program = app.normals_program
277 program.use
278 program.mvp.uniform camera.mvp_matrix
279
280 var mesh = model.mesh
281
282 # TODO apply normal map
283
284 program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
285 program.scale.uniform actor.scale
286
287 program.tex_coord.array_enabled = true
288 program.tex_coord.array(mesh.texture_coords, 2)
289
290 program.coord.array_enabled = true
291 program.coord.array(mesh.vertices, 3)
292
293 program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
294
295 program.normal.array_enabled = true
296 program.normal.array(mesh.normals, 3)
297
298 if mesh.indices.is_empty then
299 glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
300 else
301 glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
302 end
303 end
304 end
305
306 # Graphic program to display 3D models with Blinn-Phong specular lighting
307 class BlinnPhongProgram
308 super GamnitProgramFromSource
309
310 redef var vertex_shader_source = """
311 // Vertex coordinates
312 attribute vec4 coord;
313
314 // Vertex translation
315 uniform vec4 translation;
316
317 // Vertex scaling
318 uniform float scale;
319
320 // Vertex coordinates on textures
321 attribute vec2 tex_coord;
322
323 // Vertex normal
324 attribute vec3 normal;
325
326 // Camera model view projection matrix
327 uniform mat4 mvp;
328
329 uniform mat4 rotation;
330
331 // Lights config
332 uniform int light_kind;
333 uniform vec3 light_center;
334 uniform mat4 light_mvp;
335
336 // Coordinates of the camera
337 uniform vec3 camera;
338
339 // Output for the fragment shader
340 varying vec2 v_tex_coord;
341 varying vec3 v_normal;
342 varying vec4 v_to_light;
343 varying vec4 v_to_camera;
344 varying vec4 v_depth_pos;
345
346 void main()
347 {
348 vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
349 gl_Position = pos * mvp;
350 v_depth_pos = (pos * light_mvp) * 0.5 + 0.5;
351
352 // Pass varyings to the fragment shader
353 v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
354 v_normal = normalize(vec4(normal, 0.0) * rotation).xyz;
355 v_to_camera = normalize(vec4(camera, 1.0) - pos);
356
357 if (light_kind == 0) {
358 // No light
359 } else if (light_kind == 1) {
360 // Parallel
361 v_to_light = normalize(vec4(light_center, 1.0));
362 } else {
363 // Point light (and others?)
364 v_to_light = normalize(vec4(light_center, 1.0) - pos);
365 }
366 }
367 """ @ glsl_vertex_shader
368
369 redef var fragment_shader_source = """
370 precision mediump float;
371
372 // Input from the vertex shader
373 varying vec2 v_tex_coord;
374 varying vec3 v_normal;
375 varying vec4 v_to_light;
376 varying vec4 v_to_camera;
377 varying vec4 v_depth_pos;
378
379 // Colors
380 uniform vec4 ambient_color;
381 uniform vec4 diffuse_color;
382 uniform vec4 specular_color;
383
384 // Ambient map
385 uniform bool use_map_ambient;
386 uniform sampler2D map_ambient;
387
388 // Diffuse map
389 uniform bool use_map_diffuse;
390 uniform sampler2D map_diffuse;
391
392 // Specular map
393 uniform bool use_map_specular;
394 uniform sampler2D map_specular;
395
396 // Bump map
397 uniform bool use_map_bump;
398 uniform sampler2D map_bump;
399
400 // Normal map
401 uniform bool use_map_normal;
402 uniform sampler2D map_normal;
403
404 // Shadow
405 uniform int light_kind;
406 uniform bool use_shadows;
407 uniform sampler2D depth_texture;
408 uniform float depth_texture_size;
409 uniform int depth_texture_taps;
410
411 // Shadow effect on the diffuse colors of the fragment at offset `x, y`
412 float shadow_lookup(vec2 depth_coord, float x, float y) {
413 float tap_width = 1.0;
414 float pixel_size = tap_width/depth_texture_size;
415
416 vec2 offset = vec2(x * pixel_size * v_depth_pos.w,
417 y * pixel_size * v_depth_pos.w);
418 depth_coord += offset;
419
420 float depth = v_depth_pos.z/v_depth_pos.w;
421 //vec2 depth_coord = v_depth_pos.xy/v_depth_pos.w;
422 if (depth_coord.x < 0.0 || depth_coord.x > 1.0 || depth_coord.y < 0.0 || depth_coord.y > 1.0) {
423 // Out of the shadow map texture
424 //gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // debug, red out of the light view
425 return 1.0;
426 }
427
428 float shadow_depth = texture2D(depth_texture, depth_coord).r;
429 float bias = 0.0001;
430 if (shadow_depth == 1.0) {
431 // Too far to be in depth texture
432 return 1.0;
433 } else if (shadow_depth <= depth - bias) {
434 // In a shadow
435 //gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); // debug, blue shadows
436 return 0.2; // TODO replace with a configurable ambient light
437 }
438
439 //gl_FragColor = vec4(0.0, 1.0-(shadow_depth-depth), 0.0, 1.0); // debug, green lit surfaces
440 return 1.0;
441 }
442
443 // Shadow effect on the diffuse colors of the fragment
444 float shadow() {
445 if (!use_shadows) return 1.0;
446
447 vec2 depth_coord = v_depth_pos.xy/v_depth_pos.w;
448
449 float taps = float(depth_texture_taps);
450 float tap_step = 2.00/taps;
451 float sum = 0.0;
452 for (float x = -1.0; x <= 0.99; x += tap_step)
453 for (float y = -1.0; y <= 0.99; y += tap_step)
454 sum += shadow_lookup(depth_coord, x, y);
455 return sum / taps / taps;
456 }
457
458 void main()
459 {
460 // Normal
461 vec3 normal = v_normal;
462 if (use_map_bump) {
463 // TODO
464 vec3 bump = 2.0 * texture2D(map_bump, v_tex_coord).rgb - 1.0;
465 }
466
467 // Ambient light
468 vec4 ambient = ambient_color;
469 if (use_map_ambient) ambient *= texture2D(map_ambient, v_tex_coord);
470
471 if (light_kind == 0) {
472 // No light, show diffuse and ambient
473
474 vec4 diffuse = diffuse_color;
475 if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
476
477 gl_FragColor = ambient + diffuse;
478 } else {
479 // Parallel light or point light (1 or 2)
480
481 // Diffuse Lambert light
482 vec3 to_light = v_to_light.xyz;
483 float lambert = clamp(dot(normal, to_light), 0.0, 1.0);
484
485 vec4 diffuse = lambert * diffuse_color;
486 if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
487
488 // Specular Phong light
489 float s = 0.0;
490 if (lambert > 0.0) {
491 // In light
492 vec3 l = reflect(-to_light, normal);
493 s = clamp(dot(l, v_to_camera.xyz), 0.0, 1.0);
494 s = pow(s, 8.0); // TODO make this `shininess` a material attribute
495
496 // Shadows
497 diffuse *= shadow();
498 }
499
500 vec4 specular = s * specular_color;
501 if (use_map_specular) specular *= texture2D(map_specular, v_tex_coord).x;
502
503 gl_FragColor = ambient + diffuse + specular;
504 }
505
506 if (gl_FragColor.a < 0.01) discard;
507
508 //gl_FragColor = vec4(normalize(normal).rgb, 1.0); // Debug normals
509 }
510 """ @ glsl_fragment_shader
511
512 # Vertices coordinates
513 var coord = attributes["coord"].as(AttributeVec4) is lazy
514
515 # Should this program use the texture `map_ambient`?
516 var use_map_ambient = uniforms["use_map_ambient"].as(UniformBool) is lazy
517
518 # Ambient texture unit
519 var map_ambient = uniforms["map_ambient"].as(UniformSampler2D) is lazy
520
521 # Should this program use the texture `map_diffuse`?
522 var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy
523
524 # Diffuse texture unit
525 var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy
526
527 # Should this program use the texture `map_specular`?
528 var use_map_specular = uniforms["use_map_specular"].as(UniformBool) is lazy
529
530 # Specularity texture unit
531 var map_specular = uniforms["map_specular"].as(UniformSampler2D) is lazy
532
533 # Should this program use the texture `map_bump`?
534 var use_map_bump = uniforms["use_map_bump"].as(UniformBool) is lazy
535
536 # Bump texture unit
537 var map_bump = uniforms["map_bump"].as(UniformSampler2D) is lazy
538
539 # Normal per vertex
540 var normal = attributes["normal"].as(AttributeVec3) is lazy
541
542 # Coordinates on the textures, per vertex
543 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
544
545 # Ambient color
546 var ambient_color = uniforms["ambient_color"].as(UniformVec4) is lazy
547
548 # Diffuse color
549 var diffuse_color = uniforms["diffuse_color"].as(UniformVec4) is lazy
550
551 # Specular color
552 var specular_color = uniforms["specular_color"].as(UniformVec4) is lazy
553
554 # Kind of lights: 0 -> no light, 1 -> parallel, 2 -> point
555 var light_kind = uniforms["light_kind"].as(UniformInt) is lazy
556
557 # Center position of the light *or* vector to parallel light source
558 var light_center = uniforms["light_center"].as(UniformVec3) is lazy
559
560 # Light model view projection matrix
561 var light_mvp = uniforms["light_mvp"].as(UniformMat4) is lazy
562
563 # Should shadow be drawn? Would use `depth_texture` and `light_mvp`.
564 var use_shadows = uniforms["use_shadows"].as(UniformBool) is lazy
565
566 # Diffuse texture unit
567 var depth_texture = uniforms["depth_texture"].as(UniformSampler2D) is lazy
568
569 # Size, in pixels, of `depth_texture`
570 var depth_texture_size = uniforms["depth_texture_size"].as(UniformFloat) is lazy
571
572 # Times to tap the `depth_texture`, square root (set to 3 for a total of 9 taps)
573 var depth_texture_taps = uniforms["depth_texture_taps"].as(UniformInt) is lazy
574
575 # Camera position
576 var camera = uniforms["camera"].as(UniformVec3) is lazy
577
578 # Translation applied to each vertex
579 var translation = uniforms["translation"].as(UniformVec4) is lazy
580
581 # Rotation matrix
582 var rotation = uniforms["rotation"].as(UniformMat4) is lazy
583
584 # Scaling per vertex
585 var scale = uniforms["scale"].as(UniformFloat) is lazy
586
587 # Camera model view projection matrix
588 var mvp = uniforms["mvp"].as(UniformMat4) is lazy
589 end
590
591 # Program to color objects from their normal vectors
592 #
593 # May be used in place of `BlinnPhongProgram` for debugging or effect.
594 class NormalProgram
595 super BlinnPhongProgram
596
597 redef var fragment_shader_source = """
598 precision mediump float;
599
600 // Input from the vertex shader
601 varying vec3 v_normal;
602
603 void main()
604 {
605 gl_FragColor = vec4(v_normal*0.5 + 0.5, 1.0);
606 }
607 """ @ glsl_fragment_shader
608 end
609
610 redef class App
611 private var versatile_program = new BlinnPhongProgram is lazy
612
613 private var normals_program = new NormalProgram is lazy
614 end