OpenGLでDeferredシェーディングを実装する(Point Light Pass)

はじめに

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

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

2020 04 07 15 51 43

ポイントライトの描画範囲

ポイントライトを描画する際も、DirectionalLightのときのように全画面を覆うメッシュを使って描画してもよいのですが、ライトの影響範囲を考慮してもう少しライティング計算を行うピクセルを削ることができます。

今回は次のページで説明されているステンシルを使ったライトの計算領域の計算を行います。

ステンシルテストをBackFaceとFrontFaceで別の命令を与えることで、ライトの範囲内にある領域を良い感じに計算しています。

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

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

PointLightクラスの作成

PointLightクラスを作成します。

point_light.h
#ifndef OPENGL_PBR_MAP_POINT_LIGHT_H_
#define OPENGL_PBR_MAP_POINT_LIGHT_H_

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include <glm/ext.hpp>
#include <glm/glm.hpp>

namespace game {

/**
 * @brief ポイントライトを表現するオブジェクト
 *
 * ポイントライトの各種プロパティを保持し、影行列を扱うクラスです。
 */
class PointLight {
 public:
  /**
   * @brief ポイントライトの位置を取得する
   * @return ポイントライトの位置
   */
  const glm::vec3 GetPosition() const;

  /**
   * @brief ポイントライトの位置を設定する
   * @param position ポイントライトの新しい位置
   *
   * 内部で保持する影行列とModel行列の再計算が行われます。
   */
  void SetPosition(const glm::vec3 position);

  /**
   * @brief ポイントライトの強さを取得する
   * @return ポイントライトの強さ(lm)
   *
   * ポイントライトの強さはlm(ルーメン)で表現します。
   */
  const GLfloat GetIntensity() const;

  /**
   * @brief ポイントライトの強さを設定する
   * @param intensity ポイントライトの新しい強さ
   *
   * ポイントライトの強さはlm(ルーメン)で表現します。
   */
  void SetIntensity(const GLfloat intensity);

  /**
   * @brief ポイントライトの色を取得する
   * @return ポイントライトの色
   */
  const glm::vec3 GetColor() const;

  /**
   * @brief ポイントライトの色を設定する
   * @param color ポイントライトの新しい色
   */
  void SetColor(const glm::vec3 color);

  /**
   * @brief ポイントライトのRangeを取得する
   * @return ポイントライトのRange(m)
   */
  const GLfloat GetRange() const;

  /**
   * @brief ポイントライトのRangeを設定する
   * @param range ポイントライトの新しいRange(m)
   */
  void SetRange(const GLfloat range);

  /**
   * @brief ポイントライトのModel行列を取得する
   * @return ポイントライトのModel行列
   *
   * range_ + 0.1fの拡大も含みます。
   */
  const glm::mat4 GetModelMatrix() const;

  /**
   * @brief コンストラクタ
   * @param position ポイントライトの位置
   * @param intensity ポイントライトの強さ(lx)
   * @param color ポイントライトの色
   * @param range ポイントライトのRange(m)
   */
  PointLight(const glm::vec3 position, const GLfloat intensity,
             const glm::vec3 color, const GLfloat range);

 private:
  glm::vec3 position_;
  GLfloat intensity_;
  glm::vec3 color_;
  GLfloat range_;
  glm::mat4 model_matrix_;

  /**
   * @brief Model行列を再計算する
   *
   * position_からmodel_matrix_を再計算します。
   */
  void RecaluculateModelMatrix();
};

}  // namespace game

#endif  // OPENGL_PBR_MAP_POINT_LIGHT_H_
point_light.cpp
#include "point_light.h"

namespace game {

const glm::vec3 PointLight::GetPosition() const { return position_; }

void PointLight::SetPosition(const glm::vec3 position) {
  position_ = position;
  RecaluculateModelMatrix();
}

const GLfloat PointLight::GetIntensity() const { return intensity_; }

void PointLight::SetIntensity(const GLfloat intensity) {
  intensity_ = intensity;
}

const glm::vec3 PointLight::GetColor() const { return color_; }

void PointLight::SetColor(const glm::vec3 color) { color_ = color; }

const GLfloat PointLight::GetRange() const { return range_; }

void PointLight::SetRange(const GLfloat range) {
  range_ = range;
  RecaluculateModelMatrix();
}

const glm::mat4 PointLight::GetModelMatrix() const { return model_matrix_; }

PointLight::PointLight(const glm::vec3 position, const GLfloat intensity,
                       const glm::vec3 color, const GLfloat range)
    : position_(position),
      intensity_(intensity),
      color_(color),
      range_(range),
      model_matrix_() {
  RecaluculateModelMatrix();
}

void PointLight::RecaluculateModelMatrix() {
  model_matrix_ = glm::translate(glm::mat4(1), position_) *
                  glm::scale(glm::mat4(1), glm::vec3(range_ + 0.1f));
}

}  // namespace game

