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