model_viewer: use premultiplied colors on clouds and atmospheric layers
[nit.git] / contrib / model_viewer / src / globe.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 # Intro a custom model, material and graphics program to draw a globe
16 module globe
17
18 intrude import gamnit::depth # To access `Mesh::indices_c`
19
20 # Number of vertices to create parallels, meridians are double of this
21 #
22 # The minimum should be 3 for an octahedron planet.
23 fun n_parallels: Int do return 25
24
25 redef class App
26
27 # Program for the graphic card
28 private var globe_program = new GlobeProgram
29
30 # Texture of the reflexive surface of the earth (with the seas in white)
31 private var texture_seas = new Texture("globe/seas.jpg")
32
33 # Texture of the surface of the earth
34 private var texture_earth = new Texture("globe/earth.jpg")
35
36 # Texture of the lights at night on earth
37 private var texture_night = new Texture("globe/lights.jpg")
38
39 # Elevation map of earth
40 private var texture_elevation = new Texture("globe/elevation.jpg")
41
42 # Texture of the clouds above the earth
43 private var texture_clouds = new Texture("globe/clouds.png")
44
45 redef fun on_create
46 do
47 super
48
49 # Compile globe_program
50 var program = app.globe_program
51 program.compile_and_link
52
53 # Catch any errors
54 var gamnit_error = program.error
55 assert gamnit_error == null else print_error gamnit_error
56 end
57 end
58
59 # Full model of a globe, with a surface, clouds layer and atmosphere
60 class GlobeModel
61 super CompositeModel
62
63 redef fun load
64 do
65 leaves.add new LeafModel(
66 new UVSphere(1.0, 2*n_parallels, n_parallels),
67 new GlobeMaterial.surface)
68 leaves.add new LeafModel(
69 new UVSphere(1.1, 2*n_parallels, n_parallels),
70 new GlobeMaterial.clouds)
71 leaves.add new LeafModel(
72 new UVSphere(1.2, 2*n_parallels, n_parallels),
73 new GlobeMaterial.atmo)
74 end
75 end
76
77 # Parameterizable material to draw the 3 layers of the globe
78 class GlobeMaterial
79 super Material
80
81 # Id of the texture for diffuse colors, if any
82 var texture_id: nullable Int
83
84 # Draw as a surface, using the elevation map and the night lights
85 var is_surface: Bool
86
87 # Ambient color
88 var color: Array[Float]
89
90 # Create and configure a material for the earth surface
91 init surface do init(0, true, [1.0, 1.0, 1.0, 1.0])
92
93 # Create and configure a material for the cloud layer
94 init clouds do init(4, false, [1.0*clouds_a, 1.0*clouds_a, 1.0*clouds_a, clouds_a])
95 private var clouds_a = 0.5
96
97 # Create and configure a material for the visible atmosphere
98 init atmo do init(null, false, [0.0, 0.8*atmo_a, 1.0*atmo_a, atmo_a])
99 private var atmo_a = 0.05
100
101 redef fun draw(actor, model)
102 do
103 var gl_error = glGetError
104 assert gl_error == gl_NO_ERROR else print gl_error
105
106 var mesh = model.mesh
107
108 var program = app.globe_program
109 program.use
110
111 # Set constant program values
112 program.use
113
114 # Bind textures
115 glActiveTexture gl_TEXTURE0
116 glBindTexture(gl_TEXTURE_2D, app.texture_earth.gl_texture)
117
118 glActiveTexture gl_TEXTURE1
119 glBindTexture(gl_TEXTURE_2D, app.texture_seas.gl_texture)
120
121 glActiveTexture gl_TEXTURE2
122 glBindTexture(gl_TEXTURE_2D, app.texture_night.gl_texture)
123
124 glActiveTexture gl_TEXTURE3
125 glBindTexture(gl_TEXTURE_2D, app.texture_elevation.gl_texture)
126
127 glActiveTexture gl_TEXTURE4
128 glBindTexture(gl_TEXTURE_2D, app.texture_clouds.gl_texture)
129
130 # Set samplers
131 program.tex_specular.uniform 1
132 program.tex_night.uniform 2
133 program.tex_displace.uniform 3
134
135 # Update camera view and light
136 var p = app.world_camera.position
137 program.camera.uniform(p.x, p.y, p.z)
138 program.mvp.uniform app.world_camera.mvp_matrix
139 program.light_center.uniform(app.light.position.x, app.light.position.y, app.light.position.z)
140
141 # Set attributes
142 program.coord.array_enabled = true
143 program.coord.array(mesh.vertices, 3)
144
145 program.tex_coord.array_enabled = true
146 program.tex_coord.array(mesh.texture_coords, 2)
147
148 program.normal.array_enabled = true
149 program.normal.array(mesh.normals, 3)
150
151 # Set uniforms
152 program.scale.uniform 1.0
153 program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
154 program.translation.uniform(actor.center.x, -actor.center.y, actor.center.z, 0.0)
155 program.color.uniform(color[0], color[1], color[2], color[3])
156 program.is_surface.uniform is_surface
157
158 # Set the color texture?
159 var tex = texture_id
160 if tex == null then
161 program.use_texture.uniform false
162 else
163 program.use_texture.uniform true
164 program.tex.uniform tex
165 end
166
167 # Execute draw, support only meshes with `indices`
168 glDrawElements(gl_TRIANGLES, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
169
170 # Catch any errors
171 gl_error = glGetError
172 assert gl_error == gl_NO_ERROR else print gl_error
173 end
174 end
175
176 # Graphical program to draw a planet with Phong lighting
177 class GlobeProgram
178 super GamnitProgramFromSource
179
180 redef var vertex_shader_source = """
181 // Vertex coordinates
182 attribute vec4 coord;
183
184 // Vertex color tint
185 uniform vec4 color;
186
187 // Vertex translation
188 uniform vec4 translation;
189
190 // Vertex scaling
191 uniform float scale;
192
193 // Vertex coordinates on textures
194 attribute vec2 tex_coord;
195
196 // Vertex normal
197 attribute vec3 normal;
198
199 // Model view projection matrix
200 uniform mat4 mvp;
201
202 // Model rotation
203 uniform mat4 rotation;
204
205 // Lights config
206 uniform vec3 light_center;
207
208 // Texture of surface elevation to displace vertices
209 uniform sampler2D tex_displace;
210
211 // Draw this as a planet surface?
212 uniform bool is_surface;
213
214 // Output for the fragment shader
215 varying vec4 v_color;
216 varying vec2 v_tex_coord;
217 varying vec3 v_normal;
218 varying vec4 v_light_center;
219
220 void main()
221 {
222 v_color = color;
223 v_tex_coord = tex_coord;
224 v_normal = normalize(vec4(normal, 0.0) * rotation * mvp).xyz;
225 v_light_center = vec4(light_center, 0.0) * mvp;
226
227 // Apply displacement map
228 float s = scale;
229 if (is_surface)
230 s += 0.05 * texture2D(tex_displace, tex_coord).r;
231
232 gl_Position = (vec4(coord.xyz * s, 1.0) * rotation + translation) * mvp;
233 }
234 """ @ glsl_vertex_shader
235
236 redef var fragment_shader_source = """
237 precision mediump float;
238
239 // Input from the vertex shader
240 varying vec4 v_color;
241 varying vec2 v_tex_coord;
242 varying vec3 v_normal;
243 varying vec4 v_light_center;
244
245 // Coordinates of the camera
246 uniform vec3 camera;
247
248 // Does this object use a texture?
249 uniform bool use_texture;
250
251 // Texture to apply on this object
252 uniform sampler2D tex;
253
254 // Reflection map to apply to the Phong logic
255 uniform sampler2D tex_specular;
256
257 // Texture for the dark side of the earth
258 uniform sampler2D tex_night;
259
260 // Draw this as a planet surface?
261 uniform bool is_surface;
262
263 // Colors config
264 vec4 ambient_color = vec4(0.2, 0.2, 0.2, 1.0);
265 vec4 diffuse_color = vec4(1.0, 1.0, 1.0, 1.0);
266 vec4 specular_color = vec4(1.0, 1.0, 1.0, 1.0);
267
268 void main()
269 {
270 // Lambert diffusion
271 vec3 to_light = normalize(v_light_center.xyz);
272 float lambert = max(dot(to_light, v_normal), 0.0);
273
274 // Phong specular
275 float specular = 0.0;
276 if (lambert > 0.0) {
277 vec3 to_camera = normalize(camera);
278 vec3 normal = normalize(v_normal);
279 vec3 light_reflect = reflect(to_light, normal);
280 float spec_angle = max(dot(light_reflect, to_camera), 0.0);
281 specular = pow(spec_angle, 16.0);
282
283 if (is_surface)
284 specular *= texture2D(tex_specular, v_tex_coord).x;
285 else specular *= 0.2;
286 }
287
288 if(use_texture) {
289 gl_FragColor = v_color * texture2D(tex, v_tex_coord);
290 } else {
291 gl_FragColor = v_color;
292 }
293
294 gl_FragColor *= ambient_color + lambert * diffuse_color;
295 gl_FragColor += specular * specular_color;
296
297 if (is_surface && lambert < 0.2) {
298 // Show city lights at night
299 float p_night = (0.2 - lambert) * 5.0;
300 gl_FragColor += p_night*texture2D(tex_night, v_tex_coord);
301 }
302 }
303 """ @ glsl_fragment_shader
304
305 # Vertices coordinates
306 var coord = attributes["coord"].as(AttributeVec4) is lazy
307
308 # Color tint per vertex
309 var color = uniforms["color"].as(UniformVec4) is lazy
310
311 # Scaling per vertex
312 var scale = uniforms["scale"].as(UniformFloat) is lazy
313
314 # Coordinates on the textures, per vertex
315 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
316
317 # Normal per vertex
318 var normal = attributes["normal"].as(AttributeVec3) is lazy
319
320 # Model view projection matrix
321 var mvp = uniforms["mvp"].as(UniformMat4) is lazy
322
323 # Should this program use the texture `tex`?
324 var use_texture = uniforms["use_texture"].as(UniformBool) is lazy
325
326 # Main visible texture unit
327 var tex = uniforms["tex"].as(UniformSampler2D) is lazy
328
329 # Texture unit for reflection effect
330 var tex_specular = uniforms["tex_specular"].as(UniformSampler2D) is lazy
331
332 # Texture of the earth at night
333 var tex_night = uniforms["tex_night"].as(UniformSampler2D) is lazy
334
335 # Texture with elevation data
336 var tex_displace = uniforms["tex_displace"].as(UniformSampler2D) is lazy
337
338 # Position of the camera
339 var camera = uniforms["camera"].as(UniformVec3) is lazy
340
341 # Execute this program to draw a planet surface?
342 var is_surface = uniforms["is_surface"].as(UniformBool) is lazy
343
344 # Translation applied to each vertex
345 var translation = uniforms["translation"].as(UniformVec4) is lazy
346
347 # Rotation matrix
348 var rotation = uniforms["rotation"].as(UniformMat4) is lazy
349
350 # Center position of the light
351 var light_center = uniforms["light_center"].as(UniformVec3) is lazy
352 end