3 #include <msp/core/application.h>
4 #include <msp/core/getopt.h>
5 #include <msp/gl/animatedobject.h>
6 #include <msp/gl/bloom.h>
7 #include <msp/gl/box.h>
8 #include <msp/gl/colorcurve.h>
9 #include <msp/gl/cylinder.h>
10 #include <msp/gl/environmentmap.h>
11 #include <msp/gl/framebuffer.h>
12 #include <msp/gl/grid.h>
13 #include <msp/gl/instancescene.h>
14 #include <msp/gl/light.h>
15 #include <msp/gl/lighting.h>
16 #include <msp/gl/basicmaterial.h>
17 #include <msp/gl/mesh.h>
18 #include <msp/gl/meshbuilder.h>
19 #include <msp/gl/object.h>
20 #include <msp/gl/pipeline.h>
21 #include <msp/gl/program.h>
22 #include <msp/gl/renderer.h>
23 #include <msp/gl/resources.h>
24 #include <msp/gl/shader.h>
25 #include <msp/gl/shadowmap.h>
26 #include <msp/gl/simplescene.h>
27 #include <msp/gl/technique.h>
28 #include <msp/gl/tests.h>
29 #include <msp/gl/texturecube.h>
30 #include <msp/gl/windowview.h>
31 #include <msp/graphics/simplewindow.h>
32 #include <msp/input/keyboard.h>
33 #include <msp/input/keys.h>
34 #include <msp/time/timestamp.h>
35 #include <msp/time/timedelta.h>
36 #include <msp/time/utils.h>
42 This application demonstrates a variety of features of the mspgl library,
44 - Creating meshes from multiple parts
45 - Creating a mesh and then modifying it
47 - Environment mapped reflections
48 - Skybox using a cube map texture
49 - Effects with nested pipelines
50 - Complex multitexturing
51 - Shader-based deformations
52 - Creating a normalmapped texture through rendering
54 To run the program in fullscreen mode, specify --fullscreen on the command
57 During execution the following keys are available:
59 space stop camera movement
63 class DesertPillars: public RegisteredApplication<DesertPillars>
68 Graphics::WindowOptions window_opts;
70 Options(const Graphics::Display &, int, char **);
82 class Cube: public GL::AnimatedObject
85 GL::ProgramData shdata;
88 Cube(const GL::Object &);
90 void set_spherify(float);
92 virtual void setup_render(GL::Renderer &, const GL::Tag &) const;
95 Msp::Graphics::Display display;
97 Msp::Graphics::Window window;
98 Msp::Graphics::GLContext gl_context;
99 Msp::Input::Keyboard keyboard;
100 GL::Resources resources;
102 GL::Program skybox_shprog;
103 GL::Technique skybox_tech;
104 GL::TextureCube skybox_tex;
105 GL::Sampler linear_clamped_sampler;
106 ObjectData skybox_data;
108 const GL::Program &shadow_shprog;
110 GL::Program ground_shprog;
111 GL::ProgramData ground_shdata;
112 GL::Texture2D tiles_texture;
113 GL::Texture2D tiles_normalmap;
114 GL::Texture2D sand_texture;
115 GL::Texture2D sand_normalmap;
116 GL::Sampler linear_sampler;
117 GL::Sampler mipmap_sampler;
118 GL::Technique ground_tech;
119 ObjectData ground_data;
121 GL::BasicMaterial pillar_material;
122 GL::Technique pillar_tech;
123 std::vector<ObjectData> pillar_data;
124 std::vector<GL::AnimatedObject *> pillars;
126 GL::Program cube_shprog;
127 GL::Program cube_shadow_shprog;
128 GL::BasicMaterial cube_material;
129 GL::Technique cube_tech;
130 ObjectData cube_data;
132 GL::EnvironmentMap *env_cube;
135 GL::Pipeline pipeline;
137 GL::SimpleScene sky_scene;
138 GL::InstanceScene scene;
139 GL::Lighting lighting;
141 GL::ShadowMap shadow_scene;
143 GL::ColorCurve colorcurve;
145 GL::Pipeline env_pipeline;
147 Time::TimeStamp last_tick;
156 static const char texture_src[];
157 static const char skybox_src[];
158 static const char ground_src[];
159 static const char cube_src[];
160 static const char cube_shadow_src_tail[];
161 static const float cube_shapes[];
164 DesertPillars(int, char **);
169 void create_pipeline();
170 void create_skybox();
171 static void create_skybox_face(GL::TextureCube &, GL::TextureCubeFace);
172 void create_tiles_texture();
173 void create_sand_texture();
174 static void gaussian_blur(unsigned char *, unsigned, unsigned);
175 static void create_normalmap(const unsigned char *, unsigned char *, unsigned, unsigned, float);
176 void create_ground();
177 static float ground_height(float, float);
178 void create_pillars();
180 static void create_cube_face(GL::MeshBuilder &, const GL::Vector3 &, const GL::Vector3 &, const GL::Vector3 &, unsigned);
187 void key_press(unsigned);
190 const char DesertPillars::texture_src[] =
191 "import msp_interface;\n"
192 "#pragma MSP stage(vertex)\n"
195 " gl_Position = vec4(vertex.xy*2.0-1.0, -vertex.z*2.0, 1.0);\n"
198 "#pragma MSP stage(fragment)\n"
199 "layout(location=1) out vec4 frag_normal;\n"
202 " frag_color = color;\n"
203 " frag_normal = vec4(normal*0.5+0.5, 1.0);\n"
206 const char DesertPillars::skybox_src[] =
207 "import msp_interface;\n"
208 "uniform samplerCube sky;\n"
209 "#pragma MSP stage(vertex)\n"
212 " gl_Position = projection_matrix*vec4(mat3(eye_obj_matrix)*vertex.xyz, 1.0);\n"
215 "#pragma MSP stage(fragment)\n"
218 " frag_color = texture(sky, vertex.xyz);\n"
221 const char DesertPillars::ground_src[] =
223 "uniform sampler2D texture1;\n"
224 "uniform sampler2D normalmap1;\n"
225 "uniform sampler2D texture2;\n"
226 "uniform sampler2D normalmap2;\n"
227 "const bool use_normal_map = true;\n"
228 "const bool use_shadow_map = true;\n"
229 "#pragma MSP stage(vertex)\n"
230 "layout(location=11) in float ground_type;\n"
231 "void custom_transform() override\n"
235 "#pragma MSP stage(fragment)\n"
236 "vec4 get_diffuse_color() override\n"
238 " return mix(texture(texture1, texcoord.xy*3.0), texture(texture2, texcoord.xy), ground_type);\n"
240 "vec3 get_fragment_normal() override\n"
242 " return mix(texture(normalmap1, texcoord.xy*3.0).rgb, texture(normalmap2, texcoord.xy).rgb, ground_type);\n"
245 const char DesertPillars::cube_src[] =
247 "const bool use_specular = true;\n"
248 "const bool use_reflectivity = true;\n"
249 "uniform float spherify;\n"
250 "#pragma MSP stage(vertex)\n"
251 "layout(location=11) in vec3 sphere_coord;\n"
252 "vec4 transform_position(vec4 pos) override\n"
254 " return eye_obj_matrix*vec4(mix(vertex.xyz, sphere_coord, spherify), 1.0);\n"
256 "vec3 transform_normal(vec3 pos) override\n"
258 " return eye_obj_normal_matrix*normalize(mix(normal, normalize(sphere_coord), spherify));\n"
261 const char DesertPillars::cube_shadow_src_tail[] =
262 "#pragma MSP stage(fragment)\n"
265 " frag_color = vec4(1.0);\n"
268 const float DesertPillars::cube_shapes[] = { -0.4, 0.5, 1.0, 0.3 };
271 DesertPillars::Options::Options(const Graphics::Display &dpy, int argc, char **argv)
274 getopt.add_option('f', "fullscreen", window_opts.fullscreen, GetOpt::NO_ARG).set_help("Run in fullscreen mode");
277 if(window_opts.fullscreen)
279 const Graphics::VideoMode &mode = dpy.get_desktop_mode();
280 window_opts.width = mode.width;
281 window_opts.height = mode.height;
285 window_opts.width = 800;
286 window_opts.height = 600;
291 DesertPillars::DesertPillars(int argc, char **argv):
292 options(display, argc, argv),
293 window(display, options.window_opts),
296 skybox_shprog(skybox_src),
297 shadow_shprog(resources.get<GL::Program>("_occluder.glsl.shader")),
298 ground_shprog(ground_src),
299 cube_shprog(cube_src),
300 cube_shadow_shprog(string(cube_src)+cube_shadow_src_tail),
301 view(window, gl_context),
303 shadow_scene(resources, 2048, scene, light),
304 bloom(resources, window.get_width(), window.get_height()),
305 colorcurve(resources),
306 env_pipeline(512, 512),
308 camera_stopped(false),
315 window.set_title("Desert Pillars");
316 window.signal_close.connect(sigc::bind(sigc::mem_fun(this, &DesertPillars::exit), 0));
317 if(options.window_opts.fullscreen)
318 window.show_cursor(false);
319 keyboard.signal_button_press.connect(sigc::bind_return(sigc::mem_fun(this, &DesertPillars::key_press), false));
329 DesertPillars::~DesertPillars()
333 for(vector<GL::AnimatedObject *>::iterator i=pillars.begin(); i!=pillars.end(); ++i)
337 void DesertPillars::setup_view()
339 camera.set_aspect_ratio(float(window.get_width())/window.get_height());
340 camera.set_up_direction(GL::Vector3(0, 0, 1));
341 camera.set_depth_clip(1, 50);
342 view.set_camera(&camera);
343 view.set_content(&pipeline);
346 void DesertPillars::create_pipeline()
348 pipeline.set_multisample(8);
349 pipeline.set_hdr(true);
351 /* The shadow map is focused on the part of the scene that contains the
352 pillars and the cube. Making the ground cast shadows as well would result
353 either in a very low spatial resolution of the shadow map, or ugly artifacts
354 as the ground crosses the shadow map boundary. */
355 shadow_scene.set_target(GL::Vector3(0, 0, 0), 10);
356 shadow_scene.set_darkness(1);
358 // Put the sun pretty high in the sky
359 light.set_diffuse(GL::Color(2.0));
360 light.set_position(GL::Vector4(0.5, -2, 3, 0));
361 lighting.attach(0, light);
362 lighting.set_ambient(GL::Color(0.2));
364 // The skybox is rendered first
365 pipeline.add_pass(0, sky_scene);
367 GL::Pipeline::Pass *pass = &pipeline.add_pass(0, shadow_scene);
368 pass->set_lighting(&lighting);
369 pass->set_depth_test(&GL::DepthTest::lequal());
371 /* A bloom filter enhances the realism of bright surfaces, even if there
372 isn't anything really glowy in the scene. */
373 bloom.set_strength(0.3);
374 pipeline.add_postprocessor(bloom);
376 /* Lighting calculations are best done in linear color space, so the final
377 image must be converted to srgb for display. */
378 colorcurve.set_srgb();
379 pipeline.add_postprocessor(colorcurve);
381 /* Initialize a second pipeline to render the environment map. It has the
382 same renderables and passes, but no postprocessors or camera. */
383 env_pipeline.add_pass(0, sky_scene);
384 pass = &env_pipeline.add_pass(0, shadow_scene);
385 pass->set_lighting(&lighting);
386 pass->set_depth_test(&GL::DepthTest::lequal());
389 void DesertPillars::create_skybox()
391 skybox_tex.storage(GL::SRGB8, 128, 1);
392 linear_clamped_sampler.set_min_filter(GL::LINEAR);
393 linear_clamped_sampler.set_wrap(GL::CLAMP_TO_EDGE);
394 for(unsigned i=0; i<6; ++i)
395 create_skybox_face(skybox_tex, skybox_tex.enumerate_faces(i));
397 GL::RenderPass &pass = skybox_tech.add_pass(0);
398 pass.set_shader_program(&skybox_shprog, 0);
399 pass.set_texture(0, &skybox_tex, &linear_clamped_sampler);
401 // The shader will use the vertex coordinates to initialize texture coordinates as well
402 skybox_data.mesh = new GL::Mesh(GL::VERTEX3);
403 GL::BoxBuilder(10, 10, 10).build(*skybox_data.mesh);
404 skybox_data.object = new GL::Object(skybox_data.mesh, &skybox_tech);
406 sky_scene.add(*skybox_data.object);
409 void DesertPillars::create_skybox_face(GL::TextureCube &texture, GL::TextureCubeFace face)
411 unsigned char *pixels = new unsigned char[128*128*3];
412 for(int y=0; y<128; ++y)
413 for(int x=0; x<128; ++x)
415 unsigned i = (x+y*128)*3;
416 GL::Vector3 v = texture.get_texel_direction(face, x, y);
419 float l = sqrt(v.x*v.x+v.y*v.y+v.z*v.z);
420 // Render a sky-like gradient, with deeper blue at the zenith
421 pixels[i] = 96-48*v.z/l;
422 pixels[i+1] = 168-84*v.z/l;
427 // Fill with a desert-y color below horizon
433 texture.image(face, 0, pixels);
437 void DesertPillars::create_tiles_texture()
439 unsigned width = 256;
440 unsigned height = 256;
441 tiles_texture.storage(GL::RGB8, width, height, 1);
442 tiles_normalmap.storage(GL::RGB8, width, height, 1);
443 linear_sampler.set_min_filter(GL::LINEAR);
445 GL::Mesh tiles((GL::VERTEX3, GL::NORMAL3, GL::COLOR4_UBYTE));
447 // Prepare some lookup tables for rendering the tiles
448 float split = 1.0f/3;
449 float spacing = split*0.02f;
450 float bevel = split*0.1f;
451 float coords[] = { 0.0f, spacing, spacing+bevel, split-bevel, split, split+spacing, split+spacing+bevel, 1.0f-bevel, 1.0f };
452 unsigned order[] = { 4, 1, 3, 2, 1, 1, 2, 2,
453 1, 4, 2, 3, 4, 4, 3, 3,
454 1, 1, 2, 2, 1, 4, 2, 3,
455 4, 4, 3, 3, 4, 1, 3, 2,
456 2, 3, 2, 2, 3, 3, 3, 2 };
459 GL::MeshBuilder bld(tiles);
461 // Create a dark background
462 bld.color(0.2f, 0.2f, 0.2f);
463 bld.normal(0.0f, 0.0f, 1.0f);
464 bld.begin(GL::TRIANGLE_STRIP);
465 bld.vertex(0.0f, 1.0f);
466 bld.vertex(0.0f, 0.0f);
467 bld.vertex(1.0f, 1.0f);
468 bld.vertex(1.0f, 0.0f);
471 // Create the four tiles
472 bld.color(GL::Color(0.95f, 0.8f, 0.65f).to_linear());
473 for(unsigned i=0; i<2; ++i)
474 for(unsigned j=0; j<2; ++j)
476 for(unsigned k=0; k<4; ++k)
478 bld.begin(GL::TRIANGLE_STRIP);
479 float facing = (k%2)*2-1.0f;
481 bld.normal(0.0f, 0.7071f*facing, 0.7071f);
483 bld.normal(0.7071f*facing, 0.0f, 0.7071f);
484 for(unsigned l=0; l<4; ++l)
485 bld.vertex(coords[i*4+order[k*8+l*2]], coords[j*4+order[k*8+l*2+1]], (l%2 ? bevel : 0.0f));
489 bld.begin(GL::TRIANGLE_STRIP);
490 bld.normal(0.0f, 0.0f, 1.0f);
491 for(unsigned l=0; l<4; ++l)
492 bld.vertex(coords[i*4+order[32+l*2]], coords[j*4+order[32+l*2+1]], bevel);
497 GL::Program shprog(texture_src);
499 // Use an FBO to turn the geometry into a normalmapped texture
501 fbo.attach(GL::COLOR_ATTACHMENT0, tiles_texture);
502 fbo.attach(GL::COLOR_ATTACHMENT1, tiles_normalmap);
503 GL::Bind bind_fbo(fbo);
504 GL::Renderer renderer;
505 renderer.set_shader_program(&shprog, 0);
506 tiles.draw(renderer);
509 void DesertPillars::create_sand_texture()
511 unsigned width = 512;
512 unsigned height = 512;
514 sand_texture.storage(GL::SRGB8, width/16, height/16);
515 sand_texture.set_auto_generate_mipmap(true);
516 sand_normalmap.storage(GL::RGB8, width, height);
517 sand_normalmap.set_auto_generate_mipmap(true);
518 mipmap_sampler.set_min_filter(GL::LINEAR_MIPMAP_LINEAR);
519 mipmap_sampler.set_max_anisotropy(4);
521 unsigned char *pixels = new unsigned char[width*height*3];
522 unsigned char *bump = new unsigned char[width*height];
523 for(unsigned y=0; y<height; ++y)
524 for(unsigned x=0; x<width; ++x)
526 unsigned i = (x+y*width)*3;
527 unsigned c = rand()%16;
531 bump[x+y*width] = rand();
533 sand_texture.image(0, pixels);
534 gaussian_blur(bump, width, height);
535 create_normalmap(bump, pixels, width, height, 4);
536 sand_normalmap.image(0, pixels);
541 void DesertPillars::gaussian_blur(unsigned char *data, unsigned width, unsigned height)
543 /* Create a gaussian blur kernel for σ=2. Gaussian blur is separable, so a
544 1-dimensional kernel is enough. */
547 for(int i=-4; i<=4; ++i)
548 sum += (kernel[4+i] = exp(-i*i*0.5));
549 for(unsigned i=0; i<9; ++i)
552 unsigned char *line = new unsigned char[max(width, height)];
553 // Perform the blur in the X direction
554 for(unsigned y=0; y<height; ++y)
556 for(unsigned x=0; x<width; ++x)
559 for(int i=-4; i<=4; ++i)
560 value += data[(x+width+i)%width+y*width]*kernel[4+i];
563 copy(line, line+width, data+y*width);
565 // And then in the Y direction
566 for(unsigned x=0; x<width; ++x)
568 for(unsigned y=0; y<height; ++y)
571 for(int i=-4; i<=4; ++i)
572 value += data[x+((y+height+i)%height)*width]*kernel[4+i];
575 for(unsigned y=0; y<height; ++y)
576 data[x+y*width] = line[y];
581 void DesertPillars::create_normalmap(const unsigned char *bump, unsigned char *normals, unsigned width, unsigned height, float depth)
583 for(unsigned y=0; y<height; ++y)
584 for(unsigned x=0; x<width; ++x)
586 float dz_x = (bump[(x+1)%width+y*width]-bump[(x+width-1)%width+y*width])*depth/510;
587 float dz_y = (bump[x+((y+1)%height)*width]-bump[x+((y+height-1)%height)*width])*depth/510;
588 float l = sqrt(dz_x*dz_x+dz_y*dz_y+1);
589 unsigned i = (x+y*width)*3;
590 normals[i] = (0.5-0.5*dz_x/l)*255;
591 normals[i+1] = (0.5-0.5*dz_y/l)*255;
592 normals[i+2] = (0.5+0.5/l)*255;
596 void DesertPillars::create_ground()
598 create_tiles_texture();
599 create_sand_texture();
601 ground_shdata.uniform("texture1", 0);
602 ground_shdata.uniform("normalmap1", 1);
603 ground_shdata.uniform("texture2", 2);
604 ground_shdata.uniform("normalmap2", 3);
606 GL::RenderPass *pass = &ground_tech.add_pass(0);
607 pass->set_shader_program(&ground_shprog, &ground_shdata);
608 pass->set_texture(0, &tiles_texture, &linear_sampler);
609 pass->set_texture(1, &tiles_normalmap, &linear_sampler);
610 pass->set_texture(2, &sand_texture, &mipmap_sampler);
611 pass->set_texture(3, &sand_normalmap, &mipmap_sampler);
613 /* No shadow pass here; the ground only receives shadows, but doesn't cast
616 GL::VertexFormat vfmt = (GL::VERTEX3, GL::NORMAL3, GL::TANGENT3, GL::BINORMAL3, GL::TEXCOORD2, GL::GENERIC1);
617 ground_data.mesh = new GL::Mesh(vfmt);
619 // Create a base grid
620 GL::GridBuilder(80, 80, 200, 200).tbn().build(*ground_data.mesh);
622 // And modify it with a heightmap
623 unsigned n_vertices = ground_data.mesh->get_n_vertices();
624 unsigned pos = vfmt.offset(GL::VERTEX3);
625 unsigned nor = vfmt.offset(GL::NORMAL3);
626 unsigned tan = vfmt.offset(GL::TANGENT3);
627 unsigned bin = vfmt.offset(GL::BINORMAL3);
628 unsigned tex = vfmt.offset(GL::TEXCOORD2);
629 unsigned gt = vfmt.offset(GL::GENERIC1);
630 for(unsigned i=0; i<n_vertices; ++i)
632 float *v = ground_data.mesh->modify_vertex(i);
633 v[pos+2] = ground_height(v[pos], v[pos+1]);
635 float dz_x = (ground_height(v[pos]+0.01, v[pos+1])-ground_height(v[pos]-0.01, v[pos+1]))/0.02;
636 float dz_y = (ground_height(v[pos], v[pos+1]+0.01)-ground_height(v[pos], v[pos+1]-0.01))/0.02;
637 float l = sqrt(dz_x*dz_x+dz_y*dz_y+1);
642 l = sqrt(dz_x*dz_x+1);
646 l = sqrt(dz_y*dz_y+1);
650 v[gt] = min(v[pos+2]*100, 1.0f);
655 ground_data.object = new GL::Object(ground_data.mesh, &ground_tech);
657 scene.add(*ground_data.object);
660 float DesertPillars::ground_height(float x, float y)
662 // Leave a flat area in the middle
663 float d = sqrt(x*x+y*y);
667 // This results in concentric rings of low hills
668 int i = (d-6)/(M_PI*2);
669 float a = atan2(y, x);
672 float h = (i%2) ? 0.5 : 0.3;
673 return (d-6)*0.001f+(1-cos(d-6))*(1-cos(a))*h;
676 void DesertPillars::create_pillars()
678 // The pillars are a matt off-white
679 pillar_material.set_diffuse(GL::Color(0.9, 0.89, 0.85).to_linear());
680 pillar_material.set_receive_shadows(true);
682 GL::RenderPass *pass = &pillar_tech.add_pass(0);
683 pass->set_material(&pillar_material, &resources);
685 pass = &pillar_tech.add_pass("shadow");
686 pass->set_shader_program(&shadow_shprog, 0);
688 pillar_data.reserve(7);
689 for(unsigned i=3; i<=20; ++i)
692 for(unsigned j=2; j<i; ++j)
696 if(pillar_data.size()<=height)
697 pillar_data.resize(height+1);
699 ObjectData &pd = pillar_data[height];
702 pd.mesh = new GL::Mesh((GL::VERTEX3, GL::NORMAL3));
703 GL::MeshBuilder bld(*pd.mesh);
705 // Produce a fluted cylinder
706 unsigned n_flutes = 12;
707 float r_bottom = cos(M_PI/n_flutes)*0.4;
708 float flute_depth = (0.4-r_bottom)*2;
709 float half_w = sin(M_PI/n_flutes)*0.4;
710 for(unsigned j=0; j<n_flutes; ++j)
712 float a = j*M_PI*2/n_flutes;
713 bld.begin(GL::TRIANGLE_STRIP);
714 for(int k=-3; k<=3; k+=2)
717 float x = cos(a)*(r_bottom-(1-t*t)*flute_depth)-sin(a)*t*half_w;
718 float y = sin(a)*(r_bottom-(1-t*t)*flute_depth)+cos(a)*t*half_w;
719 float d = -t*2*flute_depth;
720 float l = sqrt(d*d+1);
721 bld.normal(cos(a)/l-sin(a)*d/l, sin(a)/l+cos(a)*d/l, 0);
722 bld.vertex(x, y, 0.6+height);
723 bld.vertex(x, y, 0.6);
728 // Create a square plinth and capitel
729 bld.set_matrix(GL::Matrix::translation(0, 0, 0.3));
730 GL::BoxBuilder(1.0, 1.0, 0.6).build(bld);
731 bld.set_matrix(GL::Matrix::translation(0, 0, height+0.8));
732 GL::BoxBuilder(1.0, 1.0, 0.4).build(bld);
734 pd.object = new GL::Object(pd.mesh, &pillar_tech);
737 GL::AnimatedObject *pillar = new GL::AnimatedObject(*pd.object);
739 float a = (i-3)*2*M_PI/18;
740 matrix.translate(cos(a)*5, sin(a)*5, 0);
741 matrix.rotate(a, 0, 0, 1);
742 pillar->set_matrix(matrix);
744 pillars.push_back(pillar);
749 void DesertPillars::create_cube()
751 /* The cube is bluish-gray, with a hard specular reflection to produce a
753 cube_material.set_diffuse(GL::Color(0.5, 0.5, 0.55).to_linear());
754 cube_material.set_specular(GL::Color(1.0));
755 cube_material.set_shininess(120);
756 cube_material.set_reflectivity(0.5);
758 GL::RenderPass *pass = &cube_tech.add_pass(0);
759 pass->set_material(&cube_material, &resources);
760 pass->set_shader_program(&cube_shprog, 0);
762 pass = &cube_tech.add_pass("shadow");
763 pass->set_shader_program(&cube_shadow_shprog, 0);
765 cube_data.mesh = new GL::Mesh((GL::VERTEX3, GL::NORMAL3, GL::GENERIC3));
766 GL::MeshBuilder bld(*cube_data.mesh);
767 create_cube_face(bld, GL::Vector3(-1, -1, -1), GL::Vector3(2, 0, 0), GL::Vector3(0, 2, 0), 16);
768 bld.offset(cube_data.mesh->get_n_vertices());
769 create_cube_face(bld, GL::Vector3(-1, 1, -1), GL::Vector3(2, 0, 0), GL::Vector3(0, 0, 2), 16);
770 bld.offset(cube_data.mesh->get_n_vertices());
771 create_cube_face(bld, GL::Vector3(-1, 1, 1), GL::Vector3(2, 0, 0), GL::Vector3(0, -2, 0), 16);
772 bld.offset(cube_data.mesh->get_n_vertices());
773 create_cube_face(bld, GL::Vector3(1, -1, -1), GL::Vector3(-2, 0, 0), GL::Vector3(0, 0, 2), 16);
774 bld.offset(cube_data.mesh->get_n_vertices());
775 create_cube_face(bld, GL::Vector3(-1, -1, 1), GL::Vector3(0, 0, -2), GL::Vector3(0, 2, 0), 16);
776 bld.offset(cube_data.mesh->get_n_vertices());
777 create_cube_face(bld, GL::Vector3(1, -1, -1), GL::Vector3(0, 0, 2), GL::Vector3(0, 2, 0), 16);
778 cube_data.object = new GL::Object(cube_data.mesh, &cube_tech);
780 cube = new Cube(*cube_data.object);
781 env_cube = new GL::EnvironmentMap(resources, 512, *cube, env_pipeline);
782 scene.add(*env_cube);
785 void DesertPillars::create_cube_face(GL::MeshBuilder &bld, const GL::Vector3 &base, const GL::Vector3 &side1, const GL::Vector3 &side2, unsigned div)
787 /* The sides follow the cube map convention where the cross product points
788 inwards. Since the normal has to point outwards, reverse the order. */
790 n.x = side2.y*side1.z-side2.z*side1.y;
791 n.y = side2.z*side1.x-side2.x*side1.z;
792 n.z = side2.x*side1.y-side2.y*side1.x;
793 float l = sqrt(n.x*n.x+n.y*n.y+n.z*n.z);
794 bld.normal(n.x/l, n.y/l, n.z/l);
796 // Create vertices, with precomputed spherified coordinates
797 for(unsigned i=0; i<=div; ++i)
798 for(unsigned j=0; j<=div; ++j)
801 v.x = base.x+side1.x*i/div+side2.x*j/div;
802 v.y = base.y+side1.y*i/div+side2.y*j/div;
803 v.z = base.z+side1.z*i/div+side2.z*j/div;
805 l = sqrt(v.x*v.x+v.y*v.y+v.z*v.z);
807 bld.generic(0, v.x/l, v.y/l, v.z/l);
812 for(unsigned i=0; i<div; ++i)
814 bld.begin(GL::TRIANGLE_STRIP);
815 for(unsigned j=0; j<=div; ++j)
817 bld.element(i*(div+1)+j);
818 bld.element((i+1)*(div+1)+j);
824 int DesertPillars::main()
827 return Application::main();
830 void DesertPillars::tick()
832 Time::TimeStamp t = Time::now();
840 camera_angle += (dt/Time::sec)*M_PI*2/30;
841 if(camera_angle>M_PI*4)
842 camera_angle -= M_PI*4;
843 float h = 3+(1-cos(camera_angle*1.5))*3;
844 float r = sqrt(225-h*h);
845 camera.set_position(GL::Vector3(cos(camera_angle)*r, sin(camera_angle)*r, 1.5+h));
846 camera.look_at(GL::Vector3(0, 0, 2));
851 cube_angle += (dt/Time::sec)*M_PI*2/20;
852 GL::Matrix cube_matrix;
853 cube_matrix.translate(0, 0, 2.5);
854 cube_matrix.rotate(cube_angle, 0, 0, 1);
855 cube_matrix.rotate(cube_angle*0.5, 1, 0, 0);
856 cube->set_matrix(cube_matrix);
861 cube_phase += (dt/Time::sec)/5;
871 float x = cube_phase*5;
873 cube->set_spherify((1-x)*cube_shapes[(cube_shape+3)%4]+x*cube_shapes[cube_shape]);
876 cube->set_spherify(cube_shapes[cube_shape]);
883 void DesertPillars::key_press(unsigned key)
885 if(key==Input::KEY_ESC)
887 else if(key==Input::KEY_SPACE)
888 camera_stopped = !camera_stopped;
889 else if(key==Input::KEY_F)
890 cube_frozen = !cube_frozen;
891 else if(key==Input::KEY_S)
892 cube_stopped = !cube_stopped;
896 DesertPillars::ObjectData::ObjectData():
901 DesertPillars::ObjectData::~ObjectData()
908 DesertPillars::Cube::Cube(const GL::Object &obj):
909 GL::AnimatedObject(obj)
912 void DesertPillars::Cube::set_spherify(float s)
914 shdata.uniform("spherify", s);
917 void DesertPillars::Cube::setup_render(GL::Renderer &renderer, const GL::Tag &tag) const
919 AnimatedObject::setup_render(renderer, tag);
920 renderer.add_shader_data(shdata);