OpenGLでDeferredシェーディングを実装する(PointLightの影)

はじめに

この記事はシリーズ記事です。目次はこちら。

この記事ではPBRの古典的DeferredシェーディングのPointLightの影を実装するところまでを行います。

2020 04 11 12 45 31

blendファイルの変更点

2020 04 11 12 43 22

ShadowのBiasの値を適当に0.01としておきます。 Blenderで使っているShadow Biasの値と今回実装するプログラムのバイアスの値にずれがあるようなので注意です。

前回のプログラムからの変更点

前回のプログラムからの変更点を次に示します。

PointLightクラスの変更点

PointLightクラスに影の情報をもたせます。

point_light.h
 private:
  glm::vec3 position_;
  GLfloat intensity_;
  glm::vec3 color_;
  GLfloat near_;  GLfloat range_;
  GLfloat shadow_bias_;  bool use_shadow_;  glm::mat4 light_view_projection_matrices_[6];  glm::mat4 model_matrix_;

影行列を再計算するメンバ関数を用意します。

point_light.h
  /**
   * @brief 影行列の列を再計算する
   *
   * position_、range_からshadow_matrices_を再計算します。
   */
  void RecaluculateLightViewProjectionMatrices();
point_light.cpp
void PointLight::RecaluculateLightViewProjectionMatrices() {
  auto light_projection =
      glm::perspective(glm::radians(90.0f), 1.0f, near_, range_);
  light_view_projection_matrices_[0] =
      light_projection * glm::lookAt(position_,
                                     position_ + glm::vec3(1.0f, 0.0f, 0.0f),
                                     glm::vec3(0.0f, -1.0f, 0.0f));
  light_view_projection_matrices_[1] =
      light_projection * glm::lookAt(position_,
                                     position_ + glm::vec3(-1.0f, 0.0f, 0.0f),
                                     glm::vec3(0.0f, -1.0f, 0.0f));
  light_view_projection_matrices_[2] =
      light_projection * glm::lookAt(position_,
                                     position_ + glm::vec3(0.0f, 1.0f, 0.0f),
                                     glm::vec3(0.0f, 0.0f, 1.0f));
  light_view_projection_matrices_[3] =
      light_projection * glm::lookAt(position_,
                                     position_ + glm::vec3(0.0f, -1.0f, 0.0f),
                                     glm::vec3(0.0f, 0.0f, -1.0f));
  light_view_projection_matrices_[4] =
      light_projection * glm::lookAt(position_,
                                     position_ + glm::vec3(0.0f, 0.0f, 1.0f),
                                     glm::vec3(0.0f, -1.0f, 0.0f));
  light_view_projection_matrices_[5] =
      light_projection * glm::lookAt(position_,
                                     position_ + glm::vec3(0.0f, 0.0f, -1.0f),
                                     glm::vec3(0.0f, -1.0f, 0.0f));
}

キューブマップの6面分の影行列を用意しています。

次のようにいくつかのメンバ関数を追加します。

point_light.h
  /**
   * @brief ポイントライトの影のnearを取得する
   * @return ポイントライトの影のnearの値
   */
  const GLfloat GetNear() const;

  /**
   * @brief ポイントライトの影のnearの値を設定する
   * @param near ポイントライトの影の新しいnearの値
   *
   * 内部で保持する影行列の再計算が行われます。
   */
  void SetNear(const GLfloat near);

    /**
   * @brief ポイントライトのShadow Biasを取得する
   * @return ポイントライトのShadow Bias
   */
  const GLfloat GetShadowBias() const;

  /**
   * @brief ポイントライトのShadow Biasを設定する
   * @param shadow_bias ポイントライトの新しいShadow Bias
   */
  void SetShadowBias(const GLfloat shadow_bias);

  /**
   * @brief ポイントライトが影を利用するかどうかを取得する
   * @return ポイントライトが影を利用するかどうか
   */
  const bool GetUseShadow() const;

  /**
   * @brief ポイントライトが影を利用するかどうかを設定する
   * @param use_shadow ポイントライトが影を利用するかどうか
   */

  void SetUseShadow(const bool use_shadow);

  /**
   * @brief ポイントライトの影行列の列を返す
   * @param[out] light_view_projection_matrices ポイントライトの影行列
   *
   * 影行列の列はPosX、NegX、PosY、NegY、PosZ、NegZの順です。
   */
  void GetLightViewProjectionMatrices(
      glm::mat4 light_view_projection_matrices[6]) const;
