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/cylinder.h>
9 #include <msp/gl/environmentmap.h>
10 #include <msp/gl/framebuffer.h>
11 #include <msp/gl/grid.h>
12 #include <msp/gl/instancescene.h>
13 #include <msp/gl/light.h>
14 #include <msp/gl/lighting.h>
15 #include <msp/gl/material.h>
16 #include <msp/gl/mesh.h>
17 #include <msp/gl/meshbuilder.h>
18 #include <msp/gl/object.h>
19 #include <msp/gl/pipeline.h>
20 #include <msp/gl/program.h>
21 #include <msp/gl/programbuilder.h>
22 #include <msp/gl/renderer.h>
23 #include <msp/gl/shader.h>
24 #include <msp/gl/shadowmap.h>
25 #include <msp/gl/simplescene.h>
26 #include <msp/gl/technique.h>
27 #include <msp/gl/tests.h>
28 #include <msp/gl/texturecube.h>
29 #include <msp/graphics/simplewindow.h>
30 #include <msp/input/keyboard.h>
31 #include <msp/input/keys.h>
32 #include <msp/time/timestamp.h>
33 #include <msp/time/units.h>
34 #include <msp/time/utils.h>
40 This application demonstrates a variety of features of the mspgl library,
42 - Creating meshes from multiple parts
43 - Creating a mesh and then modifying it
45 - Environment mapped reflections
46 - Skybox using a cube map texture
47 - Nested scenes and pipelines
48 - Complex multitexturing
49 - Shader-based deformations
50 - Creating a normalmapped texture through rendering
52 To run the program in fullscreen mode, specify --fullscreen on the command
55 During execution the following keys are available:
57 space stop camera movement
61 class DesertPillars: public RegisteredApplication<DesertPillars>
66 Graphics::WindowOptions window_opts;
68 Options(const Graphics::Display &, int, char **);
80 class Cube: public GL::AnimatedObject
83 GL::ProgramData shdata;
86 Cube(const GL::Object &);
88 void set_spherify(float);
90 virtual void setup_render(GL::Renderer &, const GL::Tag &) const;
93 Msp::Graphics::Display display;
95 Msp::Graphics::Window window;
96 Msp::Graphics::GLContext gl_context;
97 Msp::Input::Keyboard keyboard;
99 GL::Program skybox_shprog;
100 GL::Technique skybox_tech;
101 GL::TextureCube skybox_tex;
102 ObjectData skybox_data;
104 GL::Program shadow_shprog;
106 GL::Program ground_shprog;
107 GL::ProgramData ground_shdata;
108 GL::Texture2D tiles_texture;
109 GL::Texture2D tiles_normalmap;
110 GL::Texture2D sand_texture;
111 GL::Texture2D sand_normalmap;
112 GL::Technique ground_tech;
113 ObjectData ground_data;
115 GL::Program pillar_shprog;
116 GL::Material pillar_material;
117 GL::Technique pillar_tech;
118 std::vector<ObjectData> pillar_data;
119 std::vector<GL::AnimatedObject *> pillars;
121 GL::VertexShader cube_transform;
122 GL::Program cube_shprog;
123 GL::Program cube_shadow_shprog;
124 GL::Material cube_material;
125 GL::Technique cube_tech;
126 ObjectData cube_data;
128 GL::EnvironmentMap *env_cube;
130 GL::Pipeline pipeline;
132 GL::SimpleScene sky_scene;
133 GL::InstanceScene scene;
134 GL::Lighting lighting;
136 GL::ShadowMap shadow_scene;
139 GL::Pipeline env_pipeline;
141 Time::TimeStamp last_tick;
150 static const char texture_vertex_src[];
151 static const char texture_fragment_src[];
152 static const char skybox_vertex_src[];
153 static const char skybox_fragment_src[];
154 static const char ground_transform_src[];
155 static const char ground_colorify_src[];
156 static const char cube_transform_src[];
157 static const float cube_shapes[];
160 DesertPillars(int, char **);
164 void create_pipeline();
165 void create_skybox();
166 static void create_skybox_face(GL::TextureCube &, GL::TextureCubeFace);
167 void create_tiles_texture();
168 void create_sand_texture();
169 static void gaussian_blur(unsigned char *, unsigned, unsigned);
170 static void create_normalmap(const unsigned char *, unsigned char *, unsigned, unsigned, float);
171 void create_ground();
172 static float ground_height(float, float);
173 void create_pillars();
175 static void create_cube_face(GL::MeshBuilder &, const GL::Vector3 &, const GL::Vector3 &, const GL::Vector3 &, unsigned);
182 void key_press(unsigned);
185 const char DesertPillars::texture_vertex_src[] =
190 "out vec3 v_normal;\n"
191 "out vec3 v_color;\n"
194 " gl_Position = vec4(vertex.xy*2.0-1.0, -vertex.z*2.0, 1.0);\n"
195 " v_normal = normal;\n"
196 " v_color = color.rgb;\n"
199 const char DesertPillars::texture_fragment_src[] =
201 "in vec3 v_normal;\n"
203 "out vec4 frag_color;\n"
204 "out vec4 frag_normal;\n"
207 " frag_color = vec4(v_color, 1.0);\n"
208 " frag_normal = vec4(v_normal*0.5+0.5, 1.0);\n"
211 const char DesertPillars::skybox_vertex_src[] =
213 "uniform mat4 projection_matrix;\n"
214 "uniform mat4 eye_obj_matrix;\n"
216 "out vec3 v_texcoord;\n"
219 " gl_Position = projection_matrix*vec4(mat3(eye_obj_matrix)*vertex, 1.0);\n"
220 " v_texcoord = vertex;\n"
223 const char DesertPillars::skybox_fragment_src[] =
225 "uniform samplerCube sky;\n"
226 "in vec3 v_texcoord;\n"
227 "out vec4 frag_color;\n"
230 " frag_color = textureCube(sky, v_texcoord);\n"
233 // This exists only to transfer the ground type to fragment shader
234 const char DesertPillars::ground_transform_src[] =
236 "uniform mat4 eye_obj_matrix;\n"
237 "uniform mat3 eye_obj_normal_matrix;\n"
238 "in float ground_type;\n"
239 "out float v_ground_type;\n"
240 "vec4 transform_vertex(vec4 vertex)\n"
242 " v_ground_type = ground_type;\n"
243 " return eye_obj_matrix*vertex;\n"
245 "vec3 transform_normal(vec3 normal)\n"
247 " return mat3(eye_obj_normal_matrix)*normal;\n"
250 const char DesertPillars::ground_colorify_src[] =
252 "uniform sampler2D texture1;\n"
253 "uniform sampler2D normalmap1;\n"
254 "uniform sampler2D texture2;\n"
255 "uniform sampler2D normalmap2;\n"
256 "in float v_ground_type;\n"
257 "in vec3 v_shd_vertex;\n"
258 "vec4 sample_texture(vec2 coord)\n"
260 " return mix(texture2D(texture1, coord*3.0), texture2D(texture2, coord), v_ground_type);\n"
262 "vec3 sample_normalmap(vec2 coord)\n"
264 " return mix(texture2D(normalmap1, coord*3.0).rgb, texture2D(normalmap2, coord).rgb, v_ground_type);\n"
267 const char DesertPillars::cube_transform_src[] =
269 "uniform mat4 eye_obj_matrix;\n"
270 "uniform mat3 eye_obj_normal_matrix;\n"
271 "uniform float spherify;\n"
272 "in vec3 sphere_coord;\n"
273 "vec4 transform_vertex(vec4 vertex)\n"
275 " return eye_obj_matrix*vec4(mix(vertex.xyz, sphere_coord, spherify), 1.0);\n"
277 "vec3 transform_normal(vec3 normal)\n"
279 " return eye_obj_normal_matrix*normalize(mix(normal, normalize(sphere_coord), spherify));\n"
282 const float DesertPillars::cube_shapes[] = { -0.4, 0.5, 1.0, 0.3 };
285 DesertPillars::Options::Options(const Graphics::Display &dpy, int argc, char **argv)
288 getopt.add_option('f', "fullscreen", window_opts.fullscreen, GetOpt::NO_ARG).set_help("Run in fullscreen mode");
291 if(window_opts.fullscreen)
293 const Graphics::VideoMode &mode = dpy.get_desktop_mode();
294 window_opts.width = mode.width;
295 window_opts.height = mode.height;
299 window_opts.width = 800;
300 window_opts.height = 600;
305 DesertPillars::DesertPillars(int argc, char **argv):
306 options(display, argc, argv),
307 window(display, options.window_opts),
310 shadow_shprog(GL::ProgramBuilder::StandardFeatures()),
311 pipeline(window.get_width(), window.get_height()),
312 shadow_scene(2048, scene, light),
313 bloom(window.get_width(), window.get_height()),
314 env_pipeline(512, 512),
316 camera_stopped(false),
323 window.set_title("Desert Pillars");
324 window.signal_close.connect(sigc::bind(sigc::mem_fun(this, &DesertPillars::exit), 0));
325 if(options.window_opts.fullscreen)
326 window.show_cursor(false);
327 keyboard.signal_button_press.connect(sigc::bind_return(sigc::mem_fun(this, &DesertPillars::key_press), false));
336 DesertPillars::~DesertPillars()
340 for(vector<GL::AnimatedObject *>::iterator i=pillars.begin(); i!=pillars.end(); ++i)
344 void DesertPillars::create_pipeline()
346 pipeline.set_multisample(8);
348 camera.set_aspect(float(window.get_width())/window.get_height());
349 camera.set_up_direction(GL::Vector3(0, 0, 1));
350 camera.set_depth_clip(1, 50);
351 pipeline.set_camera(&camera);
353 /* The shadow map is focused on the part of the scene that contains the
354 pillars and the cube. Making the ground cast shadows as well would result
355 either in a very low spatial resolution of the shadow map, or ugly artifacts
356 as the ground crosses the shadow map boundary. */
357 shadow_scene.set_target(GL::Vector3(0, 0, 0), 10);
358 sky_scene.add(shadow_scene);
359 pipeline.add_renderable(sky_scene);
361 // Put the sun pretty high in the sky
362 light.set_position(GL::Vector4(0.5, -2, 3, 0));
363 lighting.attach(0, light);
364 lighting.set_ambient(GL::Color(0.5));
366 // The skybox is rendered first
367 pipeline.add_pass("sky");
369 GL::Pipeline::Pass *pass = &pipeline.add_pass(0);
370 pass->set_lighting(&lighting);
371 pass->set_depth_test(&GL::DepthTest::lequal());
373 /* A bloom filter enhances the realism of bright surfaces, even if there
374 isn't anything really glowy in the scene. */
375 bloom.set_strength(0.3);
376 pipeline.add_postprocessor(bloom);
378 /* Initialize a second pipeline to render the environment map. It has the
379 same renderables and passes, but no postprocessors or camera. */
380 env_pipeline.add_renderable(sky_scene);
381 env_pipeline.add_pass("sky");
382 pass = &env_pipeline.add_pass(0);
383 pass->set_lighting(&lighting);
384 pass->set_depth_test(&GL::DepthTest::lequal());
387 void DesertPillars::create_skybox()
389 skybox_tex.storage(GL::RGB, 128);
390 skybox_tex.set_min_filter(GL::LINEAR);
391 skybox_tex.set_wrap(GL::CLAMP_TO_EDGE);
392 for(unsigned i=0; i<6; ++i)
393 create_skybox_face(skybox_tex, skybox_tex.enumerate_faces(i));
395 skybox_shprog.attach_shader_owned(new GL::VertexShader(skybox_vertex_src));
396 skybox_shprog.attach_shader_owned(new GL::FragmentShader(skybox_fragment_src));
397 skybox_shprog.bind_attribute(GL::VERTEX3, "vertex");
398 skybox_shprog.bind_fragment_data(0, "frag_color");
399 skybox_shprog.link();
401 GL::RenderPass &pass = skybox_tech.add_pass("sky");
402 pass.set_shader_program(&skybox_shprog, 0);
403 pass.set_texture(0, &skybox_tex);
405 // The shader will use the vertex coordinates to initialize texture coordinates as well
406 skybox_data.mesh = new GL::Mesh(GL::VERTEX3);
407 GL::BoxBuilder(10, 10, 10).build(*skybox_data.mesh);
408 skybox_data.object = new GL::Object(skybox_data.mesh, &skybox_tech);
410 sky_scene.add(*skybox_data.object);
413 void DesertPillars::create_skybox_face(GL::TextureCube &texture, GL::TextureCubeFace face)
415 unsigned char *pixels = new unsigned char[128*128*3];
416 for(int y=0; y<128; ++y)
417 for(int x=0; x<128; ++x)
419 unsigned i = (x+y*128)*3;
420 GL::Vector3 v = texture.get_texel_direction(face, x, y);
423 float l = sqrt(v.x*v.x+v.y*v.y+v.z*v.z);
424 // Render a sky-like gradient, with deeper blue at the zenith
425 pixels[i] = 96-48*v.z/l;
426 pixels[i+1] = 168-84*v.z/l;
431 // Fill with a desert-y color below horizon
437 texture.image(face, 0, GL::RGB, GL::UNSIGNED_BYTE, pixels);
441 void DesertPillars::create_tiles_texture()
443 unsigned width = 256;
444 unsigned height = 256;
445 tiles_texture.storage(GL::RGB, width, height);
446 tiles_texture.set_min_filter(GL::LINEAR);
447 tiles_normalmap.storage(GL::RGB, width, height);
448 tiles_normalmap.set_min_filter(GL::LINEAR);
450 GL::Mesh tiles((GL::VERTEX3, GL::NORMAL3, GL::COLOR4_UBYTE));
452 // Prepare some lookup tables for rendering the tiles
453 float split = 1.0f/3;
454 float spacing = split*0.02f;
455 float bevel = split*0.1f;
456 float coords[] = { 0.0f, spacing, spacing+bevel, split-bevel, split, split+spacing, split+spacing+bevel, 1.0f-bevel, 1.0f };
457 unsigned order[] = { 4, 1, 3, 2, 1, 1, 2, 2,
458 1, 4, 2, 3, 4, 4, 3, 3,
459 1, 1, 2, 2, 1, 4, 2, 3,
460 4, 4, 3, 3, 4, 1, 3, 2,
461 2, 3, 2, 2, 3, 3, 3, 2 };
463 GL::MeshBuilder bld(tiles);
465 // Create a dark background
466 bld.color(0.2f, 0.2f, 0.2f);
467 bld.normal(0.0f, 0.0f, 1.0f);
468 bld.begin(GL::TRIANGLE_STRIP);
469 bld.vertex(0.0f, 1.0f);
470 bld.vertex(0.0f, 0.0f);
471 bld.vertex(1.0f, 1.0f);
472 bld.vertex(1.0f, 0.0f);
475 // Create the four tiles
476 bld.color(0.95f, 0.8f, 0.65f);
477 for(unsigned i=0; i<2; ++i)
478 for(unsigned j=0; j<2; ++j)
480 for(unsigned k=0; k<4; ++k)
482 bld.begin(GL::TRIANGLE_STRIP);
483 float facing = (k%2)*2-1.0f;
485 bld.normal(0.0f, 0.7071f*facing, 0.7071f);
487 bld.normal(0.7071f*facing, 0.0f, 0.7071f);
488 for(unsigned l=0; l<4; ++l)
489 bld.vertex(coords[i*4+order[k*8+l*2]], coords[j*4+order[k*8+l*2+1]], (l%2 ? bevel : 0.0f));
493 bld.begin(GL::TRIANGLE_STRIP);
494 bld.normal(0.0f, 0.0f, 1.0f);
495 for(unsigned l=0; l<4; ++l)
496 bld.vertex(coords[i*4+order[32+l*2]], coords[j*4+order[32+l*2+1]], bevel);
500 GL::Program shprog(texture_vertex_src, texture_fragment_src);
501 shprog.bind_attribute(GL::VERTEX3, "vertex");
502 shprog.bind_attribute(GL::NORMAL3, "normal");
503 shprog.bind_attribute(GL::COLOR4_UBYTE, "color");
504 shprog.bind_fragment_data(0, "frag_color");
505 shprog.bind_fragment_data(1, "frag_normal");
508 // Use an FBO to turn the geometry into a normalmapped texture
510 fbo.attach(GL::COLOR_ATTACHMENT0, tiles_texture);
511 fbo.attach(GL::COLOR_ATTACHMENT1, tiles_normalmap);
512 GL::Bind bind_fbo(fbo);
513 GL::Renderer renderer(0);
514 renderer.set_shader_program(&shprog, 0);
515 tiles.draw(renderer);
518 void DesertPillars::create_sand_texture()
520 unsigned width = 512;
521 unsigned height = 512;
523 sand_texture.storage(GL::RGB, width/16, height/16);
524 sand_texture.set_min_filter(GL::LINEAR_MIPMAP_LINEAR);
525 sand_texture.set_max_anisotropy(4);
526 sand_texture.set_generate_mipmap(true);
527 sand_normalmap.storage(GL::RGB, width, height);
528 sand_normalmap.set_min_filter(GL::LINEAR_MIPMAP_LINEAR);
529 sand_normalmap.set_max_anisotropy(4);
530 sand_normalmap.set_generate_mipmap(true);
532 unsigned char *pixels = new unsigned char[width*height*3];
533 unsigned char *bump = new unsigned char[width*height];
534 for(unsigned y=0; y<height; ++y)
535 for(unsigned x=0; x<width; ++x)
537 unsigned i = (x+y*width)*3;
538 unsigned c = rand()%16;
542 bump[x+y*width] = rand();
544 sand_texture.image(0, GL::RGB, GL::UNSIGNED_BYTE, pixels);
545 gaussian_blur(bump, width, height);
546 create_normalmap(bump, pixels, width, height, 4);
547 sand_normalmap.image(0, GL::RGB, GL::UNSIGNED_BYTE, pixels);
552 void DesertPillars::gaussian_blur(unsigned char *data, unsigned width, unsigned height)
554 /* Create a gaussian blur kernel for σ=2. Gaussian blur is separable, so a
555 1-dimensional kernel is enough. */
558 for(int i=-4; i<=4; ++i)
559 sum += (kernel[4+i] = exp(-i*i*0.5));
560 for(unsigned i=0; i<9; ++i)
563 unsigned char *line = new unsigned char[max(width, height)];
564 // Perform the blur in the X direction
565 for(unsigned y=0; y<height; ++y)
567 for(unsigned x=0; x<width; ++x)
570 for(int i=-4; i<=4; ++i)
571 value += data[(x+width+i)%width+y*width]*kernel[4+i];
574 copy(line, line+width, data+y*width);
576 // And then in the Y direction
577 for(unsigned x=0; x<width; ++x)
579 for(unsigned y=0; y<height; ++y)
582 for(int i=-4; i<=4; ++i)
583 value += data[x+((y+height+i)%height)*width]*kernel[4+i];
586 for(unsigned y=0; y<height; ++y)
587 data[x+y*width] = line[y];
592 void DesertPillars::create_normalmap(const unsigned char *bump, unsigned char *normals, unsigned width, unsigned height, float depth)
594 for(unsigned y=0; y<height; ++y)
595 for(unsigned x=0; x<width; ++x)
597 float dz_x = (bump[(x+1)%width+y*width]-bump[(x+width-1)%width+y*width])*depth/510;
598 float dz_y = (bump[x+((y+1)%height)*width]-bump[x+((y+height-1)%height)*width])*depth/510;
599 float l = sqrt(dz_x*dz_x+dz_y*dz_y+1);
600 unsigned i = (x+y*width)*3;
601 normals[i] = (0.5-0.5*dz_x/l)*255;
602 normals[i+1] = (0.5-0.5*dz_y/l)*255;
603 normals[i+2] = (0.5+0.5/l)*255;
607 void DesertPillars::create_ground()
609 create_tiles_texture();
610 create_sand_texture();
612 GL::ProgramBuilder::StandardFeatures features;
613 features.lighting = true;
614 features.shadow = true;
615 features.texture = true;
616 features.normalmap = true;
617 features.transform = true;
618 features.colorify = true;
619 GL::ProgramBuilder(features).add_shaders(ground_shprog);
620 ground_shprog.attach_shader_owned(new GL::VertexShader(ground_transform_src));
621 ground_shprog.attach_shader_owned(new GL::FragmentShader(ground_colorify_src));
622 ground_shprog.bind_attribute(7, "ground_type");
623 ground_shprog.link();
625 ground_shdata.uniform("texture1", 0);
626 ground_shdata.uniform("normalmap1", 1);
627 ground_shdata.uniform("texture2", 2);
628 ground_shdata.uniform("normalmap2", 3);
630 GL::RenderPass *pass = &ground_tech.add_pass(0);
631 pass->set_shader_program(&ground_shprog, &ground_shdata);
632 pass->set_texture(0, &tiles_texture);
633 pass->set_texture(1, &tiles_normalmap);
634 pass->set_texture(2, &sand_texture);
635 pass->set_texture(3, &sand_normalmap);
637 /* No shadow pass here; the ground only receives shadows, but doesn't cast
640 GL::VertexFormat vfmt = (GL::VERTEX3, GL::NORMAL3, GL::TANGENT3, GL::BINORMAL3, GL::TEXCOORD2, GL::ATTRIB1,7);
641 ground_data.mesh = new GL::Mesh(vfmt);
643 // Create a base grid
644 GL::GridBuilder(80, 80, 200, 200).tbn().build(*ground_data.mesh);
646 // And modify it with a heightmap
647 unsigned n_vertices = ground_data.mesh->get_n_vertices();
648 unsigned pos = vfmt.offset(GL::VERTEX3);
649 unsigned nor = vfmt.offset(GL::NORMAL3);
650 unsigned tan = vfmt.offset(GL::TANGENT3);
651 unsigned bin = vfmt.offset(GL::BINORMAL3);
652 unsigned tex = vfmt.offset(GL::TEXCOORD2);
653 unsigned gt = vfmt.offset(GL::make_indexed_component(GL::ATTRIB1, 7));
654 for(unsigned i=0; i<n_vertices; ++i)
656 float *v = ground_data.mesh->modify_vertex(i);
657 v[pos+2] = ground_height(v[pos], v[pos+1]);
659 float dz_x = (ground_height(v[pos]+0.01, v[pos+1])-ground_height(v[pos]-0.01, v[pos+1]))/0.02;
660 float dz_y = (ground_height(v[pos], v[pos+1]+0.01)-ground_height(v[pos], v[pos+1]-0.01))/0.02;
661 float l = sqrt(dz_x*dz_x+dz_y*dz_y+1);
666 l = sqrt(dz_x*dz_x+1);
670 l = sqrt(dz_y*dz_y+1);
674 v[gt] = min(v[pos+2]*100, 1.0f);
679 ground_data.object = new GL::Object(ground_data.mesh, &ground_tech);
681 scene.add(*ground_data.object);
684 float DesertPillars::ground_height(float x, float y)
686 // Leave a flat area in the middle
687 float d = sqrt(x*x+y*y);
691 // This results in concentric rings of low hills
692 int i = (d-6)/(M_PI*2);
693 float a = atan2(y, x);
696 float h = (i%2) ? 0.5 : 0.3;
697 return (d-6)*0.001f+(1-cos(d-6))*(1-cos(a))*h;
700 void DesertPillars::create_pillars()
702 // The pillars are a matt off-white
703 pillar_material.set_diffuse(GL::Color(0.9, 0.88, 0.8));
704 pillar_material.set_ambient(GL::Color(0.9, 0.88, 0.8));
706 GL::ProgramBuilder::StandardFeatures features;
707 features.lighting = true;
708 features.material = true;
709 features.shadow = true;
710 GL::ProgramBuilder(features).add_shaders(pillar_shprog);
711 pillar_shprog.link();
713 GL::RenderPass *pass = &pillar_tech.add_pass(0);
714 pass->set_material(&pillar_material);
715 pass->set_shader_program(&pillar_shprog, 0);
717 pass = &pillar_tech.add_pass("shadow");
718 pass->set_shader_program(&shadow_shprog, 0);
720 pillar_data.reserve(7);
721 for(unsigned i=3; i<=20; ++i)
724 for(unsigned j=2; j<i; ++j)
728 if(pillar_data.size()<=height)
729 pillar_data.resize(height+1);
731 ObjectData &pd = pillar_data[height];
734 pd.mesh = new GL::Mesh((GL::VERTEX3, GL::NORMAL3));
735 GL::MeshBuilder bld(*pd.mesh);
737 // Produce a fluted cylinder
738 unsigned n_flutes = 12;
739 float r_bottom = cos(M_PI/n_flutes)*0.4;
740 float flute_depth = (0.4-r_bottom)*2;
741 float half_w = sin(M_PI/n_flutes)*0.4;
742 for(unsigned j=0; j<n_flutes; ++j)
744 float a = j*M_PI*2/n_flutes;
745 bld.begin(GL::TRIANGLE_STRIP);
746 for(int k=-3; k<=3; k+=2)
749 float x = cos(a)*(r_bottom-(1-t*t)*flute_depth)-sin(a)*t*half_w;
750 float y = sin(a)*(r_bottom-(1-t*t)*flute_depth)+cos(a)*t*half_w;
751 float d = -t*2*flute_depth;
752 float l = sqrt(d*d+1);
753 bld.normal(cos(a)/l-sin(a)*d/l, sin(a)/l+cos(a)*d/l, 0);
754 bld.vertex(x, y, 0.6+height);
755 bld.vertex(x, y, 0.6);
760 // Create a square plinth and capitel
761 bld.matrix() = GL::Matrix::translation(0, 0, 0.3);
762 GL::BoxBuilder(1.0, 1.0, 0.6).build(bld);
763 bld.matrix() = GL::Matrix::translation(0, 0, height+0.8);
764 GL::BoxBuilder(1.0, 1.0, 0.4).build(bld);
766 pd.object = new GL::Object(pd.mesh, &pillar_tech);
769 GL::AnimatedObject *pillar = new GL::AnimatedObject(*pd.object);
771 float a = (i-3)*2*M_PI/18;
772 matrix.translate(cos(a)*5, sin(a)*5, 0);
773 matrix.rotate(a, 0, 0, 1);
774 pillar->set_matrix(matrix);
776 pillars.push_back(pillar);
781 void DesertPillars::create_cube()
783 /* The cube is bluish-gray, with a hard specular reflection to produce a
785 cube_material.set_diffuse(GL::Color(0.5, 0.5, 0.55));
786 cube_material.set_ambient(GL::Color(0.5, 0.5, 0.55));
787 cube_material.set_specular(GL::Color(1.0));
788 cube_material.set_shininess(150);
790 cube_transform.source(cube_transform_src);
791 cube_transform.compile();
793 // First create a simplified shader for rendering the shadow map
794 GL::ProgramBuilder::StandardFeatures features;
795 features.transform = true;
796 GL::ProgramBuilder(features).add_shaders(cube_shadow_shprog);
797 cube_shadow_shprog.attach_shader(cube_transform);
798 cube_shadow_shprog.bind_attribute(7, "sphere_coord");
799 cube_shadow_shprog.link();
801 // Then add the rest of the features for normal rendering
802 features.lighting = true;
803 features.specular = true;
804 features.material = true;
805 features.shadow = true;
806 features.reflection = true;
807 GL::ProgramBuilder(features).add_shaders(cube_shprog);
808 cube_shprog.attach_shader(cube_transform);
809 cube_shprog.bind_attribute(7, "sphere_coord");
812 GL::RenderPass *pass = &cube_tech.add_pass(0);
813 pass->set_material(&cube_material);
814 pass->set_shader_program(&cube_shprog, 0);
816 pass = &cube_tech.add_pass("shadow");
817 pass->set_shader_program(&cube_shadow_shprog, 0);
819 cube_data.mesh = new GL::Mesh((GL::VERTEX3, GL::NORMAL3, GL::ATTRIB3,7));
820 GL::MeshBuilder bld(*cube_data.mesh);
821 create_cube_face(bld, GL::Vector3(-1, -1, -1), GL::Vector3(2, 0, 0), GL::Vector3(0, 2, 0), 16);
822 bld.offset(cube_data.mesh->get_n_vertices());
823 create_cube_face(bld, GL::Vector3(-1, 1, -1), GL::Vector3(2, 0, 0), GL::Vector3(0, 0, 2), 16);
824 bld.offset(cube_data.mesh->get_n_vertices());
825 create_cube_face(bld, GL::Vector3(-1, 1, 1), GL::Vector3(2, 0, 0), GL::Vector3(0, -2, 0), 16);
826 bld.offset(cube_data.mesh->get_n_vertices());
827 create_cube_face(bld, GL::Vector3(1, -1, -1), GL::Vector3(-2, 0, 0), GL::Vector3(0, 0, 2), 16);
828 bld.offset(cube_data.mesh->get_n_vertices());
829 create_cube_face(bld, GL::Vector3(-1, -1, 1), GL::Vector3(0, 0, -2), GL::Vector3(0, 2, 0), 16);
830 bld.offset(cube_data.mesh->get_n_vertices());
831 create_cube_face(bld, GL::Vector3(1, -1, -1), GL::Vector3(0, 0, 2), GL::Vector3(0, 2, 0), 16);
832 cube_data.object = new GL::Object(cube_data.mesh, &cube_tech);
834 cube = new Cube(*cube_data.object);
835 env_cube = new GL::EnvironmentMap(512, *cube, env_pipeline);
836 scene.add(*env_cube);
839 void DesertPillars::create_cube_face(GL::MeshBuilder &bld, const GL::Vector3 &base, const GL::Vector3 &side1, const GL::Vector3 &side2, unsigned div)
841 /* The sides follow the cube map convention where the cross product points
842 inwards. Since the normal has to point outwards, reverse the order. */
844 n.x = side2.y*side1.z-side2.z*side1.y;
845 n.y = side2.z*side1.x-side2.x*side1.z;
846 n.z = side2.x*side1.y-side2.y*side1.x;
847 float l = sqrt(n.x*n.x+n.y*n.y+n.z*n.z);
848 bld.normal(n.x/l, n.y/l, n.z/l);
850 // Create vertices, with precomputed spherified coordinates
851 for(unsigned i=0; i<=div; ++i)
852 for(unsigned j=0; j<=div; ++j)
855 v.x = base.x+side1.x*i/div+side2.x*j/div;
856 v.y = base.y+side1.y*i/div+side2.y*j/div;
857 v.z = base.z+side1.z*i/div+side2.z*j/div;
859 l = sqrt(v.x*v.x+v.y*v.y+v.z*v.z);
861 bld.attrib(7, v.x/l, v.y/l, v.z/l);
866 for(unsigned i=0; i<div; ++i)
868 bld.begin(GL::TRIANGLE_STRIP);
869 for(unsigned j=0; j<=div; ++j)
871 bld.element(i*(div+1)+j);
872 bld.element((i+1)*(div+1)+j);
878 int DesertPillars::main()
881 return Application::main();
884 void DesertPillars::tick()
886 Time::TimeStamp t = Time::now();
894 camera_angle += (dt/Time::sec)*M_PI*2/30;
895 if(camera_angle>M_PI*4)
896 camera_angle -= M_PI*4;
897 float h = 3+(1-cos(camera_angle*1.5))*3;
898 float r = sqrt(225-h*h);
899 camera.set_position(GL::Vector3(cos(camera_angle)*r, sin(camera_angle)*r, 1.5+h));
900 camera.look_at(GL::Vector3(0, 0, 2));
905 cube_angle += (dt/Time::sec)*M_PI*2/20;
906 GL::Matrix cube_matrix;
907 cube_matrix.translate(0, 0, 2.5);
908 cube_matrix.rotate(cube_angle, 0, 0, 1);
909 cube_matrix.rotate(cube_angle*0.5, 1, 0, 0);
910 cube->set_matrix(cube_matrix);
915 cube_phase += (dt/Time::sec)/5;
925 float x = cube_phase*5;
927 cube->set_spherify((1-x)*cube_shapes[(cube_shape+3)%4]+x*cube_shapes[cube_shape]);
930 cube->set_spherify(cube_shapes[cube_shape]);
934 GL::Framebuffer::system().clear(GL::COLOR_BUFFER_BIT|GL::DEPTH_BUFFER_BIT);
936 gl_context.swap_buffers();
939 void DesertPillars::key_press(unsigned key)
941 if(key==Input::KEY_ESC)
943 else if(key==Input::KEY_SPACE)
944 camera_stopped = !camera_stopped;
945 else if(key==Input::KEY_F)
946 cube_frozen = !cube_frozen;
947 else if(key==Input::KEY_S)
948 cube_stopped = !cube_stopped;
952 DesertPillars::ObjectData::ObjectData():
957 DesertPillars::ObjectData::~ObjectData()
964 DesertPillars::Cube::Cube(const GL::Object &obj):
965 GL::AnimatedObject(obj)
967 shdata.uniform("reflectivity", 0.5f);
970 void DesertPillars::Cube::set_spherify(float s)
972 shdata.uniform("spherify", s);
975 void DesertPillars::Cube::setup_render(GL::Renderer &renderer, const GL::Tag &tag) const
977 AnimatedObject::setup_render(renderer, tag);
978 renderer.add_shader_data(shdata);