Browse Source

Shadow Mapping in igl::opengl::glfw::Viewer (#2155)

* floor and working tutorial example

* working shadows with tutorial

* shadows + matcaps

* fiddling
Alec Jacobson 2 years ago
parent
commit
50ac379c53

+ 67 - 11
include/igl/opengl/MeshGL.cpp

@@ -264,17 +264,29 @@ R"(#version 150
   out vec4 Kai;
   out vec4 Kdi;
   out vec4 Ksi;
+  uniform mat4 shadow_view;
+  uniform mat4 shadow_proj;
+  uniform bool shadow_pass;
+  uniform bool is_shadow_mapping;
+  out vec4 position_shadow;
 
   void main()
   {
     position_eye = vec3 (view * vec4 (position, 1.0));
-    normal_eye = vec3 (normal_matrix * vec4 (normal, 0.0));
-    normal_eye = normalize(normal_eye);
-    gl_Position = proj * vec4 (position_eye, 1.0); //proj * view * vec4(position, 1.0);"
-    Kai = Ka;
-    Kdi = Kd;
-    Ksi = Ks;
-    texcoordi = texcoord;
+    if(!shadow_pass)
+    {
+      if(is_shadow_mapping)
+      {
+        position_shadow = shadow_proj * shadow_view * vec4(position, 1.0);
+      }
+      normal_eye = vec3 (normal_matrix * vec4 (normal, 0.0));
+      normal_eye = normalize(normal_eye);
+      Kai = Ka;
+      Kdi = Kd;
+      Ksi = Ks;
+      texcoordi = texcoord;
+    }
+    gl_Position = proj * vec4 (position_eye, 1.0); //proj * view * vec4(position, 1.0);
   }
 )";
 
@@ -285,6 +297,9 @@ R"(#version 150
   uniform vec4 fixed_color;
   in vec3 position_eye;
   in vec3 normal_eye;
+  uniform bool is_directional_light;
+  uniform bool is_shadow_mapping;
+  uniform bool shadow_pass;
   uniform vec3 light_position_eye;
   vec3 Ls = vec3 (1, 1, 1);
   vec3 Ld = vec3 (1, 1, 1);
@@ -299,19 +314,60 @@ R"(#version 150
   uniform float texture_factor;
   uniform float matcap_factor;
   uniform float double_sided;
