void RenderingEngine::Render(float theta) const
{
    const float distance = 10;
    const vec3 target(0, -0.15, 0);
    const vec3 up(0, 1, 0);

    vec3 eye(0, -0.15, distance * 2);
    mat4 view = mat4::LookAt(eye, target, up);
    
    glUseProgram(m_simple.Program);
    glUniformMatrix4fv(m_simple.Uniforms.Modelview, 1, 0, view.Pointer());
    glDepthFunc(GL_ALWAYS);
    glBindTexture(GL_TEXTURE_2D, m_textures.Metal);
    
    RenderDrawable(m_quad, m_simple);
        
    eye = vec3(0, 0, distance);
    view = mat4::LookAt(eye, target, up);
    
    const mat4 model = mat4::RotateY(theta * 180.0f / 3.14f);
    const mat3 model3x3 = model.ToMat3();
    const mat4 modelview = model * view;

    vec4 eyeWorldSpace(0, 0, -10, 1);
    vec4 eyeObjectSpace = model * eyeWorldSpace;

    glUseProgram(m_cubemap.Program);
    glUniform3fv(m_cubemap.Uniforms.EyePosition, 1, eyeObjectSpace.Pointer());
    glUniformMatrix4fv(m_cubemap.Uniforms.Modelview, 1, 0, modelview.Pointer());
    glUniformMatrix3fv(m_cubemap.Uniforms.Model, 1, 0, model3x3.Pointer());
    glBindTexture(GL_TEXTURE_CUBE_MAP, m_textures.Cubemap);
    glEnableVertexAttribArray(m_cubemap.Attributes.Normal);
    glDepthFunc(GL_LESS);
    
    RenderDrawable(m_kleinBottle, m_cubemap);
}
void RenderingEngine::Render(float theta) const
{
    glViewport(0, 0, m_size.x, m_size.y);
    glEnable(GL_DEPTH_TEST);

    // Set the render target to the full size offscreen buffer.
    glBindTexture(GL_TEXTURE_2D, m_textures.TombWindow);
    glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffers.Scene);
    glBindRenderbuffer(GL_RENDERBUFFER, m_renderbuffers.SceneColor);

    // Blit the background texture.
    glUseProgram(m_blitting.Program);
    glUniform1f(m_blitting.Uniforms.Threshold, 0);
    glDepthFunc(GL_ALWAYS);
    RenderDrawable(m_quad, m_blitting);

    // Set the light position.
    glUseProgram(m_lighting.Program);
    vec4 lightPosition(0.25, 0.25, 1, 0);
    glUniform3fv(m_lighting.Uniforms.LightPosition, 1, lightPosition.Pointer());

    // Set the model-view transform.
    const float distance = 10;
    const vec3 target(0, -0.15, 0);
    const vec3 up(0, 1, 0);
    const vec3 eye = vec3(0, 0, distance);
    const mat4 view = mat4::LookAt(eye, target, up);
    const mat4 model = mat4::RotateY(theta * 180.0f / 3.14f);
    const mat4 modelview = model * view;
    glUniformMatrix4fv(m_lighting.Uniforms.Modelview, 1, 0, modelview.Pointer());

    // Set the normal matrix.
    mat3 normalMatrix = modelview.ToMat3();
    glUniformMatrix3fv(m_lighting.Uniforms.NormalMatrix, 1, 0, normalMatrix.Pointer());

    // Render the Klein Bottle.
    glDepthFunc(GL_LESS);
    glEnableVertexAttribArray(m_lighting.Attributes.Normal);
    RenderDrawable(m_kleinBottle, m_lighting);
    
    // Set up OpenGL for rendering full-screen quads.
    glUseProgram(m_blitting.Program);
    glUniform1f(m_blitting.Uniforms.Threshold, 0.85);
    glDisable(GL_DEPTH_TEST);

    // Downsample the rendered scene.
    int w = m_size.x, h = m_size.y;
    for (int i = 0; i < OffscreenCount; ++i, w >>= 1, h >>= 1) {
        glViewport(0, 0, w, h);
        glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffers.Offscreen[i]);
        glBindRenderbuffer(GL_RENDERBUFFER, m_renderbuffers.OffscreenColor[i]);
        glBindTexture(GL_TEXTURE_2D, i ? m_textures.Offscreen[i - 1] : m_textures.Scene);
        RenderDrawable(m_quad, m_blitting);
        glUniform1f(m_blitting.Uniforms.Threshold, 0);
    }
    
    // Accumulate the downsampled buffers onto the backbuffer.
    glUniform1f(m_blitting.Uniforms.Threshold, 0);
    glViewport(0, 0, m_size.x, m_size.y);
    glEnable(GL_BLEND);
    glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffers.Offscreen[0]);
    glBindRenderbuffer(GL_RENDERBUFFER, m_renderbuffers.OffscreenColor[0]);
    for (int i = 0; i < OffscreenCount; ++i) {
        glBindTexture(GL_TEXTURE_2D, m_textures.Offscreen[i]);
        RenderDrawable(m_quad, m_blitting);
    }

    // Blit the full-color buffer onto the backbuffer.
    glDisable(GL_BLEND);
    glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffers.Backbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, m_renderbuffers.BackbufferColor);
    glBindTexture(GL_TEXTURE_2D, m_textures.Scene);
    RenderDrawable(m_quad, m_blitting);
    
    // Add the bloom texture onto the backbuffer.
    glEnable(GL_BLEND);
    glBindTexture(GL_TEXTURE_2D, m_textures.Offscreen[0]);
    RenderDrawable(m_quad, m_blitting);
    glDisable(GL_BLEND);
}