OpenGLでDisneyの物理ベースシェーディングを書いてみる
はじめに
この記事はシリーズ記事です。目次はこちら。
この記事ではDisneyの物理ベースシェーダーでモデルを表示するところまでを行います。
Disneyの原則モデル
有名な物理ベースのモデルにDisneyの原則モデルがあります。 次の論文の中で「Disney “principled” BRDF」(Disney 原則BRDF) を提唱しています。
3DCGのソフトウェアに搭載されているマテリアルでPrincipledマテリアルというのは、このディズニーの原則BRDFに基づいたものになります。
リアルタイムレンダリングを行うゲームエンジンなどでも、このDisneyのモデルのサブセットが搭載されていることが
モデルの準備
モデルを準備します。今回はまたUV Sphereを利用します。
Substance Painterで銅のマテリアルを割り当ててテクスチャを出力します。
もう一つラフネスを高めにしたものも出力しておきます。
前回からのプログラムの変更点
前回のプログラムからの変更点を次に示します。
Applicationクラスの変更点
anisotropicの値をシェーダに渡すためのUniform Locationを保持します。
private:
GLFWwindow* window_;
GLuint program_;
GLuint model_loc_;
GLuint model_it_loc_;
GLuint view_projection_loc_;
GLuint world_camera_position_loc_;
GLuint emissive_intensity_loc_;
GLuint anisotropic_loc_; std::vector<MeshEntity> mesh_entities_;
std::unique_ptr<Camera> camera_;
// Uniform変数の位置を取得
model_loc_ = glGetUniformLocation(program_, "Model");
model_it_loc_ = glGetUniformLocation(program_, "ModelIT");
view_projection_loc_ = glGetUniformLocation(program_, "ViewProjection");
world_camera_position_loc_ =
glGetUniformLocation(program_, "worldCameraPosition");
emissive_intensity_loc_ = glGetUniformLocation(program_, "emissiveIntensity");
anisotropic_loc_ = glGetUniformLocation(program_, "anisotropic");
読み込むモデルとマテリアルを変更します。
// Meshの読み込み
auto mesh = Mesh::LoadObjMesh("sphere.obj");
// Materialの作成
auto material = std::make_shared<Material>(
Texture("sphere_BaseColor.png", true), Texture("sphere_Metallic.png", false), Texture("sphere_Roughness.png", false), Texture("sphere_Normal.png", false), Texture("sphere_Emissive.png", true), 0.0f);
MeshEntityの並び順を変えました。
// MeshEntityの作成
mesh_entities_.emplace_back(mesh, material, glm::vec3(-2.0f, 0.0f, 0.0f),
glm::vec3(0.0f), glm::vec3(1.0f));
mesh_entities_.emplace_back(mesh, material, glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(0.0f), glm::vec3(1.0f));
mesh_entities_.emplace_back(mesh, material, glm::vec3(2.0f, 0.0f, 0.0f),
glm::vec3(0.0f), glm::vec3(1.0f));
Application::Update
内のfor文でanisotropicの値を渡すようにします。
GLfloat anisotropic = 0.0f; for (auto&& mesh_entity : mesh_entities_) {
auto model = mesh_entity.GetModelMatrix();
auto model_it = glm::inverseTranspose(model);
glUniformMatrix4fv(model_loc_, 1, GL_FALSE, &model[0][0]);
glUniformMatrix4fv(model_it_loc_, 1, GL_FALSE, &model_it[0][0]);
glUniform1f(emissive_intensity_loc_,
mesh_entity.material_->emissive_intensity_);
glUniform1f(anisotropic_loc_, anisotropic); anisotropic += 0.5f;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D,
mesh_entity.material_->albedo_map_.GetTextureId());
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D,
mesh_entity.material_->metallic_map_.GetTextureId());
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D,
mesh_entity.material_->roughness_map_.GetTextureId());
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D,
mesh_entity.material_->normal_map_.GetTextureId());
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D,
mesh_entity.material_->emissive_map_.GetTextureId());
mesh_entity.mesh_->Draw();
}
これで描画する球体の左から順に0.0、0.5、1.0が渡されます。
シェーダの変更点
頂点シェーダは変わらずです。
フラグメントシェーダを変更します。
まずはUniform変数にanisotropic
を追加します。
uniform vec3 worldCameraPosition;
uniform float emissiveIntensity;
uniform float anisotropic;
次にDisneyBRDFの次の実装をGLSLに落とし込みます。
// # Copyright Disney Enterprises, Inc. All rights reserved.
// #
// # Licensed under the Apache License, Version 2.0 (the "License");
// # you may not use this file except in compliance with the License
// # and the following modification to it: Section 6 Trademarks.
// # deleted and replaced with:
// #
// # 6. Trademarks. This License does not grant permission to use the
// # trade names, trademarks, service marks, or product names of the
// # Licensor and its affiliates, except as required for reproducing
// # the content of the NOTICE file.
// #
// # You may obtain a copy of the License at
// # http://www.apache.org/licenses/LICENSE-2.0
float SchlickFresnel(float cosTheta)
{
return pow(clamp((1 - cosTheta), 0, 1), 5.0);
}
float D_GTR1(float NdotH, float a)
{
float a2 = a * a;
float tmp = 1 + (a2 - 1) * NdotH * NdotH;
return (a2 - 1) / (PI * log(a2) * tmp);
}
float D_GTR2aniso(float NdotH, float HdotX, float HdotY, float ax, float ay)
{
float tmp = (HdotX * HdotX) / (ax * ax) + (HdotY * HdotY) / (ay * ay) + NdotH * NdotH;
return 1 / (PI * ax * ay * tmp * tmp);
}
float G_GGX(float NdotV, float a)
{
float a2 = a * a;
float down = NdotV + sqrt(a2 + NdotV * NdotV - a2 * NdotV * NdotV);
return 1 / down;
}
float G_GGXaniso(float NdotV, float VdotX, float VdotY, float ax, float ay)
{
float tmp = VdotX * VdotX * ax * ax + VdotY * VdotY * ay * ay + NdotV * NdotV;
float down = NdotV + sqrt(tmp);
return 1 / down;
}
vec3 DisneyBRDF(vec3 L, vec3 V, vec3 N, vec3 H, vec3 X, vec3 Y, vec3 baseColor, float subsurface, float metallic, float specular, float specularTint, float roughness, float anisotropic, float sheen, float sheenTint, float clearcoat, float clearcoatGloss)
{
float NdotL = dot(N, L);
float NdotV = dot(N, V);
if (NdotL < 0 || NdotV < 0) return vec3(0);
float NdotH = dot(N, H);
float LdotH = dot(L, H);
float luminance = 0.3 * baseColor.r + 0.6 * baseColor.g + 0.1 * baseColor.b;
vec3 C_tint = luminance > 0 ? baseColor / luminance : vec3(1);
vec3 C_spec = mix(specular * 0.08 * mix(vec3(1), C_tint, specularTint), baseColor, metallic);
vec3 C_sheen = mix(vec3(1), C_tint, sheenTint);
// diffuse
float F_i = SchlickFresnel(NdotL);
float F_o = SchlickFresnel(NdotV);
float F_d90 = 0.5 + 2 * LdotH * LdotH * roughness;
float F_d = mix(1.0, F_d90, F_i) * mix(1.0, F_d90, F_o);
float F_ss90 = LdotH * LdotH * roughness;
float F_ss = mix(1.0, F_ss90, F_i) * mix(1.0, F_ss90, F_o);
float ss = 1.25 * (F_ss * (1 / (NdotL + NdotV) - 0.5) + 0.5);
float FH = SchlickFresnel(LdotH);
vec3 F_sheen = FH * sheen * C_sheen;
vec3 BRDFdiffuse = ((1 / PI) * mix(F_d, ss, subsurface) * baseColor + F_sheen) * (1 - metallic);
// specular
float aspect = sqrt(1 - anisotropic * 0.9);
float roughness2 = roughness * roughness;
float a_x = max(0.001, roughness2 / aspect);
float a_y = max(0.001, roughness2 * aspect);
float D_s = D_GTR2aniso(NdotH, dot(H, X), dot(H, Y), a_x, a_y);
vec3 F_s = mix(C_spec, vec3(1), FH);
float G_s = G_GGXaniso(NdotL, dot(L, X), dot(L, Y), a_x, a_y) * G_GGXaniso(NdotV, dot(V, X), dot(V, Y), a_x, a_y);
vec3 BRDFspecular = G_s * F_s * D_s;
// clearcoat
float D_r = D_GTR1(NdotH, mix(0.1, 0.001, clearcoatGloss));
float F_r = mix(0.04, 1.0, FH);
float G_r = G_GGX(NdotL, 0.25) * G_GGX(NdotV, 0.25);
vec3 BRDFclearcoat = vec3(0.25 * clearcoat * G_r * F_r * D_r);
return BRDFdiffuse + BRDFspecular + BRDFclearcoat;
}
main関数内でtangentとbitangentを計算します。
vec3 tangent = normalize(vWorldTangent);
vec3 bitangent = normalize(cross(N, tangent));
tangent = normalize(cross(bitangent, N));
パラメータをシェーダにベタ書きします。
float subsurface = 0.0;
float specular = 0.5;
float specularTint = 0.0;
float sheen = 0.0;
float sheenTint = 0.5;
float clearcoat = 0.0;
float clearcoatGloss = 1.0;
ライティングで利用するBRDFをDisneyBRDFに入れ替えます。
for (int i = 0; i < 3; i++) {
vec3 L = normalize(worldLightPositions[i] - vWorldPosition);
vec3 H = normalize(L + V);
float attenuation =
DistanceAttenuation(distance(worldLightPositions[i], vWorldPosition), 20.0);
vec3 irradiance =
lightIntensities[i] / (4.0 * PI) * attenuation * sRGBToACES(lightColors[i]) * max(dot(N, L), 0);
// color += BRDF(albedo, metallic, roughness, N, V, L, H) * irradiance; color += DisneyBRDF(L, V, N, H, tangent, bitangent, albedo, subsurface, metallic, specular, specularTint, roughness, anisotropic, sheen, sheenTint, clearcoat, clearcoatGloss) * irradiance; }
クリアコートの値などDisneyのモデルで追加されるパラメータは今回はシェーダに直接書いています。 本来ならばこれらのパラメータもUniform変数やテクスチャを通して渡せるようにしたほうが良いでしょう。 また、anisotropicのXY方向をUVのタンジェントとバイタンジェント方向にしていますが、実用するにはanisotropy angleをテクスチャで渡してやるなどしたほうが良さげです。
実行結果
実行結果は次のとおりです。
anisotropicの値によってハイライトが引き伸ばされているのが確認できます。
ラフネス高めのテクスチャで実行すると次の通り。
ラフネス高めでClearcoatを1.0にすると次の通り。
float subsurface = 0.0;
float specular = 0.5;
float specularTint = 0.0;
float sheen = 0.0;
float sheenTint = 0.5;
// float clearcoat = 0.0;
float clearcoat = 1.0;
float clearcoatGloss = 0.95;
内側の層の粗いラフネスのぼやけたハイライトと、外側の層のクリアコートによる鋭いハイライトが確認できました。
プログラム全文
プログラム全文はGitHubにアップロードしてあります。