diff --git a/Criminowl.pro b/Criminowl.pro index 92ebd88..ca0c933 100644 --- a/Criminowl.pro +++ b/Criminowl.pro @@ -38,13 +38,8 @@ HEADERS += \ include/MaterialPBR.h \ include/Scene.h \ include/DemoScene.h \ - include/MaterialPhong.h \ include/ShaderLib.h \ include/MeshVBO.h \ - include/MaterialWireframe.h \ - include/MaterialFractal.h \ - include/MaterialEnvMap.h \ - include/MaterialBump.h \ include/TriMesh.h \ include/Edge.h @@ -58,13 +53,8 @@ SOURCES += \ src/MaterialPBR.cpp \ src/Scene.cpp \ src/DemoScene.cpp \ - src/MaterialPhong.cpp \ src/ShaderLib.cpp \ src/MeshVBO.cpp \ - src/MaterialWireframe.cpp \ - src/MaterialFractal.cpp \ - src/MaterialEnvMap.cpp \ - src/MaterialBump.cpp \ src/TriMesh.cpp OTHER_FILES += \ @@ -74,6 +64,7 @@ OTHER_FILES += \ FORMS += ui/mainwindow.ui + linux:{ LIBS += -lGL -lGLU -lGLEW -lassimp } diff --git a/include/DemoScene.h b/include/DemoScene.h index 24d991a..470082c 100644 --- a/include/DemoScene.h +++ b/include/DemoScene.h @@ -3,7 +3,6 @@ #include "Scene.h" #include "MaterialPBR.h" -#include "MaterialPhong.h" #include "ShaderLib.h" diff --git a/include/MaterialBump.h b/include/MaterialBump.h deleted file mode 100644 index a1294e8..0000000 --- a/include/MaterialBump.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef MATERIALBUMP_H -#define MATERIALBUMP_H - -#include "Material.h" -#include -#include - -class MaterialBump : public Material -{ -public: - MaterialBump(const std::shared_ptr &io_camera, const std::shared_ptr &io_shaderLib, std::array* io_matrices) : - Material(io_camera, io_shaderLib, io_matrices) - {} - MaterialBump(const MaterialBump&) = default; - MaterialBump& operator=(const MaterialBump&) = default; - MaterialBump(MaterialBump&&) = default; - MaterialBump& operator=(MaterialBump&&) = default; - ~MaterialBump() override = default; - - virtual void init() override; - - virtual void update() override; - - virtual const char* shaderFileName() const override; - -private: - void initColor(); - void initNormal(); - std::unique_ptr m_colorMap; - std::unique_ptr m_normalMap; -}; - -#endif // MATERIALBUMP_H diff --git a/include/MaterialEnvMap.h b/include/MaterialEnvMap.h deleted file mode 100644 index 20eb0dd..0000000 --- a/include/MaterialEnvMap.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef MATERIALENVMAP_H -#define MATERIALENVMAP_H - -#include "Material.h" -#include -#include - -class MaterialEnvMap : public Material -{ -public: - MaterialEnvMap(const std::shared_ptr &io_camera, const std::shared_ptr &io_shaderLib, std::array* io_matrices) : - Material(io_camera, io_shaderLib, io_matrices) - {} - MaterialEnvMap(const MaterialEnvMap&) = default; - MaterialEnvMap& operator=(const MaterialEnvMap&) = default; - MaterialEnvMap(MaterialEnvMap&&) = default; - MaterialEnvMap& operator=(MaterialEnvMap&&) = default; - ~MaterialEnvMap() override = default; - - virtual void init() override; - - virtual void update() override; - - virtual const char* shaderFileName() const override; - -private: - void initEnvMap(); - void initGlossMap(); - std::unique_ptr m_envMap; - std::unique_ptr m_glossMap; - -}; - -#endif // MATERIALENVMAP_H diff --git a/include/MaterialFractal.h b/include/MaterialFractal.h deleted file mode 100644 index ca5eb4a..0000000 --- a/include/MaterialFractal.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef MATERIALFRACTAL_H -#define MATERIALFRACTAL_H - -#include "Material.h" -#include - -class MaterialFractal : public Material -{ -public: - MaterialFractal(const std::shared_ptr &io_camera, const std::shared_ptr &io_shaderLib, std::array* io_matrices) : - Material(io_camera, io_shaderLib, io_matrices) - {} - MaterialFractal(const MaterialFractal&) = default; - MaterialFractal& operator=(const MaterialFractal&) = default; - MaterialFractal(MaterialFractal&&) = default; - MaterialFractal& operator=(MaterialFractal&&) = default; - ~MaterialFractal() override = default; - - virtual void init() override; - - virtual void update() override; - - virtual const char* shaderFileName() const override; - - virtual void handleKey(QKeyEvent* io_event, QOpenGLContext* io_context) override; - -private: - std::chrono::high_resolution_clock::time_point m_last; - float m_time = 0.0f; - bool m_updateTime = true; -}; - - -#endif // MATERIALFRACTAL_H diff --git a/include/MaterialPhong.h b/include/MaterialPhong.h deleted file mode 100644 index 785cc70..0000000 --- a/include/MaterialPhong.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef MATERIALPHONG_H -#define MATERIALPHONG_H - -#include "Material.h" - -class MaterialPhong : public Material -{ -public: - MaterialPhong(const std::shared_ptr &io_camera, const std::shared_ptr &io_shaderLib, std::array* io_matrices) : - Material(io_camera, io_shaderLib, io_matrices) - {} - MaterialPhong(const MaterialPhong&) = default; - MaterialPhong& operator=(const MaterialPhong&) = default; - MaterialPhong(MaterialPhong&&) = default; - MaterialPhong& operator=(MaterialPhong&&) = default; - ~MaterialPhong() override = default; - - virtual void init() override; - - virtual void update() override; - - virtual const char* shaderFileName() const override; - -}; - -#endif // MATERIALPHONG_H diff --git a/include/MaterialWireframe.h b/include/MaterialWireframe.h deleted file mode 100644 index b9b4e28..0000000 --- a/include/MaterialWireframe.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef MATERIALWIREFRAME_H -#define MATERIALWIREFRAME_H - -#include "Material.h" - -class MaterialWireframe : public Material -{ -public: - MaterialWireframe(const std::shared_ptr &io_camera, const std::shared_ptr &io_shaderLib, std::array* io_matrices) : - Material(io_camera, io_shaderLib, io_matrices) - {} - MaterialWireframe(const MaterialWireframe&) = default; - MaterialWireframe& operator=(const MaterialWireframe&) = default; - MaterialWireframe(MaterialWireframe&&) = default; - MaterialWireframe& operator=(MaterialWireframe&&) = default; - ~MaterialWireframe() override = default; - - virtual void init() override; - - virtual void update() override; - - virtual const char* shaderFileName() const override; - -}; - -#endif // MATERIALWIREFRAME_H diff --git a/shaders/hdr_cubemap_brdf_frag.glsl b/shaders/hdr_cubemap_brdf_frag.glsl index a9d11f7..8c34599 100644 --- a/shaders/hdr_cubemap_brdf_frag.glsl +++ b/shaders/hdr_cubemap_brdf_frag.glsl @@ -1,114 +1,71 @@ #version 410 core -layout(location=0) out vec2 FragColor; +layout(location=0) out vec2 fragColor; in vec2 vs_texCoords; const float k_PI = 3.14159265359; +#include "shaders/include/pbr_funcs.h" + // ---------------------------------------------------------------------------- -// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html -// efficient VanDerCorpus calculation. -float RadicalInverse_VdC(uint bits) -{ - bits = (bits << 16u) | (bits >> 16u); - bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); - bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); - bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); - bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return float(bits) * 2.3283064365386963e-10; // / 0x100000000 -} -// ---------------------------------------------------------------------------- -vec2 Hammersley(uint i, uint N) -{ - return vec2(float(i)/float(N), RadicalInverse_VdC(i)); -} -// ---------------------------------------------------------------------------- -vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) +float brdfSchlickGGX(float n_dot_v, float roughness) { - float a = roughness*roughness; - - float phi = 2.0 * k_PI * Xi.x; - float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); - float sinTheta = sqrt(1.0 - cosTheta*cosTheta); - - // from spherical coordinates to cartesian coordinates - halfway vector - vec3 H; - H.x = cos(phi) * sinTheta; - H.y = sin(phi) * sinTheta; - H.z = cosTheta; - - // from tangent-space H vector to world-space sample vector - vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - vec3 tangent = normalize(cross(up, N)); - vec3 bitangent = cross(N, tangent); - - vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; - return normalize(sampleVec); + // note that we use a different k for IBL + float k = (roughness * roughness) * 0.5; + return n_dot_v / (n_dot_v * (1.0 - k) + k); } // ---------------------------------------------------------------------------- -float GeometrySchlickGGX(float NdotV, float roughness) +float brdfGeometrySmith(vec3 n, vec3 v, vec3 l, float roughness) { - // note that we use a different k for IBL - float a = roughness; - float k = (a * a) / 2.0; + float n_dot_v = max(dot(n, v), 0.0); + float n_dot_l = max(dot(n, l), 0.0); + float ggx2 = brdfSchlickGGX(n_dot_v, roughness); + float ggx1 = brdfSchlickGGX(n_dot_l, roughness); - float nom = NdotV; - float denom = NdotV * (1.0 - k) + k; - - return nom / denom; + return ggx1 * ggx2; } // ---------------------------------------------------------------------------- -float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) -{ - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx2 = GeometrySchlickGGX(NdotV, roughness); - float ggx1 = GeometrySchlickGGX(NdotL, roughness); - return ggx1 * ggx2; -} -// ---------------------------------------------------------------------------- -vec2 IntegrateBRDF(float NdotV, float roughness) +vec2 integrateBRDF(float n_dot_v, float roughness) { - vec3 V; - V.x = sqrt(1.0 - NdotV*NdotV); - V.y = 0.0; - V.z = NdotV; + vec3 v; + v.x = sqrt(1.0 - n_dot_v*n_dot_v); + v.y = 0.0; + v.z = n_dot_v; - float A = 0.0; - float B = 0.0; + float a = 0.0; + float b = 0.0; - vec3 N = vec3(0.0, 0.0, 1.0); - - const uint SAMPLE_COUNT = 1024u; - for(uint i = 0u; i < SAMPLE_COUNT; ++i) - { - // generates a sample vector that's biased towards the - // preferred alignment direction (importance sampling). - vec2 Xi = Hammersley(i, SAMPLE_COUNT); - vec3 H = ImportanceSampleGGX(Xi, N, roughness); - vec3 L = normalize(2.0 * dot(V, H) * H - V); + vec3 n = vec3(0.0, 0.0, 1.0); + + const uint k_sample_count = 1024u; + for(uint i = 0u; i < k_sample_count; ++i) + { + // generates a sample vector that's biased towards the + // preferred alignment direction (importance sampling). + vec3 h, l; + generateImportanceSample(i, k_sample_count, roughness, n, v, h, l); - float NdotL = max(L.z, 0.0); - float NdotH = max(H.z, 0.0); - float VdotH = max(dot(V, H), 0.0); + float n_dot_l = max(l.z, 0.0); + float n_dot_h = max(h.z, 0.0); + float v_dot_h = max(dot(v, h), 0.0); - if(NdotL > 0.0) - { - float G = GeometrySmith(N, V, L, roughness); - float G_Vis = (G * VdotH) / (NdotH * NdotV); - float Fc = pow(1.0 - VdotH, 5.0); + if(n_dot_l > 0.0) + { + float g = brdfGeometrySmith(n, v, l, roughness); + float g_vis = (g * v_dot_h) / (n_dot_h * n_dot_v); + float fc = pow(1.0 - v_dot_h, 5.0); - A += (1.0 - Fc) * G_Vis; - B += Fc * G_Vis; - } + a += (1.0 - fc) * g_vis; + b += fc * g_vis; } - A /= float(SAMPLE_COUNT); - B /= float(SAMPLE_COUNT); - return vec2(A, B); + } + a /= float(k_sample_count); + b /= float(k_sample_count); + return vec2(a, b); } // ---------------------------------------------------------------------------- void main() { - vec2 integratedBRDF = IntegrateBRDF(vs_texCoords.x, vs_texCoords.y); - FragColor = integratedBRDF; + vec2 integratedBRDF = integrateBRDF(vs_texCoords.x, vs_texCoords.y); + fragColor = integratedBRDF; } diff --git a/shaders/hdr_cubemap_irradiance_frag.glsl b/shaders/hdr_cubemap_irradiance_frag.glsl index 5ccd583..73b9cee 100644 --- a/shaders/hdr_cubemap_irradiance_frag.glsl +++ b/shaders/hdr_cubemap_irradiance_frag.glsl @@ -1,6 +1,6 @@ #version 410 core -layout(location=0) out vec4 FragColor; +layout(location=0) out vec4 fragColor; in vec3 vs_localPos; uniform samplerCube u_envMap; @@ -8,37 +8,34 @@ uniform samplerCube u_envMap; const float k_PI = 3.14159265359; void main() -{ - // The world vector acts as the normal of a tangent surface - // from the origin, aligned to WorldPos. Given this normal, calculate all - // incoming radiance of the environment. The result of this radiance - // is the radiance of light coming from -Normal direction, which is what - // we use in the PBR shader to sample irradiance. - vec3 N = normalize(vs_localPos); - - vec3 irradiance = vec3(0.0); - - // tangent space calculation from origin point - vec3 up = vec3(0.0, 1.0, 0.0); - vec3 right = cross(up, N); - up = cross(N, right); - - float sampleDelta = 0.025; - float nrSamples = 0.0; - for(float phi = 0.0; phi < 2.0 * k_PI; phi += sampleDelta) +{ + // Use the normal (world vector), to calculate the sum of all incoming radiance (irradiance). + // The result is light coming from -normal, so that's what we'll use to query later. + vec3 n = normalize(vs_localPos); + + vec3 irradiance = vec3(0.0); + + // tangent space calculation from origin point + vec3 up = vec3(0.0, 1.0, 0.0); + vec3 right = cross(up, n); + + float sampleDelta = 0.025; + float sampleCount = 0.0; + // Iterate evenly over the hemisphere + for(float phi = 0.0; phi < 2.0 * k_PI; phi += sampleDelta) + { + for(float theta = 0.0; theta < 0.5 * k_PI; theta += sampleDelta) { - for(float theta = 0.0; theta < 0.5 * k_PI; theta += sampleDelta) - { - // spherical to cartesian (in tangent space) - vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); - // tangent space to world - vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; - - irradiance += texture(u_envMap, sampleVec).rgb * cos(theta) * sin(theta); - nrSamples++; - } + // convert spherical to cartesian (in tangent space) + vec3 tgtSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); + // tangent space to world + vec3 sampleVec = tgtSample.x * right + tgtSample.y * up + tgtSample.z * n; + + irradiance += texture(u_envMap, sampleVec).rgb * cos(theta) * sin(theta); + ++sampleCount; } - irradiance = k_PI * irradiance * (1.0 / float(nrSamples)); - - FragColor = vec4(irradiance, 1.0); + } + irradiance *= k_PI * (1.0 / float(sampleCount)); + + fragColor = vec4(irradiance, 1.0); } diff --git a/shaders/hdr_cubemap_prefilter_frag.glsl b/shaders/hdr_cubemap_prefilter_frag.glsl index ccbafc9..d3770ea 100644 --- a/shaders/hdr_cubemap_prefilter_frag.glsl +++ b/shaders/hdr_cubemap_prefilter_frag.glsl @@ -1,106 +1,53 @@ #version 410 core -out vec4 FragColor; +out vec4 fragColor; in vec3 vs_localPos; uniform samplerCube u_envMap; uniform float u_roughness; + +#include "shaders/include/pbr_funcs.h" + const float k_PI = 3.14159265359; + // ---------------------------------------------------------------------------- -float DistributionGGX(vec3 N, vec3 H, float roughness) + +void main() { - float a = roughness*roughness; - float a2 = a*a; - float NdotH = max(dot(N, H), 0.0); - float NdotH2 = NdotH*NdotH; + // assume that v and r are equal to the normal to simplify + vec3 n, r, v; + n = r = v = normalize(vs_localPos); - float nom = a2; - float denom = (NdotH2 * (a2 - 1.0) + 1.0); - denom = k_PI * denom * denom; + const uint k_sample_count = 1024u; + vec3 prefilteredColor = vec3(0.0); + float totalWeight = 0.0; - return nom / denom; -} -// ---------------------------------------------------------------------------- -// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html -// efficient VanDerCorpus calculation. -float RadicalInverse_VdC(uint bits) -{ - bits = (bits << 16u) | (bits >> 16u); - bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); - bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); - bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); - bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return float(bits) * 2.3283064365386963e-10; // / 0x100000000 -} -// ---------------------------------------------------------------------------- -vec2 Hammersley(uint i, uint N) -{ - return vec2(float(i)/float(N), RadicalInverse_VdC(i)); -} -// ---------------------------------------------------------------------------- -vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) -{ - float a = roughness*roughness; - - float phi = 2.0 * k_PI * Xi.x; - float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); - float sinTheta = sqrt(1.0 - cosTheta*cosTheta); - - // from spherical coordinates to cartesian coordinates - halfway vector - vec3 H; - H.x = cos(phi) * sinTheta; - H.y = sin(phi) * sinTheta; - H.z = cosTheta; - - // from tangent-space H vector to world-space sample vector - vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - vec3 tangent = normalize(cross(up, N)); - vec3 bitangent = cross(N, tangent); - - vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; - return normalize(sampleVec); -} -// ---------------------------------------------------------------------------- -void main() -{ - vec3 N = normalize(vs_localPos); - - // make the simplyfying assumption that V equals R equals the normal - vec3 R = N; - vec3 V = R; + for(uint i = 0u; i < k_sample_count; ++i) + { + vec3 h, l; + generateImportanceSample(i, k_sample_count, u_roughness, n, v, h, l); - const uint SAMPLE_COUNT = 1024u; - vec3 prefilteredColor = vec3(0.0); - float totalWeight = 0.0; - - for(uint i = 0u; i < SAMPLE_COUNT; ++i) + float n_dot_l = max(dot(n, l), 0.0); + if(n_dot_l > 0.0) { - // generates a sample vector that's biased towards the preferred alignment direction (importance sampling). - vec2 Xi = Hammersley(i, SAMPLE_COUNT); - vec3 H = ImportanceSampleGGX(Xi, N, u_roughness); - vec3 L = normalize(2.0 * dot(V, H) * H - V); + // sample from the environment's mip level based on roughness/pdf + float d = trowbridgeReitzGGX(n, h, u_roughness); + float n_dot_h = max(dot(n, h), 0.0); + float h_dot_v = max(dot(h, v), 0.0); + float pdf = d * n_dot_h / (4.0 * h_dot_v) + 0.0001; - float NdotL = max(dot(N, L), 0.0); - if(NdotL > 0.0) - { - // sample from the environment's mip level based on roughness/pdf - float D = DistributionGGX(N, H, u_roughness); - float NdotH = max(dot(N, H), 0.0); - float HdotV = max(dot(H, V), 0.0); - float pdf = D * NdotH / (4.0 * HdotV) + 0.0001; + float resolution = 512.0; // resolution of source cubemap (per face) + float saTexel = 4.0 * k_PI / (6.0 * resolution * resolution); + float saSample = 1.0 / (float(k_sample_count) * pdf + 0.0001); - float resolution = 512.0; // resolution of source cubemap (per face) - float saTexel = 4.0 * k_PI / (6.0 * resolution * resolution); - float saSample = 1.0 / (float(SAMPLE_COUNT) * pdf + 0.0001); + float mipLevel = u_roughness == 0.0 ? 0.0 : 0.5 * log2(saSample / saTexel); - float mipLevel = u_roughness == 0.0 ? 0.0 : 0.5 * log2(saSample / saTexel); - - prefilteredColor += textureLod(u_envMap, L, mipLevel).rgb * NdotL; - totalWeight += NdotL; - } + prefilteredColor += textureLod(u_envMap, l, mipLevel).rgb * n_dot_l; + totalWeight += n_dot_l; } + } - prefilteredColor = prefilteredColor / totalWeight; + prefilteredColor = prefilteredColor / totalWeight; - FragColor = vec4(prefilteredColor, 1.0); + fragColor = vec4(prefilteredColor, 1.0); } diff --git a/shaders/include/pbr_funcs.h b/shaders/include/pbr_funcs.h new file mode 100644 index 0000000..fd8707d --- /dev/null +++ b/shaders/include/pbr_funcs.h @@ -0,0 +1,102 @@ +#define __PBR__PI__ 3.14159265359 +// ---------------------------------------------------------------------------- +float trowbridgeReitzGGX(vec3 n, vec3 h, float roughness) +{ + float a = roughness*roughness; + float a_sqr = a*a; + float n_dot_h = max(dot(n, h), 0.0); + float n_dot_h_sqr = n_dot_h * n_dot_h; + + float denom = (n_dot_h_sqr * (a_sqr - 1.0) + 1.0); + denom = __PBR__PI__ * denom * denom; + + return a_sqr / denom; +} +// ---------------------------------------------------------------------------- +// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html +// efficient VanDerCorpus calculation. +float radicalInverse_VdC(uint bits) +{ + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return float(bits) * 2.3283064365386963e-10; // / 0x100000000 +} +// ---------------------------------------------------------------------------- +vec2 hammersley(uint i, uint n) +{ + return vec2(float(i)/float(n), radicalInverse_VdC(i)); +} +// ---------------------------------------------------------------------------- +vec3 importanceSampleGGX(vec2 xi, vec3 n, float roughness) +{ + float a = roughness*roughness; + + float phi = 2.0 * __PBR__PI__ * xi.x; + float cosTheta = sqrt((1.0 - xi.y) / (1.0 + (a*a - 1.0) * xi.y)); + float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + + // convert from spherical coordinates to cartesian coordinates + // this gives us our half vector + vec3 h = vec3( + cos(phi) * sinTheta, + sin(phi) * sinTheta, + cosTheta + ); + + // convert the half vector from tangent to world space + // this gives us our sample vector + vec3 up = abs(n.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, n)); + vec3 bitangent = cross(n, tangent); + + return normalize(tangent * h.x + bitangent * h.y + n * h.z); +} +// ---------------------------------------------------------------------------- +void generateImportanceSample(uint i, uint sample_count, float roughness, vec3 n, vec3 v, out vec3 h, out vec3 l) +{ + // generates a sample vector that's biased towards the preferred alignment direction (importance sampling). + vec2 xi = hammersley(i, sample_count); + h = importanceSampleGGX(xi, n, roughness); + l = normalize(2.0 * dot(v, h) * h - v); +} +// ---------------------------------------------------------------------------- +float schlickGGX(float n_dot_v, float roughness) +{ + float r = (roughness + 1.0); + // Divide by 8, 1/8 = 0.125 + float k = (r*r) * 0.125; + return n_dot_v / (n_dot_v * (1.0 - k) + k); +} +// ---------------------------------------------------------------------------- +float geometrySmith(vec3 n, vec3 v, vec3 l, float roughness) +{ + float n_dot_v = max(dot(n, v), 0.0); + float n_dot_l = max(dot(n, l), 0.0); + float ggx2 = schlickGGX(n_dot_v, roughness); + float ggx1 = schlickGGX(n_dot_l, roughness); + + return ggx1 * ggx2; +} +// ---------------------------------------------------------------------------- +vec3 fresnelSchlickCommon(float cosTheta, vec3 f0, vec3 base) +{ + return f0 + (base - f0) * pow(1.0 - cosTheta, 5.0); +} +// ---------------------------------------------------------------------------- +vec3 fresnelSchlick(float cosTheta, vec3 f0) +{ + return fresnelSchlickCommon(cosTheta, f0, vec3(1.0)); +} +// ---------------------------------------------------------------------------- +vec3 fresnelSchlickRoughness(float cosTheta, vec3 f0, float roughness) +{ + return fresnelSchlickCommon(cosTheta, f0, max(vec3(1.0 - roughness), f0)); +} +// ---------------------------------------------------------------------------- +vec3 diffuseTerm(vec3 f, float metallic) +{ + return (1.0 - f) * (1.0 - metallic); +} diff --git a/shaders/owl_pbr_frag.glsl b/shaders/owl_pbr_frag.glsl index 174d005..385c09b 100644 --- a/shaders/owl_pbr_frag.glsl +++ b/shaders/owl_pbr_frag.glsl @@ -41,7 +41,7 @@ const vec3 k_lightPositions[4] = vec3[4]( vec3( k_scale, k_lightHeight, k_scale) ); -const float k_lightIntensity = 100.f; +const float k_lightIntensity = 100; const vec3 k_lightColors[4] = vec3[4]( vec3(k_lightIntensity, k_lightIntensity, k_lightIntensity), vec3(k_lightIntensity, k_lightIntensity, k_lightIntensity), @@ -56,146 +56,90 @@ const float k_PI = 3.14159265359; #include "shaders/include/owl_noise_funcs.h" #include "shaders/include/owl_eye_funcs.h" #include "shaders/include/owl_bump_funcs.h" +#include "shaders/include/pbr_funcs.h" -// ---------------------------------------------------------------------------- -float DistributionGGX(vec3 N, vec3 H, float roughness) -{ - float a = roughness*u_roughness; - float a2 = a*a; - float NdotH = max(dot(N, H), 0.0); - float NdotH2 = NdotH*NdotH; - - float nom = a2; - float denom = (NdotH2 * (a2 - 1.0) + 1.0); - denom = k_PI * denom * denom; - - return nom / denom; -} -// ---------------------------------------------------------------------------- -float GeometrySchlickGGX(float NdotV, float roughness) -{ - float r = (roughness + 1.0); - float k = (r*r) / 8.0; - - float nom = NdotV; - float denom = NdotV * (1.0 - k) + k; - - return nom / denom; -} -// ---------------------------------------------------------------------------- -float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) -{ - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx2 = GeometrySchlickGGX(NdotV, roughness); - float ggx1 = GeometrySchlickGGX(NdotL, roughness); - - return ggx1 * ggx2; -} -// ---------------------------------------------------------------------------- -vec3 fresnelSchlick(float cosTheta, vec3 F0) -{ - return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); -} -// ---------------------------------------------------------------------------- -vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) -{ - return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0); -} void main() { + // We use the base position to look-up our textures so that animation doesn't slide through vec3 coord = go_out.base_position * 0.2 + vec3(0.5, 0.55, 0.5); - vec4 normalTgt = texture(u_normalMap, coord); -// int normalResidency = sparseTextureEXT(u_normalMap, coord, normalTgt); - vec4 albedoDisp = texture(u_albedoMap, coord); -// int albedoResidency = sparseTextureEXT(u_albedoMap, coord, albedoDisp); -// if (!sparseTexelsResidentEXT(albedoResidency) || !sparseTexelsResidentEXT(normalResidency)) -// discard; + // Retrieve our normal map value + vec4 normalAdjust = texture(u_normalMap, coord); // Extract the normal from the normal map (rescale to [-1,1] - vec3 tgt = normalTgt.rgb * 2.0 - 1.0; + vec3 tgt = normalAdjust.rgb * 2.0 - 1.0; // The source is just up in the Z-direction vec3 src = vec3(0.0, 0.0, 1.0); // Perturb the normal according to the target - vec3 np = normalize(mix(go_out.normal, rotateVector(src, tgt, go_out.normal), u_normalStrength)); + vec3 perturbedNormal = normalize(mix(go_out.normal, rotateVector(src, tgt, go_out.normal), u_normalStrength)); + // Get the albedo map val + vec4 albedoDisp = texture(u_albedoMap, coord); + // Apply new albedo for the eyes vec3 eyeAlbedo = mix(albedoDisp.xyz, vec3(0.4, 0.34, 0.38) * turb(u_offsetPos, 10), go_out.eyeVal); - vec3 N = np; - vec3 V = normalize(u_camPos - go_out.world_position); - vec3 R = reflect(-V, N); + // Final inputs to the reflectance equation + vec3 n = perturbedNormal; + vec3 v = normalize(u_camPos - go_out.world_position); - // calculate reflectance at normal incidence; if dia-electric (like plastic) use F0 - // of 0.04 and if it's a metal, use their albedo color as F0 (metallic workflow) - vec3 F0 = vec3(u_baseSpec); - F0 = mix(F0, eyeAlbedo, u_metallic); + // use albedo as f0 when metallic, otherwise use the set uniform + vec3 f0 = mix(vec3(u_baseSpec), eyeAlbedo, u_metallic); // reflectance equation - vec3 Lo = vec3(0.0); + vec3 light_out = vec3(0.0); for(int i = 0; i < 4; ++i) { vec3 trans = vec3(0.0, 0.0, -2.0); vec3 ray = k_lightPositions[i] - go_out.world_position + trans; // calculate per-light radiance - vec3 L = normalize(ray); - vec3 H = normalize(V + L); + vec3 l = normalize(ray); + vec3 h = normalize(v + l); float dist = length(ray); float attenuation = 1.0 / (dist * dist); vec3 radiance = k_lightColors[i] * attenuation; // Cook-Torrance BRDF - float NDF = DistributionGGX(N, H, u_roughness); - float G = GeometrySmith(N, V, L, u_roughness); - vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + float ndf = trowbridgeReitzGGX(n, h, u_roughness); + float g = geometrySmith(n, v, l, u_roughness); + vec3 f = fresnelSchlick(max(dot(h, v), 0.0), f0); - vec3 nominator = NDF * G * F; - float denominator = 4 * max(dot(V, N), 0.0) * max(dot(L, N), 0.0) + 0.001; // 0.001 to prevent divide by zero. + vec3 nominator = ndf * g * f; + float denominator = 4 * max(dot(v, n), 0.0) * max(dot(l, n), 0.0) + 0.0001; // 0.0001 to prevent divide by zero. vec3 brdf = nominator / denominator; - // kS is equal to Fresnel - vec3 kS = F; - // for energy conservation, the diffuse and specular light can't - // be above 1.0 (unless the surface emits light); to preserve this - // relationship the diffuse component (kD) should equal 1.0 - kS. - vec3 kD = vec3(1.0) - kS; - // multiply kD by the inverse metalness such that only non-metals - // have diffuse lighting, or a linear blend if partly metal (pure metals - // have no diffuse light). - kD *= 1.0 - u_metallic; + // diffuse = 1 - spec, where spec = fresnel, this means that energy is conserved. + // pure metals have no diffuse lighting so we must remove it here + vec3 diffuse = diffuseTerm(f, u_metallic); // scale light by NdotL - float NdotL = max(dot(N, L), 0.0); + float n_dot_l = max(dot(n, l), 0.0); - // add to outgoing radiance Lo - Lo += (kD * eyeAlbedo / k_PI + brdf) * radiance * NdotL; + // add to outgoing radiance light_out + light_out += (diffuse * eyeAlbedo / k_PI + brdf) * radiance * n_dot_l; } - vec3 F = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, u_roughness); - - vec3 kS = F; - vec3 kD = 1.0 - kS; - kD *= (1.0 - u_metallic); + vec3 f = fresnelSchlickRoughness(max(dot(n, v), 0.0), f0, u_roughness); - vec3 irradiance = texture(u_irradianceMap, N).rgb; - vec3 diffuse = irradiance * eyeAlbedo; + vec3 irradiance = texture(u_irradianceMap, n).rgb; + vec3 diffuse = diffuseTerm(f, u_metallic) * (irradiance * eyeAlbedo); const float MAX_REFLECTION_LOD = 4.0; - vec3 prefilteredColor = textureLod(u_prefilterMap, R, u_roughness * MAX_REFLECTION_LOD).rgb; - vec2 envBRDF = texture(u_brdfMap, vec2(max(dot(N, V), 0.0), u_roughness)).rg; - vec3 specular = prefilteredColor * (F * envBRDF.x + envBRDF.y); + vec3 r = reflect(-v, n); + vec3 prefilteredColor = textureLod(u_prefilterMap, r, u_roughness * MAX_REFLECTION_LOD).rgb; + vec2 envBRDF = texture(u_brdfMap, vec2(max(dot(n, v), 0.0), u_roughness)).rg; + vec3 specular = prefilteredColor * (f * envBRDF.x + envBRDF.y); float ao = clamp(albedoDisp.w, 0.0, 1.0); - vec3 ambient = (kD * diffuse + specular); + vec3 ambient = (diffuse + specular); ambient = mix(ambient, ambient * ao, u_ao); - vec3 color = ambient + Lo; + vec3 color = ambient + light_out; // HDR tonemaping color = color / (color + vec3(1.0)); diff --git a/src/DemoScene.cpp b/src/DemoScene.cpp index 0d6d2c1..b2dbc52 100644 --- a/src/DemoScene.cpp +++ b/src/DemoScene.cpp @@ -1,10 +1,5 @@ #include "DemoScene.h" -#include "MaterialWireframe.h" #include "MaterialPBR.h" -#include "MaterialPhong.h" -#include "MaterialFractal.h" -#include "MaterialEnvMap.h" -#include "MaterialBump.h" #include #include #include diff --git a/src/MaterialBump.cpp b/src/MaterialBump.cpp deleted file mode 100644 index ccdcdbb..0000000 --- a/src/MaterialBump.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "MaterialBump.h" -#include "Scene.h" -#include "ShaderLib.h" - -void MaterialBump::init() -{ - - auto shaderPtr = m_shaderLib->getShader(m_shaderName); - initColor(); - shaderPtr->setUniformValue("ColourTexture", 0); - - initNormal(); - shaderPtr->setUniformValue("NormalTexture", 1); - - update(); -} - -void MaterialBump::initColor() -{ - using tex = QOpenGLTexture; - m_colorMap.reset(new QOpenGLTexture(QOpenGLTexture::Target2D)); - auto map = QImage("images/bricktexture.jpg").mirrored().convertToFormat(QImage::Format_RGB888); - - m_colorMap->create(); - m_colorMap->bind(0); - m_colorMap->setSize(map.width(), map.height(), map.depth()); - m_colorMap->setFormat(tex::RGBFormat); - m_colorMap->allocateStorage(); - m_colorMap->setData(tex::RGB, tex::UInt8, map.constBits()); - - m_colorMap->setWrapMode(tex::Repeat); - m_colorMap->setMinMagFilters(tex::Linear, tex::Linear); - -} - -void MaterialBump::initNormal() -{ - using tex = QOpenGLTexture; - m_normalMap.reset(new QOpenGLTexture(QOpenGLTexture::Target2D)); - auto map = QImage("images/bricknormals.jpg").mirrored().convertToFormat(QImage::Format_RGB888); - - m_normalMap->create(); - m_normalMap->bind(1); - m_normalMap->setSize(map.width(), map.height(), map.depth()); - m_normalMap->setFormat(tex::RGBFormat); - m_normalMap->allocateStorage(); - m_normalMap->setData(tex::RGB, tex::UInt8, map.constBits()); - - m_normalMap->setWrapMode(tex::Repeat); - m_normalMap->setMinMagFilters(tex::Linear, tex::Linear); - -} - -void MaterialBump::update() -{ - - m_colorMap->bind(0); - m_normalMap->bind(1); - - auto shaderPtr = m_shaderLib->getShader(m_shaderName); - auto eye = m_cam->getCameraEye(); - shaderPtr->setUniformValue("camPos", QVector3D{eye.x, eye.y, eye.z}); - - // Scope the using declaration - { - using namespace SceneMatrices; - static constexpr std::array shaderUniforms = {{"M", "MVP", "N"}}; - // Send all our matrices to the GPU - for (const auto matrixId : {MODEL_VIEW, PROJECTION, NORMAL}) - { - // Convert from glm to Qt - QMatrix4x4 qmat(glm::value_ptr((*m_matrices)[matrixId])); - // Need to transpose the matrix as they both use different majors - shaderPtr->setUniformValue(shaderUniforms[matrixId], qmat.transposed()); - } - } -} - -const char* MaterialBump::shaderFileName() const -{ - return "shaderPrograms/bump.json"; -} diff --git a/src/MaterialEnvMap.cpp b/src/MaterialEnvMap.cpp deleted file mode 100644 index 65a90fc..0000000 --- a/src/MaterialEnvMap.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "MaterialEnvMap.h" -#include "Scene.h" -#include "ShaderLib.h" - -void MaterialEnvMap::init() -{ - auto shaderPtr = m_shaderLib->getShader(m_shaderName); - - initEnvMap(); - shaderPtr->setUniformValue("envMap", 0); - - initGlossMap(); - shaderPtr->setUniformValue("glossMap", 1); - - update(); -} - -void MaterialEnvMap::initGlossMap() -{ - using tex = QOpenGLTexture; - m_glossMap.reset(new QOpenGLTexture(QOpenGLTexture::Target2D)); - auto map = QImage("images/gloss.png").mirrored().convertToFormat(QImage::Format_RGBA8888); - m_glossMap->setSize(map.width(), map.height(), map.depth()); - m_glossMap->setFormat(tex::RGBAFormat); - m_glossMap->allocateStorage(); - m_glossMap->setData(tex::RGBA, tex::UInt8, map.constBits()); - m_glossMap->create(); - m_glossMap->bind(1); - m_glossMap->setWrapMode(tex::Repeat); - m_glossMap->setMinMagFilters(tex::Linear, tex::Linear); -} - -void MaterialEnvMap::initEnvMap() -{ - m_envMap.reset(new QOpenGLTexture(QOpenGLTexture::TargetCubeMap)); - static constexpr std::array paths = {{ - "images/sky_xpos.png", - "images/sky_ypos.png", - "images/sky_zpos.png", - "images/sky_xneg.png", - "images/sky_yneg.png", - "images/sky_zneg.png" - }}; - - using tex = QOpenGLTexture; - static constexpr std::array dataTypes = {{ - tex::CubeMapPositiveX, - tex::CubeMapPositiveY, - tex::CubeMapPositiveZ, - tex::CubeMapNegativeX, - tex::CubeMapNegativeY, - tex::CubeMapNegativeZ - }}; - std::array maps; - for (size_t i = 0; i < maps.size(); ++i) - maps[i] = QImage(paths[i]).mirrored().convertToFormat(QImage::Format_RGBA8888); - - m_envMap->create(); - m_envMap->bind(0); - m_envMap->setSize(maps[0].width(), maps[0].height(), maps[0].depth()); - m_envMap->setFormat(tex::RGBAFormat); - m_envMap->allocateStorage(); - - for (size_t i = 0; i < maps.size(); ++i) - m_envMap->setData(0, 0, dataTypes[i], tex::RGBA, tex::UInt8, maps[i].constBits()); - - m_envMap->setMinMagFilters(tex::LinearMipMapLinear, tex::Linear); - - m_envMap->setWrapMode(tex::ClampToEdge); - m_envMap->generateMipMaps(); - m_envMap->setAutoMipMapGenerationEnabled(true); -} - -void MaterialEnvMap::update() -{ - auto shaderPtr = m_shaderLib->getShader(m_shaderName); - auto eye = m_cam->getCameraEye(); - shaderPtr->setUniformValue("camPos", QVector3D{eye.x, eye.y, eye.z}); - - // Scope the using declaration - { - using namespace SceneMatrices; - static constexpr std::array shaderUniforms = {{"M", "MVP", "N"}}; - // Send all our matrices to the GPU - for (const auto matrixId : {MODEL_VIEW, PROJECTION, NORMAL}) - { - // Convert from glm to Qt - QMatrix4x4 qmat(glm::value_ptr((*m_matrices)[matrixId])); - // Need to transpose the matrix as they both use different majors - shaderPtr->setUniformValue(shaderUniforms[matrixId], qmat.transposed()); - } - } -} - -const char* MaterialEnvMap::shaderFileName() const -{ - return "shaderPrograms/envMap.json"; -} diff --git a/src/MaterialFractal.cpp b/src/MaterialFractal.cpp deleted file mode 100644 index ac537a1..0000000 --- a/src/MaterialFractal.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "MaterialFractal.h" -#include "Scene.h" -#include "ShaderLib.h" -#include -#include -#include - -void MaterialFractal::init() -{ - m_last = std::chrono::high_resolution_clock::now(); - update(); -} - -void MaterialFractal::update() -{ - auto shaderPtr = m_shaderLib->getShader(m_shaderName); - - using namespace std::chrono; - auto now = high_resolution_clock::now(); - m_time += (duration_cast(now - m_last).count() * m_updateTime); - m_last = now; - shaderPtr->setUniformValue("t", m_time / 1000.0f); - // Scope the using declaration - { - using namespace SceneMatrices; - static constexpr std::array shaderUniforms = {{"M", "MVP", "N"}}; - // Send all our matrices to the GPU - for (const auto matrixId : {MODEL_VIEW, PROJECTION, NORMAL}) - { - // Convert from glm to Qt - QMatrix4x4 qmat(glm::value_ptr((*m_matrices)[matrixId])); - // Need to transpose the matrix as they both use different majors - shaderPtr->setUniformValue(shaderUniforms[matrixId], qmat.transposed()); - } - } -} - -const char* MaterialFractal::shaderFileName() const -{ - return "shaderPrograms/fractal.json"; -} - -void MaterialFractal::handleKey(QKeyEvent* io_event, QOpenGLContext* io_context) -{ - switch(io_event->key()) - { - case Qt::Key_E : - { - static constexpr GLuint id = 1; - io_context->versionFunctions()->glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &id); - break; - } - case Qt::Key_R : - { - static constexpr GLuint id = 0; - io_context->versionFunctions()->glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &id); - break; - } - case Qt::Key_T : - { - m_updateTime = !m_updateTime; - break; - } - default: break; - } - -} diff --git a/src/MaterialPhong.cpp b/src/MaterialPhong.cpp deleted file mode 100644 index 8eb82ee..0000000 --- a/src/MaterialPhong.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "MaterialPhong.h" -#include "Scene.h" -#include "ShaderLib.h" - -void MaterialPhong::init() -{ - update(); -} - -void MaterialPhong::update() -{ - auto shaderPtr = m_shaderLib->getShader(m_shaderName); - auto eye = m_cam->getCameraEye(); - shaderPtr->setUniformValue("camPos", QVector3D{eye.x, eye.y, eye.z}); - - // Scope the using declaration - { - using namespace SceneMatrices; - static constexpr std::array shaderUniforms = {{"M", "MVP", "N"}}; - // Send all our matrices to the GPU - for (const auto matrixId : {MODEL_VIEW, PROJECTION, NORMAL}) - { - // Convert from glm to Qt - QMatrix4x4 qmat(glm::value_ptr((*m_matrices)[matrixId])); - // Need to transpose the matrix as they both use different majors - shaderPtr->setUniformValue(shaderUniforms[matrixId], qmat.transposed()); - } - } -} - -const char* MaterialPhong::shaderFileName() const -{ - return "shaderPrograms/phong.json"; -} diff --git a/src/MaterialWireframe.cpp b/src/MaterialWireframe.cpp deleted file mode 100644 index 577edfe..0000000 --- a/src/MaterialWireframe.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "MaterialWireframe.h" -#include "Scene.h" -#include "ShaderLib.h" - -void MaterialWireframe::init() -{ - update(); -} - -void MaterialWireframe::update() -{ - auto shaderPtr = m_shaderLib->getShader(m_shaderName); - // Scope the using declaration - { - using namespace SceneMatrices; - static constexpr std::array shaderUniforms = {{"M", "MVP", "N"}}; - // Send all our matrices to the GPU - for (const auto matrixId : {MODEL_VIEW, PROJECTION, NORMAL}) - { - // Convert from glm to Qt - QMatrix4x4 qmat(glm::value_ptr((*m_matrices)[matrixId])); - // Need to transpose the matrix as they both use different majors - shaderPtr->setUniformValue(shaderUniforms[matrixId], qmat.transposed()); - } - } -} - -const char* MaterialWireframe::shaderFileName() const -{ - return "shaderPrograms/wireframe.json"; -}