基本的にポイントライトに必要な情報を保持しているだけです。

Model行列にちょっとだけ工夫がなされています。 後で球体を使って描画範囲を計算するのですが、その時使う球体のメッシュが半径1です。 そこで、Range倍のスケールをかけてやることで、ライトの範囲を覆う球体を考えます。 Range倍だと球体がポリゴンメッシュである関係で微妙に欠けるので、適当にRange+0.1倍することにしました。

Sceneクラスの変更

SceneクラスにPointLightを追加します。 ポイントライトは複数追加できるようにするのでvectorに格納します。

scene.h
#include <glm/glm.hpp>
#include <memory>
#include <vector>

#include "camera.h"
#include "directional_light.h"
#include "mesh.h"
#include "mesh_entity.h"
#include "point_light.h"
namespace game {

class Scene {
 public:
  std::unique_ptr<Camera> camera_;
  std::vector<MeshEntity> mesh_entities_;
  std::unique_ptr<DirectionalLight> directional_light_;
  std::vector<PointLight> point_lights_;
  /**
   * @brief テスト用のシーンを作成する
   * @param width ウィンドウの幅
   * @param height ウィンドウの高さ
   * @return テスト用シーン
   */
  static std::unique_ptr<Scene> CreateTestScene(const int width,
                                                const int height);
};

}  // namespace game
scene.cpp
  // DirectionalLightの設定
  scene->directional_light_ = std::make_unique<DirectionalLight>(
      10.0f, glm::vec3(-0.5f, -1.0f, -0.5f), glm::vec3(1.0f));
  // PointLightの追加  scene->point_lights_.emplace_back(glm::vec3(-2.0f, 0.0f, 2.0f), 600.0f,                                    glm::vec3(0.5f, 0.8f, 1.0f), 10.0f);  scene->point_lights_.emplace_back(glm::vec3(2.0f, 0.0f, 2.0f), 600.0f,                                    glm::vec3(0.5f, 1.0f, 0.8f), 10.0f);

ポイントライトを2つ追加し、directional lightの強さを弱くしました。

PointLightPassクラスの作成

次にポイントライトのパスのクラスを作ります。

point_light_pass.h
#ifndef OPENGL_PBR_MAP_POINT_LIGHT_PASS_H_
#define OPENGL_PBR_MAP_POINT_LIGHT_PASS_H_

#include <glm/ext.hpp>
#include <glm/glm.hpp>

#include "create_program.h"
#include "scene.h"

namespace game {

/**
 * @brief PointLighatパスを表現するクラス
 *
 * GBufferとシーンのPointLightの情報をもとに
 * PointLightのライティングを計算しHDRのカラーバッファに書き込みます。
 * リソースの多重開放を避けるためコピー禁止です。
 */
class PointLightPass {
 public:
  /**
   * @brief このパスをレンダリングする
   * @param scene レンダリングするシーン
   *
   * PointLightのライティングをHDRカラーバッファに書き出します。
   */
  void Render(const Scene& scene) const;

  /**
   * @brief コンストラクタ
   * @param hdr_color_fbo HDRのフレームバッファオブジェクト
   * @param gbuffer0 GBuffer0のテクスチャID
   * @param gbuffer1 GBuffer1のテクスチャID
   * @param gbuffer2 GBuffer2のテクスチャID
   * @param sphere_vao 球のMeshのVAO
   * @param width ウィンドウの幅
   * @param height ウィンドウの高さ
   */
  PointLightPass(const GLuint hdr_color_fbo, const GLuint gbuffer0,
                 const GLuint gbuffer1, const GLuint gbuffer2,
                 const GLuint sphere_vao, const GLuint width,
                 const GLuint height);

  /**
   * @brief デストラクタ
   *
   * コンストラクタで確保したリソースを開放します。
   */
  ~PointLightPass();

 private:
  const GLuint width_;
  const GLuint height_;

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

  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_;
};

}  // namespace game

#endif  // OPENGL_PBR_MAP_POINT_LIGHT_PASS_H_
point_light_pass.cpp
#include "point_light_pass.h"

