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