Teach //examples/shadows to draw a soft shadow

After this CL, this example actually draws a soft shadow. There are still a
number of shortcuts that we'll need to unwind in order to produce a fully
working system, but this CL is at least a start.

R=jeffbrown@google.com, kulakowski@chromium.org

Review URL: https://codereview.chromium.org/1810793002 .
diff --git a/examples/shadows/BUILD.gn b/examples/shadows/BUILD.gn
index f5477da..8e40db4 100644
--- a/examples/shadows/BUILD.gn
+++ b/examples/shadows/BUILD.gn
@@ -9,6 +9,8 @@
 
   sources = [
     "main.cc",
+    "penumbra_program.cc",
+    "penumbra_program.h",
     "shadows_app.cc",
     "shadows_app.h",
     "shadows_renderer.cc",
diff --git a/examples/shadows/penumbra_program.cc b/examples/shadows/penumbra_program.cc
new file mode 100644
index 0000000..8e551f1
--- /dev/null
+++ b/examples/shadows/penumbra_program.cc
@@ -0,0 +1,184 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "examples/shadows/penumbra_program.h"
+
+#include <memory>
+#include <GLES2/gl2.h>
+#include <glm/gtc/type_ptr.hpp>
+
+#include "base/logging.h"
+
+namespace examples {
+namespace {
+
+const char kVertexShaderSource[] = R"GLSL(
+uniform mat4 u_transform;
+
+attribute vec3 a_position;
+attribute vec3 a_occluder0;
+attribute vec3 a_occluder1;
+attribute vec3 a_occluder2;
+attribute vec3 a_occluder3;
+
+varying vec3 v_occluder0;
+varying vec3 v_occluder1;
+varying vec3 v_occluder2;
+varying vec3 v_occluder3;
+
+void main() {
+  gl_Position = u_transform * vec4(a_position, 1.0);
+
+  vec4 h_occluder0 = u_transform * vec4(a_occluder0, 1.0);
+  vec4 h_occluder1 = u_transform * vec4(a_occluder1, 1.0);
+  vec4 h_occluder2 = u_transform * vec4(a_occluder2, 1.0);
+  vec4 h_occluder3 = u_transform * vec4(a_occluder3, 1.0);
+
+  v_occluder0 = h_occluder0.xyz / h_occluder0.w;
+  v_occluder1 = h_occluder1.xyz / h_occluder1.w;
+  v_occluder2 = h_occluder2.xyz / h_occluder2.w;
+  v_occluder3 = h_occluder3.xyz / h_occluder3.w;
+}
+)GLSL";
+
+const char kFragmentShaderSource[] = R"GLSL(
+precision highp float;
+
+varying highp vec3 v_occluder0;
+varying highp vec3 v_occluder1;
+varying highp vec3 v_occluder2;
+varying highp vec3 v_occluder3;
+uniform vec2 u_inverse_size;
+
+const highp vec4 zero4 = vec4(0.0, 0.0, 0.0, 0.0);
+
+// TODO(abarth): Currently the light is located and sized in normalized device
+// coordinates, which are viewport-relative. We need to instead specify the
+// light in the world coordinate system and transform it by u_transform into
+// normalized device coordinates.
+const highp float light_z = 0.2;
+const highp float light_min_x = 0.0;
+const highp float light_min_y = 0.0;
+const highp float light_max_x = 1.0;
+const highp float light_max_y = 1.0;
+
+highp float getMin(vec4 value) {
+  return min(min(value.x, value.y), min(value.z, value.w));
+}
+
+highp float getMax(vec4 value) {
+  return max(max(value.x, value.y), max(value.z, value.w));
+}
+
+void main(void) {
+  // Convert gl_FragCoord from window coordiates to [0:1]^2 for texture lookup.
+  highp vec2 depth_coord = gl_FragCoord.xy * u_inverse_size;
+
+  // Depth is in the range [0:1].
+  // TODO(abarth): Fetch the depth from a depth texture.
+  highp float depth = 0.3;
+
+  // Convert from [0:1]^3 to [-1:1]^3 (i.e., normalized device coordinates).
+  highp vec3 position = vec3(depth_coord, depth) * 2.0 - 1.0;
+
+  highp float light_distance = light_z - position.z;
+
+  highp vec3 ray0 = v_occluder0 - position;
+  highp vec3 ray1 = v_occluder1 - position;
+  highp vec3 ray2 = v_occluder2 - position;
+  highp vec3 ray3 = v_occluder3 - position;
+
+  highp float scale0 = light_distance / ray0.z;
+  highp float scale1 = light_distance / ray1.z;
+  highp float scale2 = light_distance / ray2.z;
+  highp float scale3 = light_distance / ray3.z;
+
+  highp vec4 horizontal = clamp(vec4(scale0 * ray0.x,
+                                     scale1 * ray1.x,
+                                     scale2 * ray2.x,
+                                     scale3 * ray3.x),
+                                light_min_x, light_max_x);
+  highp vec4 vertical   = clamp(vec4(scale0 * ray0.y,
+                                     scale1 * ray1.y,
+                                     scale2 * ray2.y,
+                                     scale3 * ray3.y),
+                                light_min_y, light_max_y);
+
+  lowp float occulded = (getMax(horizontal) - getMin(horizontal)) *
+                        (getMax(vertical)   - getMin(vertical));
+
+  lowp float illuminated = 1.0 - occulded;
+
+  gl_FragColor = vec4(illuminated, illuminated, illuminated, 1.0);
+}
+)GLSL";
+
+GLvoid* GetOffset(int n) {
+  return reinterpret_cast<GLvoid*>(sizeof(GLfloat) * n);
+}
+
+GLint AddVertexAttribPointer(GLint index, GLint attribute, GLint count) {
+  glVertexAttribPointer(attribute, count, GL_FLOAT, GL_FALSE,
+                        sizeof(PenumbraProgram::Vertex), GetOffset(index));
+  return index + count;
+}
+
+}  // namespace
+
+PenumbraProgram::Vertex::Vertex() = default;
+
+PenumbraProgram::Vertex::Vertex(glm::vec3 point,
+                                glm::vec3 occluder0,
+                                glm::vec3 occluder1,
+                                glm::vec3 occluder2,
+                                glm::vec3 occluder3)
+    : point(std::move(point)),
+      occluder0(std::move(occluder0)),
+      occluder1(std::move(occluder1)),
+      occluder2(std::move(occluder2)),
+      occluder3(std::move(occluder3)) {
+}
+
+PenumbraProgram::PenumbraProgram()
+    : vertex_shader_(GL_VERTEX_SHADER, kVertexShaderSource),
+      fragment_shader_(GL_FRAGMENT_SHADER, kFragmentShaderSource),
+      program_(&vertex_shader_, &fragment_shader_),
+      u_transform_(glGetUniformLocation(program_.id(), "u_transform")),
+      u_inverse_size_(glGetUniformLocation(program_.id(), "u_inverse_size")),
+      a_position_(glGetAttribLocation(program_.id(), "a_position")),
+      a_occluder0_(glGetAttribLocation(program_.id(), "a_occluder0")),
+      a_occluder1_(glGetAttribLocation(program_.id(), "a_occluder1")),
+      a_occluder2_(glGetAttribLocation(program_.id(), "a_occluder2")),
+      a_occluder3_(glGetAttribLocation(program_.id(), "a_occluder3")) {
+  CHECK(u_transform_ != -1);
+  CHECK(u_inverse_size_ != -1);
+  CHECK(a_position_ != -1);
+  CHECK(a_occluder0_ != -1);
+  CHECK(a_occluder1_ != -1);
+  CHECK(a_occluder2_ != -1);
+  CHECK(a_occluder3_ != -1);
+  glEnableVertexAttribArray(a_position_);
+  glEnableVertexAttribArray(a_occluder0_);
+  glEnableVertexAttribArray(a_occluder1_);
+  glEnableVertexAttribArray(a_occluder2_);
+  glEnableVertexAttribArray(a_occluder3_);
+}
+
+PenumbraProgram::~PenumbraProgram() {
+}
+
+void PenumbraProgram::Use(const glm::mat4& transform,
+                          const glm::vec2& inverse_size) {
+  glUseProgram(program_.id());
+  glUniformMatrix4fv(u_transform_, 1, GL_FALSE, glm::value_ptr(transform));
+  glUniform2f(u_inverse_size_, inverse_size.x, inverse_size.y);
+  GLint i = 0;
+  i = AddVertexAttribPointer(i, a_position_, 3);
+  i = AddVertexAttribPointer(i, a_occluder0_, 3);
+  i = AddVertexAttribPointer(i, a_occluder1_, 3);
+  i = AddVertexAttribPointer(i, a_occluder2_, 3);
+  AddVertexAttribPointer(i, a_occluder3_, 3);
+}
+
+}  // namespace examples
diff --git a/examples/shadows/penumbra_program.h b/examples/shadows/penumbra_program.h
new file mode 100644
index 0000000..08e6e95
--- /dev/null
+++ b/examples/shadows/penumbra_program.h
@@ -0,0 +1,58 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_SHADOWS_PENUMBRA_PROGRAM_H_
+#define EXAMPLES_SHADOWS_PENUMBRA_PROGRAM_H_
+
+#include <glm/glm.hpp>
+
+#include "base/macros.h"
+#include "examples/shadows/vfx/program.h"
+#include "examples/shadows/vfx/shader.h"
+
+namespace examples {
+
+class PenumbraProgram {
+ public:
+  PenumbraProgram();
+  ~PenumbraProgram();
+
+  struct Vertex {
+    Vertex();
+    Vertex(glm::vec3 point,
+           glm::vec3 occluder0,
+           glm::vec3 occluder1,
+           glm::vec3 occluder2,
+           glm::vec3 occluder3);
+
+    glm::vec3 point;
+    glm::vec3 occluder0;
+    glm::vec3 occluder1;
+    glm::vec3 occluder2;
+    glm::vec3 occluder3;
+  };
+
+  GLuint id() const { return program_.id(); }
+
+  void Use(const glm::mat4& transform, const glm::vec2& inverse_size);
+
+ private:
+  vfx::Shader vertex_shader_;
+  vfx::Shader fragment_shader_;
+  vfx::Program program_;
+
+  GLint u_transform_;
+  GLint u_inverse_size_;
+  GLint a_position_;
+  GLint a_occluder0_;
+  GLint a_occluder1_;
+  GLint a_occluder2_;
+  GLint a_occluder3_;
+
+  DISALLOW_COPY_AND_ASSIGN(PenumbraProgram);
+};
+
+}  // namespace examples
+
+#endif  // EXAMPLES_SHADOWS_PENUMBRA_PROGRAM_H_
diff --git a/examples/shadows/shadows_renderer.cc b/examples/shadows/shadows_renderer.cc
index 1161ffe..0974738 100644
--- a/examples/shadows/shadows_renderer.cc
+++ b/examples/shadows/shadows_renderer.cc
@@ -19,7 +19,26 @@
                   {vec3( 2.f,  2.f, -2.f), green},
                   {vec3(-2.f,  2.f, -2.f), green},
                   {vec3(-2.f, -2.f, -2.f), green});