point_light.cpp
const GLfloat PointLight::GetNear() const { return near_; }

void PointLight::SetNear(const GLfloat near) {
  near_ = near;
  RecaluculateLightViewProjectionMatrices();
}

const GLfloat PointLight::GetShadowBias() const { return shadow_bias_; }

void PointLight::SetShadowBias(const GLfloat shadow_bias) {
  shadow_bias_ = shadow_bias;
}

const bool PointLight::GetUseShadow() const { return use_shadow_; }

void PointLight::SetUseShadow(const bool use_shadow) {
  use_shadow_ = use_shadow;
}

void PointLight::GetLightViewProjectionMatrices(
    glm::mat4 light_view_projection_matrices[6]) const {
  for (int i = 0; i < 6; ++i) {
    light_view_projection_matrices[i] = light_view_projection_matrices_[i];
  }
}

PointLight::SetRangeRecaluculateLightViewProjectionMatricesを追加します。

point_light.cpp
void PointLight::SetRange(const GLfloat range) {
  range_ = range;
  RecaluculateModelMatrix();
  RecaluculateLightViewProjectionMatrices();}

コンストラクタを変更します。

point_light.h
  /**
   * @brief コンストラクタ
   * @param position ポイントライトの位置
   * @param intensity ポイントライトの強さ(lx)
   * @param color ポイントライトの色
   * @param near ポイントライトの影のnearの値
   * @param range ポイントライトのRange(m)
   * @param shadow_bias ポイントライトのShadow Bias
   * @param use_shadow ポイントライトが影を利用するかどうか
   */
  PointLight(const glm::vec3 position, const GLfloat intensity,
             const glm::vec3 color, const GLfloat near, const GLfloat range,
             const GLfloat shadow_bias, const bool use_shadow);
point_light.cpp
PointLight::PointLight(const glm::vec3 position, const GLfloat intensity,
                       const glm::vec3 color, const GLfloat near,
                       const GLfloat range, const GLfloat shadow_bias,
                       const bool use_shadow)
    : position_(position),
      intensity_(intensity),
      color_(color),
      near_(near),
      range_(range),
      shadow_bias_(shadow_bias),
      use_shadow_(use_shadow),
      model_matrix_(),
      light_view_projection_matrices_() {
  RecaluculateModelMatrix();
  RecaluculateLightViewProjectionMatrices();
}

Sceneクラスの変更点

Scene::LoadSceneのポイントライトの読み込みの部分を変更します。

scene.cpp
// Point Light
    else if (texts[0] == "PointLight:") {
      glm::vec3 position;
      GLfloat intensity;
      glm::vec3 color;
      GLfloat near;
      GLfloat range;
      GLfloat shadow_bias;
      bool use_shadow;

      while (std::getline(ifs, line)) {
        auto texts = SplitString(line, ' ');

        if (texts[0] == "Position:") {
          position = glm::vec3(std::stof(texts[1]), std::stof(texts[3]),
                               -std::stof(texts[2]));
        } else if (texts[0] == "Intensity:") {
          intensity = std::stof(texts[1]);
        } else if (texts[0] == "Color:") {
          color = glm::vec3(std::stof(texts[1]), std::stof(texts[2]),
                            std::stof(texts[3]));
        } else if (texts[0] == "ClipStart:") {
          near = std::stof(texts[1]);
        } else if (texts[0] == "Range:") {
          range = std::stof(texts[1]);
        } else if (texts[0] == "ShadowBias:") {
          shadow_bias = std::stof(texts[1]);
        } else if (texts[0] == "UseShadow:") {
          use_shadow = std::stoi(texts[1]) == 1;
        }

        if (line == "PointLightEnd") break;
      }

      scene->point_lights_.emplace_back(position, intensity, color, near, range,                                        shadow_bias, use_shadow);    }

CreateProgramの変更

CreateProgramにジオメトリシェーダを使うシェーダプログラム作成用の関数を用意します。

