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