namespace game {

void PointLightPass::Render(const Scene& scene) const {
  for (const PointLight& point_light : scene.point_lights_) {
    // Stencil Pass
    glUseProgram(stencil_pass_shader_program_);
    glBindFramebuffer(GL_FRAMEBUFFER, hdr_fbo_);

    glViewport(0, 0, width_, height_);

    glDisable(GL_CULL_FACE);

    glEnable(GL_DEPTH_TEST);

    glStencilMask(255);
    glClear(GL_STENCIL_BUFFER_BIT);

    glStencilFunc(GL_ALWAYS, 0, 0);
    glStencilOpSeparate(GL_BACK, GL_KEEP, GL_INCR_WRAP, GL_KEEP);
    glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_DECR_WRAP, GL_KEEP);

    glDrawBuffer(GL_NONE);
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    glDepthMask(GL_FALSE);

    glm::mat4 model_view_projection =
        scene.camera_->GetViewProjectionMatrix() * point_light.GetModelMatrix();
    glUniformMatrix4fv(stencil_pass_model_view_projection_loc_, 1, GL_FALSE,
                       &model_view_projection[0][0]);

    glBindVertexArray(sphere_vao_);
    glDrawElements(GL_TRIANGLES, 8 * 8 * 6, GL_UNSIGNED_INT, 0);

    // 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());

    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]);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, gbuffer0_);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, gbuffer1_);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, gbuffer2_);

    glBindVertexArray(sphere_vao_);
    glDrawElements(GL_TRIANGLES, 8 * 8 * 6, GL_UNSIGNED_INT, 0);

    glCullFace(GL_BACK);
  }
}

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),
      hdr_fbo_(hdr_fbo),
      gbuffer0_(gbuffer0),
      gbuffer1_(gbuffer1),
      gbuffer2_(gbuffer2),
      sphere_vao_(sphere_vao),
      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")) {}

PointLightPass::~PointLightPass() {
  glDeleteProgram(stencil_pass_shader_program_);
  glDeleteProgram(shader_program_);
}

}  // namespace game

コンストラクタはG-Bufferや出力用のHDRバッファを受け取り、ShaderProgramを作り、Uniform Locationを取得しています。

ステンシルを実行するパスと、実際のライティングを行うパスの2パスになるので、Shader Programを2つ作っています。

PointLightPass::Renderではシーンに含まれるポイントライトでループを回しています。

最初にステンシルパスのプログラムを書いています。 ステンシルをクリアした後に、先にあげた記事の通り球体を両面描画してステンシルの計算を行っています。

次にライティングの計算を行うパスです。 ステンシルが0ではなくなった部分がライティングを計算すべきこのライトの栄養範囲の部分なので、ステンシルが0ではない部分を計算するようにステンシルテストを設定しています。 デプステストはオフにして、球体の奥側をレンダリングするためにCullFaceをFrontにしています。 奥側をレンダリングしないと、カメラがライトの影響範囲の球体の内部に入ったときにうまくいきません。 Uniform変数を指定してG-Bufferをバインドして球体を描画してライティングの計算を走らせています。

PointLightPass用のシェーダ

まず最初にステンシルパス用のシェーダです。 shader/PunctualLightStencilPass.vertshader/PunctualLightStencilPass.vertを作成します。

shader/PunctualLightStencilPass.vert
#version 460

layout (location = 0) in vec4 position;

uniform mat4 ModelViewProjection;

void main()
{
  gl_Position = ModelViewProjection * position;
}
shader/PunctualLightStencilPass.frag
#version 460

void main() {}

ステンシルの更新が目的なのでフラグメントシェーダは空っぽです。

ちなみにPunctualLightというのはポイントライトとスポットライトを合わせた呼び方のようです?

次にshader/PointLightPass.vertshader/PointLightPass.fragを作成します。

shader/PointLightPass.vert
#version 460

layout (location = 0) in vec4 position;

uniform mat4 ModelViewProjection;

void main()
{
  gl_Position = ModelViewProjection * position;
}
shader/PointLightPass.frag
#version 460

layout (location = 0) out vec3 outRadiance;

layout (binding = 0) uniform sampler2D GBuffer0;
layout (binding = 1) uniform sampler2D GBuffer1;
layout (binding = 2) uniform sampler2D GBuffer2;

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


const float PI = 3.14159265358979323846;