create_program.h
/**
 * @brief シェーダを読み込む
 * @param vertex_shader_pass 頂点シェーダのパス
 * @param geometry_shader_pass ジオメトリシェーダのパス
 * @param fragment_shader_pass フラグメントシェーダのパス
 */
GLuint CreateProgram(const std::string& vertex_shader_pass,
                     const std::string& geometry_shader_pass,
                     const std::string& fragment_shader_pass);
create_program.cpp
GLuint CreateProgram(const std::string& vertex_shader_pass,
                     const std::string& geometry_shader_pass,
                     const std::string& fragment_shader_pass) {
  // 頂点シェーダの読み込み
  std::ifstream vertex_ifs(vertex_shader_pass, std::ios::binary);
  if (vertex_ifs.fail()) {
    std::cerr << "Error: Can't open source file: " << vertex_shader_pass
              << std::endl;
    return 0;
  }
  auto vertex_shader_source =
      std::string(std::istreambuf_iterator<char>(vertex_ifs),
                  std::istreambuf_iterator<char>());
  if (vertex_ifs.fail()) {
    std::cerr << "Error: could not read source file: " << vertex_shader_pass
              << std::endl;
    return 0;
  }
  GLchar const* vertex_shader_source_pointer = vertex_shader_source.c_str();

  // ジオメトリシェーダの読み込み
  std::ifstream geometry_ifs(geometry_shader_pass, std::ios::binary);
  if (geometry_ifs.fail()) {
    std::cerr << "Error: Can't open source file: " << geometry_shader_pass
              << std::endl;
    return 0;
  }
  auto geometry_shader_source =
      std::string(std::istreambuf_iterator<char>(geometry_ifs),
                  std::istreambuf_iterator<char>());
  if (geometry_ifs.fail()) {
    std::cerr << "Error: could not read source file: " << vertex_shader_pass
              << std::endl;
    return 0;
  }
  GLchar const* geometry_shader_source_pointer = geometry_shader_source.c_str();

  // フラグメントシェーダの読み込み
  std::ifstream fragment_ifs(fragment_shader_pass, std::ios::binary);
  if (fragment_ifs.fail()) {
    std::cerr << "Error: Can't open source file: " << fragment_shader_pass
              << std::endl;
    return 0;
  }
  auto fragment_shader_source =
      std::string(std::istreambuf_iterator<char>(fragment_ifs),
                  std::istreambuf_iterator<char>());
  if (fragment_ifs.fail()) {
    std::cerr << "Error: could not read source file: " << fragment_shader_pass
              << std::endl;
    return 0;
  }
  GLchar const* fragment_shader_source_pointer = fragment_shader_source.c_str();

  // プログラムオブジェクトを作成
  const GLuint program = glCreateProgram();

  GLint status = GL_FALSE;
  GLsizei info_log_length;

  // 頂点シェーダのコンパイル
  const GLuint vertex_shader_obj = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(vertex_shader_obj, 1, &vertex_shader_source_pointer, nullptr);
  glCompileShader(vertex_shader_obj);
  glAttachShader(program, vertex_shader_obj);

  // 頂点シェーダのチェック
  glGetShaderiv(vertex_shader_obj, GL_COMPILE_STATUS, &status);
  if (status == GL_FALSE)
    std::cerr << "Compile Error in Vertex Shader." << std::endl;
  glGetShaderiv(vertex_shader_obj, GL_INFO_LOG_LENGTH, &info_log_length);
  if (info_log_length > 1) {
    std::vector<GLchar> vertex_shader_error_message(info_log_length);
    glGetShaderInfoLog(vertex_shader_obj, info_log_length, nullptr,
                       vertex_shader_error_message.data());
    std::cerr << vertex_shader_error_message.data() << std::endl;
  }

  glDeleteShader(vertex_shader_obj);

  // ジオメトリシェーダのコンパイル
  const GLuint geometry_shader_obj = glCreateShader(GL_GEOMETRY_SHADER);
  glShaderSource(geometry_shader_obj, 1, &geometry_shader_source_pointer,
                 nullptr);
  glCompileShader(geometry_shader_obj);
  glAttachShader(program, geometry_shader_obj);

  // ジオメトリシェーダのチェック
  glGetShaderiv(geometry_shader_obj, GL_COMPILE_STATUS, &status);
  if (status == GL_FALSE)
    std::cerr << "Compile Error in Geometry Shader." << std::endl;
  glGetShaderiv(geometry_shader_obj, GL_INFO_LOG_LENGTH, &info_log_length);
  if (info_log_length > 1) {
    std::vector<GLchar> geometry_shader_error_message(info_log_length);
    glGetShaderInfoLog(geometry_shader_obj, info_log_length, nullptr,
                       geometry_shader_error_message.data());
    std::cerr << geometry_shader_error_message.data() << std::endl;
  }

  glDeleteShader(geometry_shader_obj);

  // フラグメントシェーダのコンパイル
  const GLuint fragment_shader_obj = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(fragment_shader_obj, 1, &fragment_shader_source_pointer,
                 nullptr);
  glCompileShader(fragment_shader_obj);
  glAttachShader(program, fragment_shader_obj);

  // フラグメントシェーダのチェック
  glGetShaderiv(fragment_shader_obj, GL_COMPILE_STATUS, &status);
  if (status == GL_FALSE)
    std::cerr << "Compile Error in Fragment Shader." << std::endl;
  glGetShaderiv(fragment_shader_obj, GL_INFO_LOG_LENGTH, &info_log_length);
  if (info_log_length > 1) {
    std::vector<GLchar> fragment_shader_error_message(info_log_length);
    glGetShaderInfoLog(fragment_shader_obj, info_log_length, nullptr,
                       fragment_shader_error_message.data());
    std::cerr << fragment_shader_error_message.data() << std::endl;
  }

  glDeleteShader(fragment_shader_obj);

  // プログラムのリンク
  glLinkProgram(program);

  // リンクのチェック
  glGetProgramiv(program, GL_LINK_STATUS, &status);
  if (status == GL_FALSE) std::cerr << "Link Error." << std::endl;
  glGetProgramiv(program, GL_INFO_LOG_LENGTH, &info_log_length);
  if (info_log_length > 1) {
    std::vector<GLchar> program_link_error_message(info_log_length);
    glGetProgramInfoLog(program, info_log_length, nullptr,
                        program_link_error_message.data());
    std::cerr << program_link_error_message.data() << std::endl;
  }

  return program;
}

