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