// Calc gbuffer texcoord #######################################################
vec2 CalcTexCoord()
{
   return gl_FragCoord.xy / textureSize(GBuffer0, 0);
}
// #############################################################################


// world pos from depth texture ################################################
float DecodeDepth(float d)
{
  return -d * (ProjectionParams.y - ProjectionParams.x) - ProjectionParams.x;
}

vec3 worldPosFromDepth(float d, vec2 uv)
{
  float depth = DecodeDepth(d);
  float m22 = -(ProjectionParams.y + ProjectionParams.x) / (ProjectionParams.y - ProjectionParams.x);
  float m23 = -2.0 * ProjectionParams.y * ProjectionParams.x / (ProjectionParams.y - ProjectionParams.x);
  float z = depth * m22 + m23;
  float w = -depth;
  vec4 projectedPos = vec4(uv.x * 2.0 - 1.0, uv.y * 2.0 - 1.0, z / w, 1.0);
  vec4 worldPos = ViewProjectionI * projectedPos;
  return worldPos.xyz / worldPos.w;
}
// #############################################################################


// attenuation #################################################################
float DistanceAttenuation(float distance)
{
  float EPSILON = 0.01;
  float att = 1.0 / (distance * distance + EPSILON);
  float smoothatt = 1 - pow(distance / lightRange, 4.0);
  smoothatt = max(smoothatt, 0.0);
  smoothatt =  smoothatt * smoothatt;
  return att * smoothatt;
}

vec3 LightIrradiance(float intensity, vec3 color, vec3 L, vec3 N, float distance)
{
  return 1.0 / (4.0 * PI) * intensity * color * max(0, dot(L, N)) * DistanceAttenuation(distance);
}
// #############################################################################


// BRDF ######################################################################
vec3 NormalizedLambert(vec3 diffuseColor) {
  return diffuseColor / PI;
}

vec3 F_Schlick(vec3 F0, vec3 H, vec3 V) {
  return (F0 + (1.0 - F0) * pow(1.0 - max(dot(V, H), 0), 5.0));
}

float D_GGX(float a, float NoH) {
  float a2 = a * a;
  float NoH2 = NoH * NoH;
  float d = NoH2 * (a2 - 1.0) + 1.0;
  return a2 / (PI * d * d);
}

float G_SchlickGGX(float a, float NoV) {
  float EPSILON = 0.001;
  float k = a * a / 2 + EPSILON;
  float up = NoV;
  float down = NoV * (1 - k) + k;
  return up / down;
}

float G_Smith(float a, float NoV, float NoL) {
  float ggx0 = G_SchlickGGX(1, NoV);
  float ggx1 = G_SchlickGGX(1, NoL);
  return ggx0 * ggx1;
}

vec3 BRDF(vec3 albedo, float metallic, float roughness, vec3 N, vec3 V, vec3 L, vec3 H) {
  float EPSILON = 0.001;
  vec3 F0 = mix(vec3(0.04), albedo, metallic);
  float NoH = max(dot(N, H), 0);
  float NoV = max(dot(N, V), 0);
  float NoL = max(dot(N, L), 0);
  float a = roughness * roughness;

  // specular
  vec3 F = F_Schlick(F0, H, V);
  float D = D_GGX(a, NoH);
  float G = G_Smith(a, NoV, NoL);
  vec3 up = F * D * G;
  float down = max(4.0 * NoV * NoL, EPSILON);
  vec3 specular = up / down;

  // diffuse
  vec3 diffuse = NormalizedLambert(albedo);

  vec3 kD = vec3(1.0) - F;
  kD *= 1.0 - metallic;
  return kD * diffuse + specular;
}
// ###########################################################################


// ACES ######################################################################
const mat3 sRGB_2_AP0 = mat3(
  0.4397010, 0.0897923, 0.0175440,
  0.3829780, 0.8134230, 0.1115440,
  0.1773350, 0.0967616, 0.8707040
);

vec3 sRGBToACES(vec3 srgb)
{
  return sRGB_2_AP0 * srgb;
}
// ###########################################################################


// ###################
// main
// ###################
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);

  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);

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

今回はUV座標が前回のDirectional Lightの場合と違って頂点シェーダから渡ってこないので、最初に計算しています。

G-Bufferからマテリアルのパラメータを読み出して、幾何情報を計算して、ライトの減衰を計算して、照度を計算してBRDFと掛け合わせてカメラに向かって射出される輝度を求めています。