PointLightPassクラスの変更点

PointLightPassクラスにシャドウマップとシャドウパス用のメンバ変数をもたせます。

point_light_pass.h
 private:
  const GLuint width_;
  const GLuint height_;

  const GLuint shadow_map_size_;
  const GLuint hdr_fbo_;
  const GLuint gbuffer0_;
  const GLuint gbuffer1_;
  const GLuint gbuffer2_;
  const GLuint sphere_vao_;

  const GLuint shadow_map_;  const GLuint shadow_map_fbo_;
  const GLuint shadow_pass_shader_program_;  const GLuint shadow_pass_model_loc_;  const GLuint shadow_pass_light_view_projection_matrices_loc_;  const GLuint shadow_pass_world_light_pos_loc_;  const GLuint shadow_pass_range_loc_;
  const GLuint stencil_pass_shader_program_;
  const GLuint stencil_pass_model_view_projection_loc_;

  const GLuint shader_program_;
  const GLuint model_view_projection_loc_;
  const GLuint world_light_position_loc_;
  const GLuint light_intensity_loc_;
  const GLuint light_color_loc_;
  const GLuint light_range_loc_;
  const GLuint world_camera_pos_loc_;
  const GLuint view_projection_i_loc_;
  const GLuint projection_params_loc_;
  const GLuint shadow_bias_loc_;  const GLuint use_shadow_loc_;

シャドウマップ作成用のstaticメンバ関数を作成します。

point_light_pass.h
  /**
   * @brief シャドウマップテクスチャを作成する
   * @return 生成したテクスチャのID
   */
  static const GLuint CreateShadowMap(const GLuint shadow_map_size);

  /**
   * @brief シャドウマップのFBOを生成する
   * @param shadow_map シャドウマップテクスチャのID
   * @return 生成したFBOのID
   */
  static const GLuint CreateShadowMapFbo(const GLuint shadow_map);
