gamnit: leave it up to the clients to set premultiplied colors in Materials
[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
21 redef class Material
22 # Get the default blueish material
23 new do return new SmoothMaterial(
24 [0.0, 0.0, 0.3, 1.0],
25 [0.0, 0.0, 0.6, 1.0],
26 [1.0, 1.0, 1.0, 1.0])
27 end
28
29 # Simple material with static colors
30 class SmoothMaterial
31 super Material
32
33 # Ambient color, always visible
34 #
35 # The RGB values should be premultiplied by the alpha value.
36 var ambient_color: Array[Float] is writable
37
38 # Diffuse color when covered by a light source
39 #
40 # The RGB values should be premultiplied by the alpha value.
41 var diffuse_color: Array[Float] is writable
42
43 # Specular color affecting reflections
44 #
45 # The RGB values should be premultiplied by the alpha value.
46 var specular_color: Array[Float] is writable
47
48 redef fun draw(actor, model)
49 do
50 var program = app.versatile_program
51 program.use
52
53 var mesh = model.mesh
54
55 # Actor specs
56 program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
57 program.scale.uniform actor.scale
58 program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
59
60 # From mesh
61 program.coord.array_enabled = true
62 program.coord.array(mesh.vertices, 3)
63
64 program.normal.array_enabled = true
65 program.normal.array(mesh.normals, 3)
66
67 # No textures
68 program.use_map_ambient.uniform false
69 program.use_map_diffuse.uniform false
70 program.use_map_specular.uniform false
71 program.tex_coord.array_enabled = false
72
73 # Lights
74 program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
75
76 # Camera
77 program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_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 # Execute draw
89 if mesh.indices.is_empty then
90 glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
91 else
92 glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
93 end
94 end
95 end
96
97 # Material with potential `diffuse_texture` and `specular_texture`
98 class TexturedMaterial
99 super SmoothMaterial
100
101 # Texture applied to the ambient_color
102 var ambient_texture: nullable Texture = null is writable
103
104 # Texture applied to the diffuse color
105 var diffuse_texture: nullable Texture = null is writable
106
107 # Texture applied to the specular color
108 var specular_texture: nullable Texture = null is writable
109
110 # Bump map TODO
111 private var normals_texture: nullable Texture = null is writable
112
113 redef fun draw(actor, model)
114 do
115 var mesh = model.mesh
116
117 var program = app.versatile_program
118 program.use
119
120 # One of the textures used, if any
121 var sample_used_texture = null
122
123 var texture = ambient_texture
124 if texture != null then
125 glActiveTexture gl_TEXTURE0
126 glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
127 program.use_map_ambient.uniform true
128 program.map_ambient.uniform 0
129 sample_used_texture = texture
130 else
131 program.use_map_ambient.uniform false
132 end
133
134 texture = diffuse_texture
135 if texture != null then
136 glActiveTexture gl_TEXTURE1
137 glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
138 program.use_map_diffuse.uniform true
139 program.map_diffuse.uniform 1
140 sample_used_texture = texture
141 else
142 program.use_map_diffuse.uniform false
143 end
144
145 texture = specular_texture
146 if texture != null then
147 glActiveTexture gl_TEXTURE2
148 glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
149 program.use_map_specular.uniform true
150 program.map_specular.uniform 2
151 sample_used_texture = texture
152 else
153 program.use_map_specular.uniform false
154 end
155
156 texture = normals_texture
157 if texture != null then
158 glActiveTexture gl_TEXTURE3
159 glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
160 program.use_map_bump.uniform true
161 program.map_bump.uniform 3
162 sample_used_texture = texture
163 else
164 program.use_map_bump.uniform false
165 end
166
167 program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
168 program.scale.uniform actor.scale
169
170 # If using a texture, set `texture_coords`
171 program.tex_coord.array_enabled = sample_used_texture != null
172 if sample_used_texture != null then
173 if sample_used_texture isa RootTexture then
174 # Coordinates are directly valid
175 program.tex_coord.array(mesh.texture_coords, 2)
176 else
177 # Correlate texture coordinates from the substexture and the mesh.
178 # This is slow, but should be cached on the GPU.
179 var xa = sample_used_texture.offset_left
180 var xd = sample_used_texture.offset_right - xa
181 var ya = sample_used_texture.offset_top
182 var yd = sample_used_texture.offset_bottom - ya
183 xd *= 0.999
184 yd *= 0.999
185
186 var tex_coords = new Array[Float].with_capacity(mesh.texture_coords.length)
187 for i in [0..mesh.texture_coords.length/2[ do
188 tex_coords[i*2] = xa + xd * mesh.texture_coords[i*2]
189 tex_coords[i*2+1] = 1.0 - (ya + yd * mesh.texture_coords[i*2+1])
190 end
191
192 program.tex_coord.array(tex_coords, 2)
193 end
194 end
195
196 program.coord.array_enabled = true
197 program.coord.array(mesh.vertices, 3)
198
199 program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
200
201 var a = actor.alpha
202 program.ambient_color.uniform(ambient_color[0]*a, ambient_color[1]*a,
203 ambient_color[2]*a, ambient_color[3]*a)
204 program.diffuse_color.uniform(diffuse_color[0]*a, diffuse_color[1]*a,
205 diffuse_color[2]*a, diffuse_color[3]*a)
206 program.specular_color.uniform(specular_color[0]*a, specular_color[1]*a,
207 specular_color[2]*a, specular_color[3]*a)
208
209 program.normal.array_enabled = true
210 program.normal.array(mesh.normals, 3)
211
212 program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
213 program.camera.uniform(app.world_camera.position.x, app.world_camera.position.y, app.world_camera.position.z)
214
215 if mesh.indices.is_empty then
216 glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
217 else
218 glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
219 end
220 end
221 end
222
223 # Simple material using the normals of the surface as color
224 #
225 # Each axis composing the normals are translated to color values.
226 # This material is useful for debugging normals or display models in a colorful way.
227 class NormalsMaterial
228 super Material
229
230 redef fun draw(actor, model)
231 do
232 var program = app.normals_program
233 program.use
234 program.mvp.uniform app.world_camera.mvp_matrix
235
236 var mesh = model.mesh
237
238 # TODO apply normal map
239
240 program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
241 program.scale.uniform actor.scale
242
243 program.tex_coord.array_enabled = true
244 program.tex_coord.array(mesh.texture_coords, 2)
245
246 program.coord.array_enabled = true
247 program.coord.array(mesh.vertices, 3)
248
249 program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
250
251 program.normal.array_enabled = true
252 program.normal.array(mesh.normals, 3)
253
254 if mesh.indices.is_empty then
255 glDrawArrays(mesh.draw_mode, 0, mesh.vertices.length/3)
256 else
257 glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
258 end
259 end
260 end
261
262 # Graphic program to display 3D models with Blinn-Phong specular lighting
263 class BlinnPhongProgram
264 super GamnitProgramFromSource
265
266 redef var vertex_shader_source = """
267 // Vertex coordinates
268 attribute vec4 coord;
269
270 // Vertex translation
271 uniform vec4 translation;
272
273 // Vertex scaling
274 uniform float scale;
275
276 // Vertex coordinates on textures
277 attribute vec2 tex_coord;
278
279 // Vertex normal
280 attribute vec3 normal;
281
282 // Model view projection matrix
283 uniform mat4 mvp;
284
285 uniform mat4 rotation;
286
287 // Lights config
288 uniform vec3 light_center;
289
290 // Coordinates of the camera
291 uniform vec3 camera;
292
293 // Output for the fragment shader
294 varying vec2 v_tex_coord;
295 varying vec3 v_normal;
296 varying vec4 v_to_light;
297 varying vec4 v_to_camera;
298
299 void main()
300 {
301 vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
302 gl_Position = pos * mvp;
303
304 // Pass varyings to the fragment shader
305 v_tex_coord = vec2(tex_coord.x, 1.0 - tex_coord.y);
306 v_normal = normalize(vec4(normal, 0.0) * rotation).xyz;
307 v_to_light = normalize(vec4(light_center, 1.0) - pos);
308 v_to_camera = normalize(vec4(camera, 1.0) - pos);
309 }
310 """ @ glsl_vertex_shader
311
312 redef var fragment_shader_source = """
313 precision mediump float;
314
315 // Input from the vertex shader
316 varying vec2 v_tex_coord;
317 varying vec3 v_normal;
318 varying vec4 v_to_light;
319 varying vec4 v_to_camera;
320
321 // Colors
322 uniform vec4 ambient_color;
323 uniform vec4 diffuse_color;
324 uniform vec4 specular_color;
325
326 // Ambient map
327 uniform bool use_map_ambient;
328 uniform sampler2D map_ambient;
329
330 // Diffuse map
331 uniform bool use_map_diffuse;
332 uniform sampler2D map_diffuse;
333
334 // Specular map
335 uniform bool use_map_specular;
336 uniform sampler2D map_specular;
337
338 // Bump map
339 uniform bool use_map_bump;
340 uniform sampler2D map_bump;
341
342 // Normal map
343 uniform bool use_map_normal;
344 uniform sampler2D map_normal;
345
346 void main()
347 {
348 // Normal
349 vec3 normal = v_normal;
350 if (use_map_bump) {
351 // TODO
352 vec3 bump = 2.0 * texture2D(map_bump, v_tex_coord).rgb - 1.0;
353 }
354
355 // Ambient light
356 vec4 ambient = ambient_color;
357 if (use_map_ambient) ambient *= texture2D(map_ambient, v_tex_coord);
358
359 // Diffuse Lambert light
360 vec3 to_light = v_to_light.xyz;
361 float lambert = clamp(dot(normal, to_light), 0.0, 1.0);
362
363 vec4 diffuse = lambert * diffuse_color;
364 if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
365
366 // Specular Phong light
367 float s = 0.0;
368 if (lambert > 0.0) {
369 vec3 l = reflect(-to_light, normal);
370 s = clamp(dot(l, v_to_camera.xyz), 0.0, 1.0);
371 s = pow(s, 8.0); // TODO make this `shininess` a material attribute
372 }
373
374 vec4 specular = s * specular_color;
375 if (use_map_specular) specular *= texture2D(map_specular, v_tex_coord).x;
376
377 gl_FragColor = ambient + diffuse + specular;
378 if (gl_FragColor.a < 0.01) discard;
379
380 //gl_FragColor = vec4(normalize(normal).rgb, 1.0); // Debug
381 }
382 """ @ glsl_fragment_shader
383
384 # Vertices coordinates
385 var coord = attributes["coord"].as(AttributeVec4) is lazy
386
387 # Should this program use the texture `map_ambient`?
388 var use_map_ambient = uniforms["use_map_ambient"].as(UniformBool) is lazy
389
390 # Ambient texture unit
391 var map_ambient = uniforms["map_ambient"].as(UniformSampler2D) is lazy
392
393 # Should this program use the texture `map_diffuse`?
394 var use_map_diffuse = uniforms["use_map_diffuse"].as(UniformBool) is lazy
395
396 # Diffuser texture unit
397 var map_diffuse = uniforms["map_diffuse"].as(UniformSampler2D) is lazy
398
399 # Should this program use the texture `map_specular`?
400 var use_map_specular = uniforms["use_map_specular"].as(UniformBool) is lazy
401
402 # Specularity texture unit
403 var map_specular = uniforms["map_specular"].as(UniformSampler2D) is lazy
404
405 # Should this program use the texture `map_bump`?
406 var use_map_bump = uniforms["use_map_bump"].as(UniformBool) is lazy
407
408 # Bump texture unit
409 var map_bump = uniforms["map_bump"].as(UniformSampler2D) is lazy
410
411 # Normal per vertex
412 var normal = attributes["normal"].as(AttributeVec3) is lazy
413
414 # Coordinates on the textures, per vertex
415 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
416
417 # Ambient color
418 var ambient_color = uniforms["ambient_color"].as(UniformVec4) is lazy
419
420 # Diffuse color
421 var diffuse_color = uniforms["diffuse_color"].as(UniformVec4) is lazy
422
423 # Specular color
424 var specular_color = uniforms["specular_color"].as(UniformVec4) is lazy
425
426 # Center position of the light
427 var light_center = uniforms["light_center"].as(UniformVec3) is lazy
428
429 # Camera position
430 var camera = uniforms["camera"].as(UniformVec3) is lazy
431
432 # Translation applied to each vertex
433 var translation = uniforms["translation"].as(UniformVec4) is lazy
434
435 # Rotation matrix
436 var rotation = uniforms["rotation"].as(UniformMat4) is lazy
437
438 # Scaling per vertex
439 var scale = uniforms["scale"].as(UniformFloat) is lazy
440
441 # Model view projection matrix
442 var mvp = uniforms["mvp"].as(UniformMat4) is lazy
443 end
444
445 # Program to color objects from their normal vectors
446 #
447 # May be used in place of `BlinnPhongProgram` for debugging or effect.
448 class NormalProgram
449 super BlinnPhongProgram
450
451 redef var fragment_shader_source = """
452 precision mediump float;
453
454 // Input from the vertex shader
455 varying vec3 v_normal;
456
457 void main()
458 {
459 gl_FragColor = vec4(v_normal*0.5 + 0.5, 1.0);
460 }
461 """ @ glsl_fragment_shader
462 end
463
464 redef class App
465 private var versatile_program = new BlinnPhongProgram is lazy
466
467 private var normals_program = new NormalProgram is lazy
468 end