減衰の計算は1r2\frac{1}{r^2}をベースに0除算を避けるためのEPSILONと、影響度をRangeで0にするための窓関数を使っています。 ここらへんは以前に説明したことがあったと思います。

SceneRendererクラスの変更点

SceneRendererクラスの変更点を次に示します。

まず最初に球体のメッシュを作成します。

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

  const GLuint fullscreen_mesh_vao_;
  const GLuint fullscreen_mesh_vertices_vbo_;
  const GLuint fullscreen_mesh_uvs_vbo_;

  const GLuint sphere_vao_;  const GLuint sphere_vertices_vbo_;  const GLuint sphere_indices_ibo_;

球体メッシュ作成用の静的メンバ関数を用意しました。

scene_renderer.h
  /**
   * @brief 球状のメッシュのVAOを作成する
   * @return 作成したVAOのID
   *
   * 作成した球状のメッシュはPunctual Lightの描画範囲を決めるのに使います。
   */
  static const GLuint CreateSphereMeshVao();

  /**
   * @brief 球状のメッシュのVBOを作成する
   * @return 作成したVBOのID
   */
  static const GLuint CreateSphereMeshVbo(const GLuint sphere_mesh_vao);

  /**
   * @brief 球状のメッシュのIBOを作成する
   * @return 作成したIBOのID
   */
  static const GLuint CreateSphereMeshIbo(const GLuint sphere_mesh_vao);
scene_renderer.cpp
const GLuint SceneRenderer::CreateSphereMeshVao() {
  GLuint sphere_vao;
  glGenVertexArrays(1, &sphere_vao);
  return sphere_vao;
}

const GLuint SceneRenderer::CreateSphereMeshVbo(const GLuint sphere_mesh_vao) {
  glBindVertexArray(sphere_mesh_vao);

  const int slices = 8, stacks = 8;
  std::vector<glm::vec3> sphere_vertices;
  for (int j = 0; j <= stacks; ++j) {
    const float t(static_cast<float>(j) / static_cast<float>(stacks));
    const float y(cos(3.141593f * t)), r(sin(3.141593f * t));
    for (int i = 0; i <= slices; ++i) {
      const float s(static_cast<float>(i) / static_cast<float>(slices));
      const float z(r * cos(6.283185f * s)), x(r * sin(6.283185f * s));
      sphere_vertices.emplace_back(x, y, z);
    }
  }

  GLuint sphere_vertices_vbo;
  glGenBuffers(1, &sphere_vertices_vbo);
  glBindBuffer(GL_ARRAY_BUFFER, sphere_vertices_vbo);
  glBufferData(GL_ARRAY_BUFFER, sphere_vertices.size() * sizeof(glm::vec3),
               &sphere_vertices[0], GL_STATIC_DRAW);
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, static_cast<void*>(0));

  glBindVertexArray(0);

  return sphere_vertices_vbo;
}

const GLuint SceneRenderer::CreateSphereMeshIbo(const GLuint sphere_mesh_vao) {
  glBindVertexArray(sphere_mesh_vao);

  const int slices = 8, stacks = 8;
  std::vector<GLuint> sphere_indices;
  for (int j = 0; j < stacks; ++j) {
    const int k((slices + 1) * j);
    for (int i = 0; i < slices; ++i) {
      const GLuint k0(k + i);
      const GLuint k1(k0 + 1);
      const GLuint k2(k1 + slices);
      const GLuint k3(k2 + 1);

      sphere_indices.emplace_back(k0);
      sphere_indices.emplace_back(k2);
      sphere_indices.emplace_back(k3);

      sphere_indices.emplace_back(k0);
      sphere_indices.emplace_back(k3);
      sphere_indices.emplace_back(k1);
    }
  }

  GLuint sphere_indices_ibo;
  glGenBuffers(1, &sphere_indices_ibo);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sphere_indices_ibo);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sphere_indices.size() * sizeof(GLuint),
               &sphere_indices[0], GL_STATIC_DRAW);

  glBindVertexArray(0);

  return sphere_indices_ibo;
}

球体のメッシュを作成しています。

ヘッダファイルを追加し、PointLightPassをメンバ変数に追加します。

scene_renderer.h
#include "directional_light_pass.h"
#include "exposure_pass.h"
#include "geometry_pass.h"
#include "point_light_pass.h"#include "scene.h"
#include "tonemapping_pass.h"
scene_renderer.h
  GeometryPass geometry_pass_;
  DirectionalLightPass directional_light_pass_;
  PointLightPass point_light_pass_;  ExposurePass exposure_pass_;
  TonemappingPass tonemapping_pass_;