point_light_pass.cpp
const GLuint PointLightPass::CreateShadowMap(const GLuint shadow_map_size) {
  GLuint shadow_map;
  glGenTextures(1, &shadow_map);
  glBindTexture(GL_TEXTURE_CUBE_MAP, shadow_map);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_COMPARE_MODE,
                  GL_COMPARE_REF_TO_TEXTURE);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
  for (int i = 0; i < 6; i++) {
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT,
                 shadow_map_size, shadow_map_size, 0, GL_DEPTH_COMPONENT,
                 GL_FLOAT, nullptr);
  }
  glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
  return shadow_map;
}

const GLuint PointLightPass::CreateShadowMapFbo(const GLuint shadow_map) {
  GLuint shadow_map_fbo;
  glGenFramebuffers(1, &shadow_map_fbo);
  glBindFramebuffer(GL_FRAMEBUFFER, shadow_map_fbo);
  glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadow_map, 0);
  glBindFramebuffer(GL_FRAMEBUFFER, 0);
  return shadow_map_fbo;
}

シャドウマップにはデプスのキューブマップを使っています。

コンストラクタを次のようにします。

point_light_pass.cpp
PointLightPass::PointLightPass(const GLuint hdr_fbo, const GLuint gbuffer0,
                               const GLuint gbuffer1, const GLuint gbuffer2,
                               const GLuint sphere_vao, const GLuint width,
                               const GLuint height)
    : width_(width),
      height_(height),

      shadow_map_size_(512),
      hdr_fbo_(hdr_fbo),
      gbuffer0_(gbuffer0),
      gbuffer1_(gbuffer1),
      gbuffer2_(gbuffer2),
      sphere_vao_(sphere_vao),

      shadow_map_(CreateShadowMap(shadow_map_size_)),      shadow_map_fbo_(CreateShadowMapFbo(shadow_map_)),
      shadow_pass_shader_program_(          CreateProgram("shader/PointLightShadowPass.vert",                        "shader/PointLightShadowPass.geom",                        "shader/PointLightShadowPass.frag")),      shadow_pass_model_loc_(          glGetUniformLocation(shadow_pass_shader_program_, "Model")),      shadow_pass_light_view_projection_matrices_loc_(          glGetUniformLocation(shadow_pass_shader_program_, "ShadowMatrices")),      shadow_pass_world_light_pos_loc_(          glGetUniformLocation(shadow_pass_shader_program_, "worldLightPos")),      shadow_pass_range_loc_(          glGetUniformLocation(shadow_pass_shader_program_, "range")),
      stencil_pass_shader_program_(
          CreateProgram("shader/PunctualLightStencilPass.vert",
                        "shader/PunctualLightStencilPass.frag")),
      stencil_pass_model_view_projection_loc_(glGetUniformLocation(
          stencil_pass_shader_program_, "ModelViewProjection")),

      shader_program_(CreateProgram("shader/PointLightPass.vert",
                                    "shader/PointLightPass.frag")),
      model_view_projection_loc_(
          glGetUniformLocation(shader_program_, "ModelViewProjection")),
      world_light_position_loc_(
          glGetUniformLocation(shader_program_, "worldLightPosition")),
      light_intensity_loc_(
          glGetUniformLocation(shader_program_, "lightIntensity")),
      light_color_loc_(glGetUniformLocation(shader_program_, "lightColor")),
      light_range_loc_(glGetUniformLocation(shader_program_, "lightRange")),
      world_camera_pos_loc_(
          glGetUniformLocation(shader_program_, "worldCameraPos")),
      view_projection_i_loc_(
          glGetUniformLocation(shader_program_, "ViewProjectionI")),
      projection_params_loc_(
          glGetUniformLocation(shader_program_, "ProjectionParams")),
      shadow_bias_loc_(glGetUniformLocation(shader_program_, "shadowBias")),      use_shadow_loc_(glGetUniformLocation(shader_program_, "useShadow")) {}

デストラクタで確保したシャドウマップを破棄するようにします。

point_light_pass.cpp
PointLightPass::~PointLightPass() {
  glDeleteFramebuffers(1, &shadow_map_fbo_);  glDeleteTextures(1, &shadow_map_);
  glDeleteProgram(shadow_pass_shader_program_);  glDeleteProgram(stencil_pass_shader_program_);
  glDeleteProgram(shader_program_);
}