+
+  uniform sampler2D shadow_tex;
+  in vec4 position_shadow;
+
   out vec4 outColor;
   void main()
   {
+    if(shadow_pass)
+    {
+      // Would it be better to have a separate no-op frag shader?
+      outColor = vec4(0.56,0.85,0.77,1.);
+      return;
+    }
+    // If is_directional_light then assume normalized
+    vec3 direction_to_light_eye = light_position_eye;
+    if(! is_directional_light)
+    {
+      vec3 vector_to_light_eye = light_position_eye - position_eye;
+      direction_to_light_eye = normalize(vector_to_light_eye);
+    }
+    float shadow = 1.0;
+    if(is_shadow_mapping)
+    {
+      vec3 shadow_pos = (position_shadow.xyz / position_shadow.w) * 0.5 + 0.5; 
+      float currentDepth = shadow_pos.z;
+      //float bias = 0.005;
+      float ddd = max(dot(normalize(normal_eye), direction_to_light_eye),0);
+      float bias = max(0.02 * (1.0 - ddd), 0.005);  
+      // 5-point stencil
+      if(shadow_pos.z < 1.0)
+      {
+        float closestDepth = texture( shadow_tex , shadow_pos.xy).r;
+        shadow = currentDepth - bias >= closestDepth ? 0.0 : 1.0;  
+        vec2 texelSize = 1.0 / textureSize(shadow_tex, 0);
+        for(int x = -1; x <= 1; x+=2)
+        {
+          for(int y = -1; y <= 1; y+=2)
+          {
+            float pcfDepth = texture(shadow_tex,  shadow_pos.xy + vec2(x, y) * texelSize).r; 
+            shadow += currentDepth - bias >= pcfDepth ? 0.0 : 1.0;        
+          }    
+        }
+        shadow /= 5.0;
+      }
+    }
+
     if(matcap_factor == 1.0f)
     {
       vec2 uv = normalize(normal_eye).xy * 0.5 + 0.5;
-      outColor = texture(tex, uv);
+      outColor = mix(Kai,texture(tex, uv),shadow);
     }else
     {
       vec3 Ia = La * vec3(Kai);    // ambient intensity
 
-      vec3 vector_to_light_eye = light_position_eye - position_eye;
-      vec3 direction_to_light_eye = normalize (vector_to_light_eye);
       float dot_prod = dot (direction_to_light_eye, normalize(normal_eye));
       float clamped_dot_prod = abs(max (dot_prod, -double_sided));
       vec3 Id = Ld * vec3(Kdi) * clamped_dot_prod;    // Diffuse intensity
@@ -322,7 +378,7 @@ R"(#version 150
       dot_prod_specular = float(abs(dot_prod)==dot_prod) * abs(max (dot_prod_specular, -double_sided));
       float specular_factor = pow (dot_prod_specular, specular_exponent);
       vec3 Is = Ls * vec3(Ksi) * specular_factor;    // specular intensity
-      vec4 color = vec4(lighting_factor * (Is + Id) + Ia + (1.0-lighting_factor) * vec3(Kdi),(Kai.a+Ksi.a+Kdi.a)/3);
+      vec4 color = vec4(Ia + shadow*(lighting_factor * (Is + Id) + (1.0-lighting_factor) * vec3(Kdi)),(Kai.a+Ksi.a+Kdi.a)/3);
       outColor = mix(vec4(1,1,1,1), texture(tex, texcoordi), texture_factor) * color;
       if (fixed_color != vec4(0.0)) outColor = fixed_color;
     }

+ 162 - 1
include/igl/opengl/ViewerCore.cpp

@@ -10,6 +10,7 @@
 #include "ViewerData.h"
 #include "gl.h"
 #include "../quat_to_mat.h"
+#include "../null.h"
 #include "../snap_to_fixed_up.h"
 #include "../look_at.h"
 #include "../frustum.h"
@@ -17,6 +18,8 @@
 #include "../massmatrix.h"
 #include "../barycenter.h"
 #include "../PI.h"
+#include "report_gl_error.h"
+#include "read_pixels.h"
 #include <Eigen/Geometry>
 #include <iostream>
 
@@ -175,11 +178,33 @@ IGL_INLINE void igl::opengl::ViewerCore::draw(
   GLint matcap_factori        = glGetUniformLocation(data.meshgl.shader_mesh,"matcap_factor");
   GLint double_sidedi         = glGetUniformLocation(data.meshgl.shader_mesh,"double_sided");
 
+  const bool eff_is_directional_light = is_directional_light || is_shadow_mapping;
   glUniform1f(specular_exponenti, data.shininess);
-  glUniform3fv(light_position_eyei, 1, light_position.data());
+  if(eff_is_directional_light)
+  {
+    Eigen::Vector3f light_direction  = light_position.normalized();
+    glUniform3fv(light_position_eyei, 1, light_direction.data());
+  }else
+  {
+    glUniform3fv(light_position_eyei, 1, light_position.data());
+  }
+  if(is_shadow_mapping)
+  {
+    glUniformMatrix4fv(glGetUniformLocation(data.meshgl.shader_mesh,"shadow_view"), 1, GL_FALSE, shadow_view.data());
+    glUniformMatrix4fv(glGetUniformLocation(data.meshgl.shader_mesh,"shadow_proj"), 1, GL_FALSE, shadow_proj.data());
+    glActiveTexture(GL_TEXTURE0+1);
+    glBindTexture(GL_TEXTURE_2D, shadow_depth_tex);
+    {
+      glUniform1i(glGetUniformLocation(data.meshgl.shader_mesh,"shadow_tex"), 1);
+    }
+  }
   glUniform1f(lighting_factori, lighting_factor); // enables lighting
   glUniform4f(fixed_colori, 0.0, 0.0, 0.0, 0.0);
 
+  glUniform1i(glGetUniformLocation(data.meshgl.shader_mesh,"is_directional_light"),eff_is_directional_light);
+  glUniform1i(glGetUniformLocation(data.meshgl.shader_mesh,"is_shadow_mapping"),is_shadow_mapping);
+  glUniform1i(glGetUniformLocation(data.meshgl.shader_mesh,"shadow_pass"),false);
+
   if (data.V.rows()>0)
   {
     // Render fill
@@ -252,6 +277,74 @@ IGL_INLINE void igl::opengl::ViewerCore::draw(
     draw_labels(data, data.meshgl.custom_labels);
 }
 
+IGL_INLINE void igl::opengl::ViewerCore::initialize_shadow_pass()
+{
+  // attach buffers
+  glBindFramebuffer(GL_FRAMEBUFFER,   shadow_depth_fbo);
+  glBindRenderbuffer(GL_RENDERBUFFER, shadow_color_rbo);
+  // clear buffer 
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+  // In the libigl viewer setup, each mesh has its own shader program. This is
+  // kind of funny because they should all be the same, just different uniform
+  // values.
+  glViewport(0,0,shadow_width,shadow_height);
+  // Assumes light is directional
+  assert(is_directional_light);
+  Eigen::Vector3f camera_eye = light_position.normalized()*5;
+  Eigen::Vector3f camera_up = [&camera_eye]()
+    {
+      Eigen::Matrix<float,3,2> T;
+      igl::null(camera_eye.transpose().eval(),T);
+      return T.col(0);
+    }();
+  Eigen::Vector3f camera_center = this->camera_center;
+  // Same camera parameters except 2× field of view and reduced far plane
+  float camera_view_angle =               2*this->camera_view_angle;
+  float camera_dnear =                      this->camera_dnear;
+  float camera_dfar =                       this->camera_dfar;
+  Eigen::Quaternionf trackball_angle =      this->trackball_angle;
+  float camera_zoom =                       1;//this->camera_zoom;
+  float camera_base_zoom =                  this->camera_base_zoom;
+  Eigen::Vector3f camera_translation =      this->camera_translation;
+  Eigen::Vector3f camera_base_translation = this->camera_base_translation;
+  camera_dfar = exp2( 0.5 * ( log2(camera_dnear) + log2(camera_dfar)));
+  igl::look_at( camera_eye, camera_center, camera_up, shadow_view);
+  shadow_view = shadow_view
+    * (trackball_angle * Eigen::Scaling(camera_zoom * camera_base_zoom)
+    * Eigen::Translation3f(camera_translation + camera_base_translation)).matrix();
+
+  float length = (camera_eye - camera_center).norm();
+  float h = tan(camera_view_angle/360.0 * igl::PI) * (length);
+  igl::ortho(-h*shadow_width/shadow_height, h*shadow_width/shadow_height, -h, h, camera_dnear, camera_dfar,shadow_proj);
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::deinitialize_shadow_pass()
+{
+  glBindFramebuffer(GL_FRAMEBUFFER, 0);
+  glBindRenderbuffer(GL_RENDERBUFFER, 0);
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::draw_shadow_pass(
+  ViewerData& data,
+  bool update_matrices)
+{
+  if (data.dirty)
+  {
+    data.updateGL(data, data.invert_normals, data.meshgl);
+    data.dirty = igl::opengl::MeshGL::DIRTY_NONE;
+  }
+  data.meshgl.bind_mesh();
+  // Send transformations to the GPU as if rendering from shadow point of view
+  GLint viewi  = glGetUniformLocation(data.meshgl.shader_mesh,"view");
+  GLint proji  = glGetUniformLocation(data.meshgl.shader_mesh,"proj");
+  glUniformMatrix4fv(viewi, 1, GL_FALSE, shadow_view.data());
+  glUniformMatrix4fv(proji, 1, GL_FALSE, shadow_proj.data());
+  glUniform1i(glGetUniformLocation(data.meshgl.shader_mesh,"shadow_pass"),true);
+  data.meshgl.draw_mesh(true);
+  glUniform1i(glGetUniformLocation(data.meshgl.shader_mesh,"shadow_pass"),false);
+
+}
+
 IGL_INLINE void igl::opengl::ViewerCore::draw_buffer(
   ViewerData& data,
   bool update_matrices,
@@ -442,6 +535,11 @@ IGL_INLINE igl::opengl::ViewerCore::ViewerCore()
 
   // Default lights settings
   light_position << 0.0f, 0.3f, 0.0f;
+  is_directional_light = false;
+  is_shadow_mapping = false;
+  shadow_width =  2056;
+  shadow_height = 2056;
+
   lighting_factor = 1.0f; //on
 
   // Default trackball
@@ -472,8 +570,71 @@ IGL_INLINE igl::opengl::ViewerCore::ViewerCore()
 
 IGL_INLINE void igl::opengl::ViewerCore::init()
 {
+  delete_shadow_buffers();
+  generate_shadow_buffers();
 }
 
 IGL_INLINE void igl::opengl::ViewerCore::shut()
 {
+  delete_shadow_buffers();
+}
+
+IGL_INLINE void igl::opengl::ViewerCore::delete_shadow_buffers()
+{
+  glDeleteTextures(1,&shadow_depth_tex);
+  glDeleteFramebuffers(1,&shadow_depth_fbo);
+  glDeleteRenderbuffers(1,&shadow_color_rbo);
 }
+
+IGL_INLINE void igl::opengl::ViewerCore::generate_shadow_buffers()
+{
+  // Create a texture for writing the shadow map depth values into
+  {
+    glDeleteTextures(1,&shadow_depth_tex);
+    glGenTextures(1, &shadow_depth_tex);
+    glBindTexture(GL_TEXTURE_2D, shadow_depth_tex);
+    // Should this be using double/float precision?
+    glTexImage2D(
+      GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
+      shadow_width,
+      shadow_height,
+      0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, Eigen::Vector4f(1,1,1,1).data() );
+    glBindTexture(GL_TEXTURE_2D, 0);
+  }
+  // Generate a framebuffer with depth attached to this texture and color
+  // attached to a render buffer object
+  glGenFramebuffers(1, &shadow_depth_fbo);
+  glBindFramebuffer(GL_FRAMEBUFFER, shadow_depth_fbo);
+  // Attach depth texture
+  glFramebufferTexture2D(
+      GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
+      shadow_depth_tex,0);
+  // Generate a render buffer to write colors into. Low precision we don't
+  // care about them. Is there a way to not write/compute them at? Probably
+  // just need to change fragment shader.
+  glGenRenderbuffers(1,&shadow_color_rbo);
+  glBindRenderbuffer(GL_RENDERBUFFER,shadow_color_rbo);
+  glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, shadow_width, shadow_height);
+  // Attach color buffer
+  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+      GL_RENDERBUFFER, shadow_color_rbo);
+  //Does the GPU support current FBO configuration?
+  GLenum status;
+  status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+  switch(status)
+  {
+    case GL_FRAMEBUFFER_COMPLETE:
+      break;
+    default:
+      printf("[ViewerCore] Error: We failed to set up a good FBO: %d\n",status);
+      assert(false);
+  }
+  glBindRenderbuffer(GL_RENDERBUFFER, 0);
+  glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+

+ 16 - 0
include/igl/opengl/ViewerCore.h

@@ -27,6 +27,7 @@ class ViewerData;
 class ViewerCore
 {
 public:
+  using GLuint = MeshGL::GLuint;
   IGL_INLINE ViewerCore();
 
   // Initialization
@@ -78,6 +79,9 @@ public:
   //   update_matrices  whether to update view, proj, and norm matrices in
   //     shaders
   IGL_INLINE void draw(ViewerData& data, bool update_matrices = true);
+  IGL_INLINE void initialize_shadow_pass();
+  IGL_INLINE void deinitialize_shadow_pass();
+  IGL_INLINE void draw_shadow_pass(ViewerData& data, bool update_matrices = true);
   // Render given ViewerData to a buffer. The width and height are determined by
   // non-zeros dimensions of R (and G,B,A should match) or – if both are zero —
   // are set to this core's viewport sizes.
@@ -128,6 +132,10 @@ public:
   // Check whether a ViewerData visualization option is set for this viewport
   IGL_INLINE bool is_set(unsigned int property_mask) const;
 
+  // ------------------- Function
+  IGL_INLINE void delete_shadow_buffers();
+  IGL_INLINE void generate_shadow_buffers();
+
   // ------------------- Properties
 
   // Unique identifier
@@ -138,6 +146,12 @@ public:
 
   // Lighting
   Eigen::Vector3f light_position;
+  bool is_directional_light;
+  bool is_shadow_mapping;
+  GLuint shadow_width, shadow_height;
+  GLuint shadow_depth_tex;
+  GLuint shadow_depth_fbo;
+  GLuint shadow_color_rbo;
   float lighting_factor;
 
   RotationType rotation_type;
@@ -172,6 +186,8 @@ public:
   Eigen::Matrix4f view;
   Eigen::Matrix4f proj;
   Eigen::Matrix4f norm;
+  Eigen::Matrix4f shadow_view;
+  Eigen::Matrix4f shadow_proj;
   public:
       EIGEN_MAKE_ALIGNED_OPERATOR_NEW
 };

+ 59 - 2
include/igl/opengl/glfw/Viewer.cpp

@@ -273,7 +273,10 @@ namespace glfw
     {
       data.meshgl.free();
     }
-    core().shut(); // Doesn't do anything
+    for(auto &core : this->core_list)
+    {
+      core.shut(); 
+    }
     shutdown_plugins();
     glfwDestroyWindow(window);
     glfwTerminate();
@@ -282,7 +285,10 @@ namespace glfw
 
   IGL_INLINE void Viewer::init()
   {
-    core().init(); // Doesn't do anything
+    for(auto &core : this->core_list)
+    {
+      core.init();
+    }
 
     if (callback_init)
       if (callback_init(*this))
@@ -359,6 +365,7 @@ namespace glfw
   I,i     Toggle invert normals
   L,l     Toggle wireframe
   O,o     Toggle orthographic/perspective projection
+  S,s     Toggle shadows
   T,t     Toggle filled faces
   Z       Snap to canonical view
   [,]     Toggle between rotation control types (trackball, two-axis
@@ -553,6 +560,39 @@ namespace glfw
         core().orthographic = !core().orthographic;
         return true;
       }
+      case 'S':
+      case 's':
+      {
+        if(core().is_directional_light)
+        {
+          core().is_shadow_mapping = !core().is_shadow_mapping;
+        }else
+        {
+          if(core().is_shadow_mapping)
+          {
+            core().is_shadow_mapping = false;
+          }else
+          {
+            // The light_position when !is_directional_light is interpretted as
+            // a position relative to the _eye_ (not look-at) position of the
+            // camera.
+            //
+            // Meanwhile shadows only current work in is_directional_light mode.
+            //
+            // If the user wants to flip back and forth between [positional lights
+            // without shadows] and [directional lights with shadows] then they
+            // can high-jack this key_pressed with a callback.
+            // 
+            // Until shadows support positional lights, let's switch to
+            // directional lights here and match the direction best as possible to
+            // the current light position.
+            core().is_directional_light = true;
+            core().light_position = core().light_position + core().camera_eye;
+            core().is_shadow_mapping = true;
+          }
+        }
+        return true;
+      }
       case 'T':
       case 't':
       {
@@ -911,6 +951,23 @@ namespace glfw
         return;
       }
     }
+    
+    // Shadow pass
+    for (auto& core : core_list)
+    {
+      if(core.is_shadow_mapping)
+      {
+        core.initialize_shadow_pass();
+        for (auto& mesh : data_list)
+        {
+          if (mesh.is_visible & core.id)
+          {
+            core.draw_shadow_pass(mesh);
+          }
+        }
+        core.deinitialize_shadow_pass();
+      }
+    }
 
     for (auto& core : core_list)
     {

+ 46 - 0
include/igl/opengl/read_pixels.cpp

@@ -0,0 +1,46 @@
+#include "read_pixels.h"
+
+template <typename T>
+void igl::opengl::read_pixels(
+  const GLuint width,
+  const GLuint height,
+  Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> & R,
+  Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> & G,
+  Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> & B,
+  Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> & A,
+  Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> & D)
+{
+  R.resize(width,height);
+  G.resize(width,height);
+  B.resize(width,height);
+  A.resize(width,height);
+  D.resize(width,height);
+  typedef typename std::conditional< std::is_floating_point<T>::value,GLfloat,GLubyte>::type GLType;
+  GLenum type = std::is_floating_point<T>::value ?  GL_FLOAT : GL_UNSIGNED_BYTE;
+  GLType* pixels = (GLType*)calloc(width*height*4,sizeof(GLType));
+  GLType * depth = (GLType*)calloc(width*height*1,sizeof(GLType));
+  glReadPixels(0, 0,width, height,GL_RGBA,            type, pixels);
+  glReadPixels(0, 0,width, height,GL_DEPTH_COMPONENT, type, depth);
+  int count = 0;
+  for (unsigned j=0; j<height; ++j)
+  {
+    for (unsigned i=0; i<width; ++i)
+    {
+      R(i,j) = pixels[count*4+0];
+      G(i,j) = pixels[count*4+1];
+      B(i,j) = pixels[count*4+2];
+      A(i,j) = pixels[count*4+3];
+      D(i,j) = depth[count*1+0];
+      ++count;
+    }
+  }
+  // Clean up
+  free(pixels);
+  free(depth);
+}
+
+#ifdef IGL_STATIC_LIBRARY
+template void igl::opengl::read_pixels<unsigned char>(unsigned int, unsigned int, Eigen::Matrix<unsigned char, -1, -1, 0, -1, -1>&, Eigen::Matrix<unsigned char, -1, -1, 0, -1, -1>&, Eigen::Matrix<unsigned char, -1, -1, 0, -1, -1>&, Eigen::Matrix<unsigned char, -1, -1, 0, -1, -1>&, Eigen::Matrix<unsigned char, -1, -1, 0, -1, -1>&);
+template void igl::opengl::read_pixels<double>(unsigned int, unsigned int, Eigen::Matrix<double, -1, -1, 0, -1, -1>&, Eigen::Matrix<double, -1, -1, 0, -1, -1>&, Eigen::Matrix<double, -1, -1, 0, -1, -1>&, Eigen::Matrix<double, -1, -1, 0, -1, -1>&, Eigen::Matrix<double, -1, -1, 0, -1, -1>&);
+template void igl::opengl::read_pixels<float>(unsigned int, unsigned int, Eigen::Matrix<float, -1, -1, 0, -1, -1>&, Eigen::Matrix<float, -1, -1, 0, -1, -1>&, Eigen::Matrix<float, -1, -1, 0, -1, -1>&, Eigen::Matrix<float, -1, -1, 0, -1, -1>&, Eigen::Matrix<float, -1, -1, 0, -1, -1>&);
+#endif 

+ 39 - 0
include/igl/opengl/read_pixels.h

@@ -0,0 +1,39 @@
+#ifndef IGL_OPENGL_READ_PIXELS_H
+#define IGL_OPENGL_READ_PIXELS_H
+
+#include "../igl_inline.h"
+#include "gl.h"
+#include <Eigen/Core>
+
+namespace igl
+{
+namespace opengl
+{
+  // Read full viewport into color, alpha and depth arrays suitable for
+  // igl::png::writePNG
+  // Inputs:
+  //   width  width of viewport
+  //   height height of viewport
+  // Outputs:
+  //   R  width by height list of red values
+  //   G  width by height list of green values
+  //   B  width by height list of blue values
+  //   A  width by height list of alpha values
+  //   D  width by height list of depth values
+  template <typename T>
+  IGL_INLINE void read_pixels(
+    const GLuint width,
+    const GLuint height,
+    Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> & R,
+    Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> & G,
+    Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> & B,
+    Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> & A,
+    Eigen::Matrix<T,Eigen::Dynamic,Eigen::Dynamic> & D);
+}
+}
+
+#ifndef IGL_STATIC_LIBRARY
+#include "read_pixels.cpp"
+#endif
+
+#endif

+ 144 - 0
tutorial/113_Shadows/main.cpp

@@ -0,0 +1,144 @@
+#include <igl/opengl/glfw/Viewer.h>
+#include <igl/read_triangle_mesh.h>
+#include <igl/triangulated_grid.h>
+#include <igl/png/readPNG.h>
+#include <igl/get_seconds.h>
+#include <igl/PI.h>
+#include <Eigen/Core>
+
+#include <algorithm>
+
+void floor(const Eigen::MatrixXd & V, 
+    Eigen::MatrixXd & fV,
+    Eigen::MatrixXd & fU,
+    Eigen::MatrixXi & fF)
+{
+  igl::triangulated_grid(2,2,fU,fF);
+  fV = fU;
+  fV.array() -= 0.5;
+  fV.array() *= 2 * 2 * 
+    (V.colwise().maxCoeff() - V.colwise().minCoeff()).norm();
+  fV = (fV * (Eigen::Matrix<double,2,3>()<<1,0,0,0,0,-1).finished() ).eval();
+  fV.col(0).array() += 0.5*(V.col(0).minCoeff()+ V.col(0).maxCoeff());
+  fV.col(1).array() += V.col(1).minCoeff();
+  fV.col(2).array() += 0.5*(V.col(2).minCoeff()+ V.col(2).maxCoeff());
+}
+
+void checker_texture(const int s, const int f,
+  Eigen::Matrix<unsigned char, Eigen::Dynamic, Eigen::Dynamic> & X,
+  Eigen::Matrix<unsigned char, Eigen::Dynamic, Eigen::Dynamic> & A)
+{
+  X.resize(s*f,s*f);
+  A.resize(s*f,s*f);
+  for(int i = 0;i<s*f;i++)
+  {
+    const double x = double(i)/double(s*f-1)*2-1;
+    for(int j = 0;j<s*f;j++)
+    {
+      const int u = i/f;
+      const int v = j/f;
+      const double y = double(j)/double(s*f-1)*2-1;
+      const double r1 = std::min(std::max( (1.0 - sqrt(x*x+y*y))*1.0 ,0.0),1.0);
+      const double r3 = std::min(std::max( (1.0 - sqrt(x*x+y*y))*3.0 ,0.0),1.0);
+      //const double a = 3*r*r - 2*r*r*r;
+      const auto smooth_step = [](const double w)
+      {
+        return ((w * (w * 6.0 - 15.0) + 10.0) * w * w * w) ;
+      };
+      double a3 = smooth_step(r1);
+      double a1 = smooth_step(r1);
+      X(i,j) = (0.75+0.25*a1) * (u%2 == v%2 ? 245 : 235);
+      A(i,j) = a3 * 255;
+    }
+  }
+}
+
+int main(int argc, char *argv[])
+{
+  igl::opengl::glfw::Viewer vr;
+  Eigen::MatrixXd V;
+  Eigen::MatrixXi F;
+  igl::read_triangle_mesh(
+    argc>1?argv[1]: TUTORIAL_SHARED_PATH "/armadillo.obj",V,F);
+
+  // Create a floor
+  Eigen::MatrixXd fV, fU;
+  Eigen::MatrixXi fF;
+  floor(V,fV,fU,fF);
+  const int s = 16;
+  const int f = 100;
+  Eigen::Matrix<unsigned char, Eigen::Dynamic, Eigen::Dynamic> X;
+  Eigen::Matrix<unsigned char, Eigen::Dynamic, Eigen::Dynamic> A;
+  checker_texture(s,f,X,A);
+  vr.data().set_mesh(fV,fF);
+  vr.data().set_uv(fU);
+  vr.data().uniform_colors(Eigen::Vector3d(0.3,0.3,0.3),Eigen::Vector3d(0.8,0.8,0.8),Eigen::Vector3d(0,0,0));
+  vr.data().set_texture(X,X,X,A);
+  vr.data().show_texture = true;
+  vr.data().show_lines = false;
+
+  // Move the light a bit off center to cast a more visible shadow.
+  vr.core().light_position << 1.0f, 2.0f, 0.0f;
+  // For now, the default is a positional light with no shadows. Meanwhile,
+  // shadows only support a directional light. To best match the appearance of
+  // current lighting use this conversion when turning on shadows. In the
+  // future, hopefully this will reduce to just 
+  //     core().is_shadow_mapping = true
+  vr.core().is_directional_light = true;
+  vr.core().light_position = vr.core().light_position + vr.core().camera_eye;
+  vr.core().is_shadow_mapping = true;
+
+  // Send the main object to the viewer
+  vr.append_mesh();
+  vr.data().set_mesh(V,F);
+  vr.data().show_lines = false;
+  vr.data().set_face_based(true);
+
+  // If a second argument is present read it as a matcap
+  if(argc>2)
+  {
+    Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> R,G,B,A;
+    igl::png::readPNG(argv[2],R,G,B,A);
+    // If more args, read them as light direction
+    if(argc>2+3)
+    {
+      Eigen::Vector3f D;
+      D << std::atof(argv[3]), std::atof(argv[4]), std::atof(argv[5]);
+      D.normalize();
+      vr.core().light_position = D;
+      Eigen::Vector3d Ka(0.14,0.14,0.14);
+      if(argc>2+3+1 && std::atoi(argv[6]))
+      {
+        // Assume that color opposite D along umbra boundary is ambient color
+        const double s = -D(2)*1.0/sqrt((D(0)*D(0)+D(1)*D(1))*(D(0)*D(0)+D(1)*D(1)+D(2)*D(2))); 
+        const int i = ((D(0)*s)*0.5+0.5)*R.cols();
+        const int j = ((D(1)*s)*0.5+0.5)*R.rows();
+        Ka << double(R(i,j))/255.0, double(G(i,j))/255.0, double(B(i,j))/255.0;
+      }
+      std::cout<<"Ka : "<<Ka<<std::endl;
+      // viewer only exposes ambient color through per-face and per-vertex
+      // materials
+      vr.data().V_material_ambient.col(0).setConstant( Ka(0) );
+      vr.data().V_material_ambient.col(1).setConstant( Ka(1) );
+      vr.data().V_material_ambient.col(2).setConstant( Ka(2) );
+      vr.data().F_material_ambient.col(0).setConstant( Ka(0) );
+      vr.data().F_material_ambient.col(1).setConstant( Ka(1) );
+      vr.data().F_material_ambient.col(2).setConstant( Ka(2) );
+    }
+    vr.data().set_texture(R,G,B,A);
+    vr.data().use_matcap = true;
+  }
+  vr.core().is_animating = true;
+  vr.core().camera_zoom *= 1.5;
+  vr.callback_pre_draw = [&](decltype(vr)&)
+  {
+    if(vr.core().is_animating)
+    {
+      vr.core().trackball_angle = Eigen::AngleAxisf( 
+          sin(igl::get_seconds())*igl::PI*0.5,
+          Eigen::Vector3f(0,1,0));
+    }
+    return false;
+  };
+  vr.launch();
+}

+ 2 - 1
tutorial/CMakeLists.txt

@@ -25,9 +25,10 @@ if(LIBIGL_TUTORIALS_CHAPTER1)
     igl_add_tutorial(107_MultipleMeshes igl::glfw)
     igl_add_tutorial(108_MultipleViews igl::glfw)
     igl_add_tutorial(109_ImGuizmo igl::imgui)
-    igl_add_tutorial(112_Selection igl::imgui)
     igl_add_tutorial(110_MshView igl::glfw)
     igl_add_tutorial(111_MatCap igl::glfw igl::png)
+    igl_add_tutorial(112_Selection igl::imgui)
+    igl_add_tutorial(113_Shadows igl::imgui igl::png)
 endif()
 
 # Chapter 2