OpenGLでDeferredシェーディングを実装する(Point Light Pass)
はじめに
この記事はシリーズ記事です。目次はこちら。
この記事ではPBRの古典的DeferredシェーディングのPoint Light Passを実装するところまでを行います。
ポイントライトの描画範囲
ポイントライトを描画する際も、DirectionalLightのときのように全画面を覆うメッシュを使って描画してもよいのですが、ライトの影響範囲を考慮してもう少しライティング計算を行うピクセルを削ることができます。
今回は次のページで説明されているステンシルを使ったライトの計算領域の計算を行います。
ステンシルテストをBackFaceとFrontFaceで別の命令を与えることで、ライトの範囲内にある領域を良い感じに計算しています。
前回のプログラムからの変更点
前回のプログラムからの変更点を次に示します。
PointLightクラスの作成
PointLightクラスを作成します。
#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_
#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に格納します。
#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
// 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クラスの作成
次にポイントライトのパスのクラスを作ります。
#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_
#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.vert
とshader/PunctualLightStencilPass.vert
を作成します。
#version 460
layout (location = 0) in vec4 position;
uniform mat4 ModelViewProjection;
void main()
{
gl_Position = ModelViewProjection * position;
}
#version 460
void main() {}
ステンシルの更新が目的なのでフラグメントシェーダは空っぽです。
ちなみにPunctualLightというのはポイントライトとスポットライトを合わせた呼び方のようです?
次にshader/PointLightPass.vert
とshader/PointLightPass.frag
を作成します。
#version 460
layout (location = 0) in vec4 position;
uniform mat4 ModelViewProjection;
void main()
{
gl_Position = ModelViewProjection * position;
}
#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と掛け合わせてカメラに向かって射出される輝度を求めています。
減衰の計算はをベースに0除算を避けるためのEPSILONと、影響度をRangeで0にするための窓関数を使っています。 ここらへんは以前に説明したことがあったと思います。
SceneRendererクラスの変更点
SceneRendererクラスの変更点を次に示します。
まず最初に球体のメッシュを作成します。
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_;
球体メッシュ作成用の静的メンバ関数を用意しました。
/**
* @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);
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をメンバ変数に追加します。
#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"
GeometryPass geometry_pass_;
DirectionalLightPass directional_light_pass_;
PointLightPass point_light_pass_; ExposurePass exposure_pass_;
TonemappingPass tonemapping_pass_;
次にコンストラクタです。
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
に次のように追加します。
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
も更新します。
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_);
}
実行結果
実行結果は次のとおりです。
プログラム全文
プログラム全文はGitHubにアップロードしてあります。