PointLightPass::Renderにシャドウパスを追加します。

point_light_pass.cpp
    // Shadow Map Pass
    if (point_light.GetUseShadow()) {
      glUseProgram(shadow_pass_shader_program_);
      glBindFramebuffer(GL_FRAMEBUFFER, shadow_map_fbo_);

      glStencilFunc(GL_ALWAYS, 0, 0);
      glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

      glViewport(0, 0, shadow_map_size_, shadow_map_size_);

      glDepthMask(GL_TRUE);
      glEnable(GL_DEPTH_TEST);

      glClear(GL_DEPTH_BUFFER_BIT);

      glm::mat4 light_view_projection_matrices[6];
      point_light.GetLightViewProjectionMatrices(
          light_view_projection_matrices);
      const glm::vec3 position = point_light.GetPosition();
      const GLfloat range = point_light.GetRange();
      glUniformMatrix4fv(shadow_pass_light_view_projection_matrices_loc_, 6,
                         GL_FALSE, &light_view_projection_matrices[0][0][0]);
      glUniform3fv(shadow_pass_world_light_pos_loc_, 1, &position[0]);
      glUniform1fv(shadow_pass_range_loc_, 1, &range);

      for (const auto& mesh_entity : scene.mesh_entities_) {
        if (!mesh_entity.TestSphereAABB(position, range)) {
          continue;
        }

        const glm::mat4 model = mesh_entity.GetModelMatrix();

        glUniformMatrix4fv(shadow_pass_model_loc_, 1, GL_FALSE, &model[0][0]);
        mesh_entity.mesh_->Draw();
      }
    }

use_shadowがオフだったらまず影の描画を行いません。 影を描画する場合はAABBとポイントライトのsphereの当たり判定を撮ってから描画をしています。

ライトパスも変更します。

point_light_pass.cpp
    // Lighting Pass
    glUseProgram(shader_program_);

    glDisable(GL_DEPTH_TEST);

    glStencilFunc(GL_NOTEQUAL, 0, 255);
    glStencilMask(0);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);

    glDrawBuffer(GL_COLOR_ATTACHMENT0);
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glDepthMask(GL_FALSE);

    const glm::vec3 world_light_pos = point_light.GetPosition();
    const GLfloat light_intensity = point_light.GetIntensity();
    const glm::vec3 light_color = point_light.GetColor();
    const GLfloat light_range = point_light.GetRange();
    const glm::vec3 world_camera_pos = scene.camera_->GetPosition();
    const glm::mat4 view_projection_i =
        glm::inverse(scene.camera_->GetViewProjectionMatrix());
    const glm::vec2 projection_params =
        glm::vec2(scene.camera_->GetNear(), scene.camera_->GetFar());
    const GLfloat shadow_bias = point_light.GetShadowBias();    const GLint use_shadow = point_light.GetUseShadow() ? 1 : 0;
    glUniformMatrix4fv(model_view_projection_loc_, 1, GL_FALSE,
                       &model_view_projection[0][0]);
    glUniform3fv(world_light_position_loc_, 1, &world_light_pos[0]);
    glUniform1fv(light_intensity_loc_, 1, &light_intensity);
    glUniform3fv(light_color_loc_, 1, &light_color[0]);
    glUniform1fv(light_range_loc_, 1, &light_range);
    glUniform3fv(world_camera_pos_loc_, 1, &world_camera_pos[0]);
    glUniformMatrix4fv(view_projection_i_loc_, 1, GL_FALSE,
                       &view_projection_i[0][0]);
    glUniform2fv(projection_params_loc_, 1, &projection_params[0]);
    glUniform1fv(shadow_bias_loc_, 1, &shadow_bias);    glUniform1i(use_shadow_loc_, use_shadow);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, gbuffer0_);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, gbuffer1_);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, gbuffer2_);
    glActiveTexture(GL_TEXTURE3);    glBindTexture(GL_TEXTURE_CUBE_MAP, shadow_map_);
    glBindVertexArray(sphere_vao_);
    glDrawElements(GL_TRIANGLES, 8 * 8 * 6, GL_UNSIGNED_INT, 0);

    glCullFace(GL_BACK);
  }

