LightRayExample(void)
	 : gl()
	 , mesh_loader(
		mesh_input.stream,
		shapes::ObjMesh::LoadingOptions(false).Normals()
	), meshes(List("Position")("Normal").Get(), mesh_loader)
	 , fan_index(mesh_loader.GetMeshIndex("Fan"))
	 , light_position(0.0, 0.0, -100.0)
	 , vert_shader()
	 , mask_prog(vert_shader)
	 , mask_vao(meshes.VAOForProgram(mask_prog))
	 , draw_prog(vert_shader)
	 , draw_vao(meshes.VAOForProgram(draw_prog))
	 , shadow_tex_unit(0)
	 , light_tex_unit(1)
	{
		mesh_input.stream.close();

		gl.ClearDepth(1.0f);
		gl.Enable(Capability::DepthTest);

		RenderShadowMap(512);

		SetupLightMask();

		mask_prog.Use();
		mask_prog.light_position.Set(light_position);

		draw_prog.Use();
		draw_prog.light_position.Set(light_position);

		gl.ClearColor(1.0f, 1.0f, 1.0f, 0.0f);
	}
	ObjMeshExample(void)
	 : gl()
	 , objects(load_objects())
	 , depth_prog()
	 , draw_prog()
	 , depth_vao(objects.VAOForProgram(depth_prog))
	 , draw_vao(objects.VAOForProgram(draw_prog))
	{
		UniformSampler(draw_prog, "DepthTex").Set(0);
		Texture::Active(0);
		depth_tex.Bind(Texture::Target::Rectangle);

		gl.ClearColor(0.8f, 0.8f, 0.7f, 0.0f);
		gl.Enable(Capability::DepthTest);
		gl.Enable(Capability::CullFace);
	}
	BarDisplay(void)
	 : gl()
	 , side(128)
	 , shadow_size(512)
	 , cube(
		List("Position")("Normal").Get(),
		shapes::Cube(0.95, 1.0, 0.95, 0.0, 0.5, 0.0)
	), display_vao(cube.VAOForProgram(display_prog))
	 , shadow_vao(cube.VAOForProgram(shadow_prog))
	{
		init_shadows();
		init_heights();
		init_offsets();

		auto light_proj = CamMatrixf::PerspectiveX(
			Degrees(74),
			1.0,
			1, 3*side
		);
		display_prog.light_proj_matrix.Set(light_proj);
		shadow_prog.projection_matrix.Set(light_proj);
		gl.PolygonOffset(4.0, 4.0);
	}
	void RenderShadowMap(GLuint size)
	{
		// matrices
		auto lt_proj= CamMatrixf::PerspectiveX(Degrees(12), 1.0, 85.0, 110.0);
		auto light = CamMatrixf::LookingAt(light_position, Vec3f());
		// setup the texture
		Texture::Active(shadow_tex_unit);

		mask_prog.Use();
		Uniform<Mat4f>(mask_prog, "LightMatrix").Set(lt_proj*light);
		UniformSampler(mask_prog, "ShadowMap").Set(GLuint(shadow_tex_unit));

		draw_prog.Use();
		Uniform<Mat4f>(draw_prog, "LightMatrix").Set(lt_proj*light);
		UniformSampler(draw_prog, "ShadowMap").Set(GLuint(shadow_tex_unit));

		Texture::Target tex_tgt = Texture::Target::_2D;
		shadow_map.Bind(tex_tgt);
		Texture::MinFilter(tex_tgt, TextureMinFilter::Linear);
		Texture::MagFilter(tex_tgt, TextureMagFilter::Linear);
		Texture::WrapS(tex_tgt, TextureWrap::ClampToEdge);
		Texture::WrapT(tex_tgt, TextureWrap::ClampToEdge);
		Texture::CompareMode(tex_tgt, TextureCompareMode::CompareRefToTexture);
		Texture::Image2D(
			tex_tgt,
			0,
			PixelDataInternalFormat::DepthComponent32,
			size, size,
			0,
			PixelDataFormat::DepthComponent,
			PixelDataType::Float,
			nullptr
		);

		// create shadow program
		ShadowProgram shadow_prog(vert_shader);

		// VAO for the meshes in shadow program
		VertexArray vao = meshes.VAOForProgram(shadow_prog);
		vao.Bind();

		// FBO for offscreen rendering of the shadow map
		Framebuffer::Target fbo_tgt = Framebuffer::Target::Draw;
		Framebuffer fbo;
		fbo.Bind(fbo_tgt);
		Framebuffer::AttachTexture(fbo_tgt, FramebufferAttachment::Depth, shadow_map, 0);

		// RBO for offscreen rendering
		Renderbuffer::Target rbo_tgt = Renderbuffer::Target::Renderbuffer;
		Renderbuffer rbo;
		rbo.Bind(rbo_tgt);
		Renderbuffer::Storage(rbo_tgt, PixelDataInternalFormat::RGBA, size, size);
		Framebuffer::AttachRenderbuffer(fbo_tgt, FramebufferAttachment::Color, rbo);

		// setup the matrices
		shadow_prog.projection_matrix.Set(lt_proj);
		shadow_prog.camera_matrix.Set(light);

		// setup and clear the viewport
		gl.Viewport(size, size);
		gl.Clear().DepthBuffer();

		// draw the meshes
		gl.PolygonOffset(1.0, 1.0);
		gl.Enable(Capability::PolygonOffsetFill);
		meshes.Draw();
		gl.Disable(Capability::PolygonOffsetFill);
		gl.Finish();

		// bind the default framebuffer
		DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
	}