+  vec4 white(1.f, 1.f, 1.f, 1.f);
+  shapes_.AddQuad({vec3( 20.f, -20.f, -5.f), white},
+                  {vec3( 20.f,  20.f, -5.f), white},
+                  {vec3(-20.f,  20.f, -5.f), white},
+                  {vec3(-20.f, -20.f, -5.f), white});
   shapes_.BufferData(GL_STATIC_DRAW);
+
+  vec3 occluder0( 2.f, -2.f, -2.f);
+  vec3 occluder1( 2.f,  2.f, -2.f);
+  vec3 occluder2(-2.f,  2.f, -2.f);
+  vec3 occluder3(-2.f, -2.f, -2.f);
+  penumbra_.AddQuad({vec3( 20.f, -20.f, -5.f), occluder0, occluder1,
+                                               occluder2, occluder3},
+                    {vec3( 20.f,  20.f, -5.f), occluder0, occluder1,
+                                               occluder2, occluder3},
+                    {vec3(-20.f,  20.f, -5.f), occluder0, occluder1,
+                                               occluder2, occluder3},
+                    {vec3(-20.f, -20.f, -5.f), occluder0, occluder1,
+                                               occluder2, occluder3});
+  penumbra_.BufferData(GL_STATIC_DRAW);
 }
 
 ShadowsRenderer::~ShadowsRenderer() {}