シェーダの作成と変更

shader/PointLightShadowPass.vertshader/PointLightShadowPass.geomshader/PointLightShadowPass.fragを作成します。

shader/PointLightShadowPass.vert
#version 460

layout (location = 0) in vec4 position;

uniform mat4 Model;

void main()
{
  gl_Position = Model * position;
}
shader/PointLightShadowPass.geom
#version 460

layout (triangles) in;
layout (triangle_strip, max_vertices=18) out;

uniform mat4 ShadowMatrices[6];

out vec4 worldFragPos;

void main()
{
  for (int face = 0; face < 6; face++)
  {
    gl_Layer = face;
    for (int i = 0; i < 3; i++)
    {
      worldFragPos = gl_in[i].gl_Position;
      gl_Position = ShadowMatrices[face] * worldFragPos;
      EmitVertex();
    }
    EndPrimitive();
  }
}
shader/PointLightShadowPass.frag
#version 460

in vec4 worldFragPos;

uniform vec3 worldLightPos;
uniform float range;

void main()
{
  float lightDistance = length(worldFragPos.xyz - worldLightPos);
  lightDistance = lightDistance / range;
  gl_FragDepth = lightDistance;
}

ジオメトリシェーダで1パスでキューブマップの6面をレンダリングしています。 格納するデプスはそのままのデプスではなく、距離をrangeで割ったものとしています。

shader/PointLightPass.fragを編集します。

まずはUniform変数の追加。

shader/PointLightPass.frag
layout (binding = 0) uniform sampler2D GBuffer0;
layout (binding = 1) uniform sampler2D GBuffer1;
layout (binding = 2) uniform sampler2D GBuffer2;
layout (binding = 3) uniform samplerCubeShadow ShadowMap;
uniform vec3 worldLightPosition;
uniform float lightIntensity; // lm
uniform vec3 lightColor;
uniform float lightRange;

uniform vec3 worldCameraPos;
uniform mat4 ViewProjectionI;
uniform vec2 ProjectionParams; // x: near, y: far

uniform float shadowBias;uniform bool useShadow;

次に影の計算をする関数を用意します。

shader/PointLightPass.frag
// shadow ######################################################################
float getShadowAttenuation(vec3 worldPos)
{
  vec3 lightToFragVec = worldPos - worldLightPosition;
  float depth = length(lightToFragVec) / lightRange;
  return texture(ShadowMap, vec4(lightToFragVec, depth - shadowBias)).x;
}
// #############################################################################

main関数で影の影響をiraddianceに掛け合わせています。

shader/PointLightPass.frag
void main()
{
  vec2 uv = CalcTexCoord();

  vec4 gbuffer0 = texture(GBuffer0, uv);
  vec4 gbuffer1 = texture(GBuffer1, uv);
  vec4 gbuffer2 = texture(GBuffer2, uv);

  vec3 albedo = gbuffer0.rgb;
  float metallic = gbuffer0.a;
  vec3 emissive = gbuffer1.rgb;
  float depth = gbuffer1.a;
  vec3 normal = gbuffer2.rgb * 2.0 - 1.0;
  float roughness = gbuffer2.a;

  vec3 worldPos = worldPosFromDepth(depth, uv);

  float shadow = useShadow ? getShadowAttenuation(worldPos) : 1;

  vec3 V = normalize(worldCameraPos - worldPos);
  vec3 N = normalize(normal);
  vec3 L = normalize(worldLightPosition - worldPos);
  vec3 H = normalize(L + V);

  float distance = length(worldLightPosition - worldPos);
  vec3 irradiance = LightIrradiance(lightIntensity, sRGBToACES(lightColor), L, N, distance) * shadow;

  outRadiance = BRDF(albedo, metallic, roughness, N, V, L, H) * irradiance;
}

実行結果

実行結果は次のとおりです。

2020 04 11 12 45 31

前回と比べると緑や水色のポイントライトの光に影が加わったのがわかります。

2020 04 11 12 45 48

Blenderと比較しても、ちゃんと影が実装されていることがわかりますね。

プログラム全文

プログラム全文はGitHubにアップロードしてあります。

GitHub: MatchaChoco010/OpenGL-PBR-Map at v18