diff --git a/data/shaders/IBL.frag b/data/shaders/IBL.frag index 2bee625db..a638c3629 100644 --- a/data/shaders/IBL.frag +++ b/data/shaders/IBL.frag @@ -50,9 +50,8 @@ void main(void) sampleDirection = (InverseViewMatrix * vec4(sampleDirection, 0.)).xyz; float specval = texture(ntex, uv).z; - // From http://graphics.cs.williams.edu/papers/EnvMipReport2013/ - int texSize = textureSize(tex, 0).x; - float lodval = clamp(log2(texSize * sqrt(3.)) - (5. * specval + 1.), 0., 10.); + // Assume 8 level of lod (ie 256x256 texture) + float lodval = 8. * (1. - specval); vec4 specular = textureLod(tex, sampleDirection, lodval); Spec = max(specular, vec4(0.)); } diff --git a/data/shaders/importance_sampling_specular.frag b/data/shaders/importance_sampling_specular.frag new file mode 100644 index 000000000..51549aa6f --- /dev/null +++ b/data/shaders/importance_sampling_specular.frag @@ -0,0 +1,30 @@ +uniform samplerCube tex; +uniform float samples[2048]; +uniform float ViewportSize; + +uniform mat4 PermutationMatrix; + +out vec4 FragColor; + +void main(void) +{ + vec2 uv = gl_FragCoord.xy / ViewportSize; + vec3 RayDir = 2. * vec3(uv, 1.) - 1.; + RayDir = normalize((PermutationMatrix * vec4(RayDir, 0.)).xyz); + + vec4 FinalColor = vec4(0.); + vec3 up = (RayDir.y < .99) ? vec3(0., 1., 0.) : vec3(0., 0., 1.); + vec3 Tangent = normalize(cross(up, RayDir)); + vec3 Bitangent = cross(RayDir, Tangent); + + for (int i = 0; i < 1024; i++) + { + float Theta = samples[2 * i]; + float Phi = samples[2 * i + 1]; + + vec3 sampleDir = cos(Theta) * RayDir + sin(Theta) * cos(Phi) * Tangent + sin(Theta) * sin(Phi) * Bitangent; + FinalColor += textureLod(tex, sampleDir, 0.); + } + + FragColor = FinalColor / 1024.; +} diff --git a/src/graphics/IBL.cpp b/src/graphics/IBL.cpp index ac354d11b..71e7b854e 100644 --- a/src/graphics/IBL.cpp +++ b/src/graphics/IBL.cpp @@ -1,6 +1,8 @@ #include "IBL.hpp" #include "gl_headers.hpp" +#include "shaders.hpp" #include +#include static void getXYZ(GLenum face, float i, float j, float &x, float &y, float &z) { @@ -203,4 +205,108 @@ void SphericalHarmonics(Color *CubemapFace[6], size_t edge_size, float *blueSHCo delete[] Y21[face]; delete[] Y22[face]; } +} + +// From http://http.developer.nvidia.com/GPUGems3/gpugems3_ch20.html +/** Returns the index-th pair from Hammersley set of pseudo random set. + Hammersley set is a uniform distribution between 0 and 1 for 2 components. + We use the natural indexation on the set to avoid storing the whole set. + \param index of the pair + \param size of the set. */ +std::pair HammersleySequence(int index, int samples) +{ + float InvertedBinaryRepresentation = 0.; + for (size_t i = 0; i < 32; i++) + { + InvertedBinaryRepresentation += ((index >> i) & 0x1) * powf(.5, (float) (i + 1.)); + } + return std::make_pair(float(index) / float(samples), InvertedBinaryRepresentation); +} + + +/** Returns a pseudo random (theta, phi) generated from a probability density function modeled after Phong function. + \param a pseudo random float pair from a uniform density function between 0 and 1. + \param exponent from the Phong formula. */ +std::pair ImportanceSamplingPhong(std::pair Seeds, float exponent) +{ + return std::make_pair(acosf(powf(Seeds.first, 1.f / (exponent + 1.f))), 2.f * 3.14f * Seeds.second); +} + +static +core::matrix4 getPermutationMatrix(size_t indexX, float valX, size_t indexY, float valY, size_t indexZ, float valZ) +{ + core::matrix4 resultMat; + float *M = resultMat.pointer(); + memset(M, 0, 16 * sizeof(float)); + assert(indexX < 4); + assert(indexY < 4); + assert(indexZ < 4); + M[indexX] = valX; + M[4 + indexY] = valY; + M[8 + indexZ] = valZ; + return resultMat; +} + +GLuint generateSpecularCubemap(GLuint probe) +{ + GLuint cubemap_texture; + + glGenTextures(1, &cubemap_texture); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap_texture); + size_t cubemap_size = 256; + for (int i = 0; i < 6; i++) + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA16F, cubemap_size, cubemap_size, 0, GL_BGRA, GL_FLOAT, 0); + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + + GLuint fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glViewport(0, 0, cubemap_size, cubemap_size); + GLenum bufs[] = { GL_COLOR_ATTACHMENT0 }; + glDrawBuffers(1, bufs); + glUseProgram(UtilShader::SpecularIBLGenerator::getInstance()->Program); + glBindVertexArray(SharedObject::FullScreenQuadVAO); + + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + + core::matrix4 M[6] = { + getPermutationMatrix(2, -1., 1, -1., 0, 1.), + getPermutationMatrix(2, 1., 1, -1., 0, -1.), + getPermutationMatrix(0, 1., 2, 1., 1, 1.), + getPermutationMatrix(0, 1., 2, -1., 1, -1.), + getPermutationMatrix(0, 1., 1, -1., 2, 1.), + getPermutationMatrix(0, -1., 1, -1., 2, -1.), + }; + + for (unsigned level = 0; level < 8; level++) + { + // Blinn Phong can be approximated by Phong with 4x the specular coefficient + // See http://seblagarde.wordpress.com/2012/03/29/relationship-between-phong-and-blinn-lighting-model/ + float roughness = (8 - level) * 4 * pow(2., 10.) / 8.; + float viewportSize = 1 << (8 - level); + + std::vector Samples; + for (unsigned i = 0; i < 1024; i++) + { + std::pair sample = ImportanceSamplingPhong(HammersleySequence(i, 1024), roughness); + Samples.push_back(sample.first); + Samples.push_back(sample.second); + } + + for (unsigned face = 0; face < 6; face++) + { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, cubemap_texture, level); + GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + assert(status == GL_FRAMEBUFFER_COMPLETE); + + UtilShader::SpecularIBLGenerator::getInstance()->SetTextureUnits(probe); + UtilShader::SpecularIBLGenerator::getInstance()->setUniforms(M[face], Samples, viewportSize); + glDrawArrays(GL_TRIANGLES, 0, 3); + } + } + + glDeleteFramebuffers(1, &fbo); + return cubemap_texture; } \ No newline at end of file diff --git a/src/graphics/IBL.hpp b/src/graphics/IBL.hpp index 09ddd7d35..24cdac36e 100644 --- a/src/graphics/IBL.hpp +++ b/src/graphics/IBL.hpp @@ -1,6 +1,8 @@ #ifndef IBL_HPP #define IBL_HPP +#include "gl_headers.hpp" + struct Color { float Red; @@ -14,4 +16,6 @@ using the cubemap provided by CubemapFace. * \param row/columns count of textures. */ void SphericalHarmonics(Color *CubemapFace[6], size_t edge_size, float *blueSHCoeff, float *greenSHCoeff, float *redSHCoeff); + +GLuint generateSpecularCubemap(GLuint probe); #endif \ No newline at end of file diff --git a/src/graphics/irr_driver.cpp b/src/graphics/irr_driver.cpp index df502a7e3..35b8163ae 100644 --- a/src/graphics/irr_driver.cpp +++ b/src/graphics/irr_driver.cpp @@ -1365,6 +1365,7 @@ scene::ISceneNode *IrrDriver::addSkyBox(const std::vector &tex SkyboxTextures = texture; SphericalHarmonicsTextures = sphericalHarmonics; SkyboxCubeMap = 0; + SkyboxSpecularProbe = 0; m_SH_dirty = true; return m_scene_manager->addSkyBoxSceneNode(texture[0], texture[1], texture[2], texture[3], @@ -1377,8 +1378,12 @@ void IrrDriver::suppressSkyBox() SphericalHarmonicsTextures.clear(); m_SH_dirty = true; if ((SkyboxCubeMap) && (!ProfileWorld::isNoGraphics())) + { glDeleteTextures(1, &SkyboxCubeMap); + glDeleteTextures(1, &SkyboxSpecularProbe); + } SkyboxCubeMap = 0; + SkyboxSpecularProbe = 0; } // ---------------------------------------------------------------------------- diff --git a/src/graphics/irr_driver.hpp b/src/graphics/irr_driver.hpp index 843b43cd0..60a8b2154 100644 --- a/src/graphics/irr_driver.hpp +++ b/src/graphics/irr_driver.hpp @@ -254,6 +254,7 @@ private: public: GLuint SkyboxCubeMap; + GLuint SkyboxSpecularProbe; /** A simple class to store video resolutions. */ class VideoMode { diff --git a/src/graphics/render_lighting.cpp b/src/graphics/render_lighting.cpp index 1262f0881..ca5e45061 100644 --- a/src/graphics/render_lighting.cpp +++ b/src/graphics/render_lighting.cpp @@ -155,7 +155,7 @@ void IrrDriver::renderLights(unsigned pointlightcount, bool hasShadow) { ScopedGPUTimer timer(irr_driver->getGPUTimer(Q_ENVMAP)); - m_post_processing->renderEnvMap(blueSHCoeff, greenSHCoeff, redSHCoeff, SkyboxCubeMap); + m_post_processing->renderEnvMap(blueSHCoeff, greenSHCoeff, redSHCoeff, SkyboxSpecularProbe); } // Render sunlight if and only if track supports shadow diff --git a/src/graphics/render_skybox.cpp b/src/graphics/render_skybox.cpp index 2a9fc4412..c408636d5 100644 --- a/src/graphics/render_skybox.cpp +++ b/src/graphics/render_skybox.cpp @@ -280,6 +280,7 @@ void IrrDriver::generateSkyboxCubemap() assert(SkyboxTextures.size() == 6); SkyboxCubeMap = generateCubeMapFromTextures(SkyboxTextures); + SkyboxSpecularProbe = generateSpecularCubemap(SkyboxCubeMap); } void IrrDriver::generateDiffuseCoefficients() @@ -322,8 +323,8 @@ void IrrDriver::generateDiffuseCoefficients() } else { - int sh_w = 16; - int sh_h = 16; + sh_w = 16; + sh_h = 16; video::SColor ambient = m_scene_manager->getAmbientLight().toSColor(); diff --git a/src/graphics/shaders.cpp b/src/graphics/shaders.cpp index 3706f140e..1b67a5546 100644 --- a/src/graphics/shaders.cpp +++ b/src/graphics/shaders.cpp @@ -889,6 +889,18 @@ unsigned getGLSLVersion() return irr_driver->getGLSLVersion(); } +namespace UtilShader +{ + SpecularIBLGenerator::SpecularIBLGenerator() + { + Program = LoadProgram(OBJECT, + GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(), + GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/importance_sampling_specular.frag").c_str()); + AssignUniforms("PermutationMatrix", "samples[0]", "ViewportSize"); + AssignSamplerNames(Program, 0, "tex"); + } +} + namespace MeshShader { // Solid Normal and depth pass shaders diff --git a/src/graphics/shaders.hpp b/src/graphics/shaders.hpp index 2251cbb65..97ba49c41 100644 --- a/src/graphics/shaders.hpp +++ b/src/graphics/shaders.hpp @@ -48,6 +48,12 @@ public: static void init(); static void setUniforms(const irr::video::SColor &); }; + +class SpecularIBLGenerator : public ShaderHelperSingleton, float >, public TextureRead +{ +public: + SpecularIBLGenerator(); +}; }