@@ -27,15 +46,17 @@
 void ShadowsRenderer::Render(const mojo::Size& size) {
   glViewport(0, 0, size.width, size.height);
   glClearColor(0.f, 0.f, 1.f, 1.f);
-  glClear(GL_COLOR_BUFFER_BIT);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+  glEnable(GL_DEPTH_TEST);
 
   float aspect =
       static_cast<GLfloat>(size.width) / static_cast<GLfloat>(size.height);
   mat4 mvp = glm::perspective(60.f, aspect, 1.f, 20.f);
+  vec2 inverse_size = vec2(1.f / size.width, 1.f / size.height);
 
-  shapes_.Bind();
-  color_program_.Use(mvp);
-  shapes_.Draw();
+  penumbra_.Bind();
+  penumbra_program_.Use(mvp, inverse_size);
+  penumbra_.Draw();
 }
 
 }  // namespace examples
diff --git a/examples/shadows/shadows_renderer.h b/examples/shadows/shadows_renderer.h
index d9cffd6..7afc89d 100644
--- a/examples/shadows/shadows_renderer.h
+++ b/examples/shadows/shadows_renderer.h
@@ -6,6 +6,7 @@
 #define EXAMPLES_SHADOWS_SHADOWS_RENDERER_H_
 
 #include "base/macros.h"
+#include "examples/shadows/penumbra_program.h"
 #include "examples/shadows/vfx/color_program.h"
 #include "examples/shadows/vfx/element_array_buffer.h"
 #include "mojo/services/geometry/interfaces/geometry.mojom.h"
@@ -21,7 +22,9 @@
 
  private:
   vfx::ColorProgram color_program_;
+  PenumbraProgram penumbra_program_;
   vfx::ElementArrayBuffer<vfx::ColorProgram::Vertex> shapes_;
+  vfx::ElementArrayBuffer<PenumbraProgram::Vertex> penumbra_;
 
   DISALLOW_COPY_AND_ASSIGN(ShadowsRenderer);
 };