次にコンストラクタです。

scene_renderer.cpp
SceneRenderer::SceneRenderer(const GLuint width, const GLuint height)
    : width_(width),
      height_(height),

      fullscreen_mesh_vao_(CreateFullscreenMeshVao()),
      fullscreen_mesh_vertices_vbo_(
          CreateFullscreenMeshVerticesVbo(fullscreen_mesh_vao_)),
      fullscreen_mesh_uvs_vbo_(
          CreateFullscreenMeshUvsVbo(fullscreen_mesh_vao_)),

      sphere_vao_(CreateSphereMeshVao()),      sphere_vertices_vbo_(CreateSphereMeshVbo(sphere_vao_)),      sphere_indices_ibo_(CreateSphereMeshIbo(sphere_vao_)),
      gbuffer0_(CreateGBuffer0(width, height)),
      gbuffer1_(CreateGBuffer1(width, height)),
      gbuffer2_(CreateGBuffer2(width, height)),
      gbuffer_depth_(CreateGBufferDepth(width, height)),
      gbuffer_fbo_(
          CreateGBufferFbo(gbuffer0_, gbuffer1_, gbuffer2_, gbuffer_depth_)),

      hdr_color_buffer_(CreateHdrColorBuffer(width, height)),
      hdr_depth_buffer_(CreateHdrDepthBuffer(width, height)),
      hdr_fbo_(CreateHdrFbo(hdr_color_buffer_, hdr_depth_buffer_)),

      exposured_color_buffer_(CreateExposuredColorBuffer(width, height)),
      exposured_fbo_(CreateExposuredFbo(exposured_color_buffer_)),

      geometry_pass_(gbuffer_fbo_),
      directional_light_pass_(hdr_fbo_, gbuffer0_, gbuffer1_, gbuffer2_,
                              fullscreen_mesh_vao_, width, height),
      point_light_pass_(hdr_fbo_, gbuffer0_, gbuffer1_, gbuffer2_, sphere_vao_,                        width, height),      exposure_pass_(hdr_color_buffer_, exposured_fbo_, fullscreen_mesh_vao_,
                     width, height),
      tonemapping_pass_(exposured_color_buffer_, fullscreen_mesh_vao_, width,
                        height) {}

球体メッシュの作成とpoint_light_pass_の初期化を追加しました。

SceneRenderer::Renderに次のように追加します。

scene_renderer.cpp
void SceneRenderer::Render(const Scene& scene, const double delta_time) {
  geometry_pass_.Render(scene);

  // HDRカラーバッファにDepthとStencilの転写
  glBindFramebuffer(GL_READ_FRAMEBUFFER, gbuffer_fbo_);
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, hdr_fbo_);
  glBlitFramebuffer(0, 0, width_, height_, 0, 0, width_, height_,
                    GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);

  directional_light_pass_.Render(scene);

  point_light_pass_.Render(scene);
  exposure_pass_.SetExposure(0.1f);
  exposure_pass_.Render();

  tonemapping_pass_.Render();
}

リソースの開放処理のSceneRenderer::Releaseも更新します。

scene_renderer.cpp
void SceneRenderer::Release() {
  glDeleteVertexArrays(1, &fullscreen_mesh_vao_);
  glDeleteBuffers(1, &fullscreen_mesh_vertices_vbo_);
  glDeleteBuffers(1, &fullscreen_mesh_uvs_vbo_);

  glDeleteVertexArrays(1, &sphere_vao_);  glDeleteBuffers(1, &sphere_vertices_vbo_);  glDeleteBuffers(1, &sphere_indices_ibo_);
  glDeleteFramebuffers(1, &gbuffer_fbo_);
  glDeleteTextures(1, &gbuffer0_);
  glDeleteTextures(1, &gbuffer1_);
  glDeleteTextures(1, &gbuffer2_);
  glDeleteRenderbuffers(1, &gbuffer_depth_);

  glDeleteFramebuffers(1, &hdr_fbo_);
  glDeleteTextures(1, &hdr_color_buffer_);
  glDeleteRenderbuffers(1, &hdr_depth_buffer_);

  glDeleteFramebuffers(1, &exposured_fbo_);
  glDeleteTextures(1, &exposured_color_buffer_);
}

実行結果

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

2020 04 07 15 51 43

プログラム全文

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

GitHub: MatchaChoco010/OpenGL-PBR-Map at v13