Implement specular IBL properly

This commit is contained in:
Vincent Lejeune 2014-12-02 17:38:21 +01:00
parent c2d7356d05
commit 8f3b8cf448
10 changed files with 170 additions and 6 deletions

View File

@ -50,9 +50,8 @@ void main(void)
sampleDirection = (InverseViewMatrix * vec4(sampleDirection, 0.)).xyz; sampleDirection = (InverseViewMatrix * vec4(sampleDirection, 0.)).xyz;
float specval = texture(ntex, uv).z; float specval = texture(ntex, uv).z;
// From http://graphics.cs.williams.edu/papers/EnvMipReport2013/ // Assume 8 level of lod (ie 256x256 texture)
int texSize = textureSize(tex, 0).x; float lodval = 8. * (1. - specval);
float lodval = clamp(log2(texSize * sqrt(3.)) - (5. * specval + 1.), 0., 10.);
vec4 specular = textureLod(tex, sampleDirection, lodval); vec4 specular = textureLod(tex, sampleDirection, lodval);
Spec = max(specular, vec4(0.)); Spec = max(specular, vec4(0.));
} }

View File

@ -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.;
}

View File

@ -1,6 +1,8 @@
#include "IBL.hpp" #include "IBL.hpp"
#include "gl_headers.hpp" #include "gl_headers.hpp"
#include "shaders.hpp"
#include <cmath> #include <cmath>
#include <set>
static void getXYZ(GLenum face, float i, float j, float &x, float &y, float &z) 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[] Y21[face];
delete[] Y22[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<float, float> 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<float, float> ImportanceSamplingPhong(std::pair<float, float> 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<float> Samples;
for (unsigned i = 0; i < 1024; i++)
{
std::pair<float, float> 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;
} }

View File

@ -1,6 +1,8 @@
#ifndef IBL_HPP #ifndef IBL_HPP
#define IBL_HPP #define IBL_HPP
#include "gl_headers.hpp"
struct Color struct Color
{ {
float Red; float Red;
@ -14,4 +16,6 @@ using the cubemap provided by CubemapFace.
* \param row/columns count of textures. * \param row/columns count of textures.
*/ */
void SphericalHarmonics(Color *CubemapFace[6], size_t edge_size, float *blueSHCoeff, float *greenSHCoeff, float *redSHCoeff); void SphericalHarmonics(Color *CubemapFace[6], size_t edge_size, float *blueSHCoeff, float *greenSHCoeff, float *redSHCoeff);
GLuint generateSpecularCubemap(GLuint probe);
#endif #endif

View File

@ -1365,6 +1365,7 @@ scene::ISceneNode *IrrDriver::addSkyBox(const std::vector<video::ITexture*> &tex
SkyboxTextures = texture; SkyboxTextures = texture;
SphericalHarmonicsTextures = sphericalHarmonics; SphericalHarmonicsTextures = sphericalHarmonics;
SkyboxCubeMap = 0; SkyboxCubeMap = 0;
SkyboxSpecularProbe = 0;
m_SH_dirty = true; m_SH_dirty = true;
return m_scene_manager->addSkyBoxSceneNode(texture[0], texture[1], return m_scene_manager->addSkyBoxSceneNode(texture[0], texture[1],
texture[2], texture[3], texture[2], texture[3],
@ -1377,8 +1378,12 @@ void IrrDriver::suppressSkyBox()
SphericalHarmonicsTextures.clear(); SphericalHarmonicsTextures.clear();
m_SH_dirty = true; m_SH_dirty = true;
if ((SkyboxCubeMap) && (!ProfileWorld::isNoGraphics())) if ((SkyboxCubeMap) && (!ProfileWorld::isNoGraphics()))
{
glDeleteTextures(1, &SkyboxCubeMap); glDeleteTextures(1, &SkyboxCubeMap);
glDeleteTextures(1, &SkyboxSpecularProbe);
}
SkyboxCubeMap = 0; SkyboxCubeMap = 0;
SkyboxSpecularProbe = 0;
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -254,6 +254,7 @@ private:
public: public:
GLuint SkyboxCubeMap; GLuint SkyboxCubeMap;
GLuint SkyboxSpecularProbe;
/** A simple class to store video resolutions. */ /** A simple class to store video resolutions. */
class VideoMode class VideoMode
{ {

View File

@ -155,7 +155,7 @@ void IrrDriver::renderLights(unsigned pointlightcount, bool hasShadow)
{ {
ScopedGPUTimer timer(irr_driver->getGPUTimer(Q_ENVMAP)); 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 // Render sunlight if and only if track supports shadow

View File

@ -280,6 +280,7 @@ void IrrDriver::generateSkyboxCubemap()
assert(SkyboxTextures.size() == 6); assert(SkyboxTextures.size() == 6);
SkyboxCubeMap = generateCubeMapFromTextures(SkyboxTextures); SkyboxCubeMap = generateCubeMapFromTextures(SkyboxTextures);
SkyboxSpecularProbe = generateSpecularCubemap(SkyboxCubeMap);
} }
void IrrDriver::generateDiffuseCoefficients() void IrrDriver::generateDiffuseCoefficients()
@ -322,8 +323,8 @@ void IrrDriver::generateDiffuseCoefficients()
} }
else else
{ {
int sh_w = 16; sh_w = 16;
int sh_h = 16; sh_h = 16;
video::SColor ambient = m_scene_manager->getAmbientLight().toSColor(); video::SColor ambient = m_scene_manager->getAmbientLight().toSColor();

View File

@ -889,6 +889,18 @@ unsigned getGLSLVersion()
return irr_driver->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 namespace MeshShader
{ {
// Solid Normal and depth pass shaders // Solid Normal and depth pass shaders

View File

@ -48,6 +48,12 @@ public:
static void init(); static void init();
static void setUniforms(const irr::video::SColor &); static void setUniforms(const irr::video::SColor &);
}; };
class SpecularIBLGenerator : public ShaderHelperSingleton<SpecularIBLGenerator, core::matrix4, std::vector<float>, float >, public TextureRead<Trilinear_cubemap>
{
public:
SpecularIBLGenerator();
};
} }