// SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2015 SuperTuxKart-Team // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 3 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "graphics/irr_driver.hpp" #include "graphics/sphericalHarmonics.hpp" #include "utils/log.hpp" #include #include #include using namespace irr; namespace { /** Convert an unsigned char cubemap texture to a float texture * \param sh_rgba The 6 faces of the cubemap texture * \param sh_w Texture width * \param sh_h Texture height * \param[out] float_tex_cube The converted float cubemap texture */ void convertToFloatTexture(unsigned char *sh_rgba[6], unsigned sh_w, unsigned sh_h, Color *float_tex_cube[6]) { for (unsigned i = 0; i < 6; i++) { float_tex_cube[i] = new Color[sh_w * sh_h]; for (unsigned j = 0; j < sh_w * sh_h; j++) { float_tex_cube[i][j].Blue = powf(float(0xFF & sh_rgba[i][4 * j]) / 255.f, 2.2f); float_tex_cube[i][j].Green = powf(float(0xFF & sh_rgba[i][4 * j + 1]) / 255.f, 2.2f); float_tex_cube[i][j].Red = powf(float(0xFF & sh_rgba[i][4 * j + 2]) / 255.f, 2.2f); } } } //convertToFloatTexture // ------------------------------------------------------------------------ /** Print the nine first spherical harmonics coefficients * \param SH_coeff The nine spherical harmonics coefficients */ void displayCoeff(float *SH_coeff) { Log::debug("SphericalHarmonics", "L00:%f", SH_coeff[0]); Log::debug("SphericalHarmonics", "L1-1:%f, L10:%f, L11:%f", SH_coeff[1], SH_coeff[2], SH_coeff[3]); Log::debug("SphericalHarmonics", "L2-2:%f, L2-1:%f, L20:%f, L21:%f, L22:%f", SH_coeff[4], SH_coeff[5], SH_coeff[6], SH_coeff[7], SH_coeff[8]); } // displayCoeff // ------------------------------------------------------------------------ /** Compute the value of the (i,j) texel of the environment map * from the spherical harmonics coefficients * \param i The texel line * \param j The texel column * \param width The texture width * \param height The texture height * \param Coeff The 9 first SH coefficients for a color channel (blue, green or red) * \param Yml The sphericals harmonics functions values on each texel of the cubemap */ float getTexelValue(unsigned i, unsigned j, size_t width, size_t height, float *Coeff, float *Y00, float *Y1minus1, float *Y10, float *Y11, float *Y2minus2, float * Y2minus1, float * Y20, float *Y21, float *Y22) { float solidangle = 1.; size_t idx = i * height + j; float reconstructedVal = Y00[idx] * Coeff[0]; reconstructedVal += Y1minus1[i * height + j] * Coeff[1] + Y10[i * height + j] * Coeff[2] + Y11[i * height + j] * Coeff[3]; reconstructedVal += Y2minus2[idx] * Coeff[4] + Y2minus1[idx] * Coeff[5] + Y20[idx] * Coeff[6] + Y21[idx] * Coeff[7] + Y22[idx] * Coeff[8]; reconstructedVal /= solidangle; return std::max(255.0f * reconstructedVal, 0.f); } // getTexelValue // ------------------------------------------------------------------------ /** Return a normalized vector aiming at a texel on a cubemap * \param face The face of the cubemap * \param j The texel line in the face * \param j The texel column in the face * \param x The x vector component * \param y The y vector component * \param z The z vector component */ void getXYZ(GLenum face, float i, float j, float &x, float &y, float &z) { switch (face) { case GL_TEXTURE_CUBE_MAP_POSITIVE_X: x = 1.; y = -i; z = -j; break; case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: x = -1.; y = -i; z = j; break; case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: x = j; y = 1.; z = i; break; case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: x = j; y = -1; z = -i; break; case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: x = j; y = -i; z = 1; break; case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: x = -j; y = -i; z = -1; break; } float norm = sqrt(x * x + y * y + z * z); x /= norm, y /= norm, z /= norm; return; } // getXYZ // ------------------------------------------------------------------------ /** Compute the value of the spherical harmonics basis functions (Yml) * on each texel of a cubemap face * \param face Face of the cubemap * \param edge_size Size of the cubemap face * \param[out] Yml The sphericals harmonics functions values on each texel of the cubemap */ void getYml(GLenum face, size_t edge_size, float *Y00, float *Y1minus1, float *Y10, float *Y11, float *Y2minus2, float *Y2minus1, float *Y20, float *Y21, float *Y22) { #pragma omp parallel for for (int i = 0; i < int(edge_size); i++) { for (unsigned j = 0; j < edge_size; j++) { float x, y, z; float fi = float(i), fj = float(j); fi /= edge_size, fj /= edge_size; fi = 2 * fi - 1, fj = 2 * fj - 1; getXYZ(face, fi, fj, x, y, z); // constant part of Ylm float c00 = 0.282095f; float c1minus1 = 0.488603f; float c10 = 0.488603f; float c11 = 0.488603f; float c2minus2 = 1.092548f; float c2minus1 = 1.092548f; float c21 = 1.092548f; float c20 = 0.315392f; float c22 = 0.546274f; size_t idx = i * edge_size + j; Y00[idx] = c00; Y1minus1[idx] = c1minus1 * y; Y10[idx] = c10 * z; Y11[idx] = c11 * x; Y2minus2[idx] = c2minus2 * x * y; Y2minus1[idx] = c2minus1 * y * z; Y21[idx] = c21 * x * z; Y20[idx] = c20 * (3 * z * z - 1); Y22[idx] = c22 * (x * x - y * y); } } } } //namespace // ---------------------------------------------------------------------------- /** Compute m_red_SH_coeff, m_green_SH_coeff and m_blue_SH_coeff from Yml values * \param cubemap_face The 6 cubemap faces (float textures) * \param edge_size Size of the cubemap face * \param Yml The sphericals harmonics functions values on each texel of the cubemap */ void SphericalHarmonics::projectSH(Color *cubemap_face[6], size_t edge_size, float *Y00[], float *Y1minus1[], float *Y10[], float *Y11[], float *Y2minus2[], float *Y2minus1[], float * Y20[], float *Y21[], float *Y22[]) { for (unsigned i = 0; i < 9; i++) { m_blue_SH_coeff[i] = 0; m_green_SH_coeff[i] = 0; m_red_SH_coeff[i] = 0; } float wh = float(edge_size * edge_size); float b0 = 0., b1 = 0., b2 = 0., b3 = 0., b4 = 0., b5 = 0., b6 = 0., b7 = 0., b8 = 0.; float r0 = 0., r1 = 0., r2 = 0., r3 = 0., r4 = 0., r5 = 0., r6 = 0., r7 = 0., r8 = 0.; float g0 = 0., g1 = 0., g2 = 0., g3 = 0., g4 = 0., g5 = 0., g6 = 0., g7 = 0., g8 = 0.; for (unsigned face = 0; face < 6; face++) { #pragma omp parallel for reduction(+ : b0, b1, b2, b3, b4, b5, b6, b7, b8, \ r0, r1, r2, r3, r4, r5, r6, r7, r8, \ g0, g1, g2, g3, g4, g5, g6, g7, g8) for (int i = 0; i < int(edge_size); i++) { for (unsigned j = 0; j < edge_size; j++) { int idx = i * edge_size + j; float fi = float(i), fj = float(j); fi /= edge_size, fj /= edge_size; fi = 2 * fi - 1, fj = 2 * fj - 1; float d = sqrt(fi * fi + fj * fj + 1); // Constant obtained by projecting unprojected ref values float solidangle = 2.75f / (wh * pow(d, 1.5f)); // pow(., 2.2) to convert from srgb float b = cubemap_face[face][edge_size * i + j].Blue; float g = cubemap_face[face][edge_size * i + j].Green; float r = cubemap_face[face][edge_size * i + j].Red; b0 += b * Y00[face][idx] * solidangle; b1 += b * Y1minus1[face][idx] * solidangle; b2 += b * Y10[face][idx] * solidangle; b3 += b * Y11[face][idx] * solidangle; b4 += b * Y2minus2[face][idx] * solidangle; b5 += b * Y2minus1[face][idx] * solidangle; b6 += b * Y20[face][idx] * solidangle; b7 += b * Y21[face][idx] * solidangle; b8 += b * Y22[face][idx] * solidangle; g0 += g * Y00[face][idx] * solidangle; g1 += g * Y1minus1[face][idx] * solidangle; g2 += g * Y10[face][idx] * solidangle; g3 += g * Y11[face][idx] * solidangle; g4 += g * Y2minus2[face][idx] * solidangle; g5 += g * Y2minus1[face][idx] * solidangle; g6 += g * Y20[face][idx] * solidangle; g7 += g * Y21[face][idx] * solidangle; g8 += g * Y22[face][idx] * solidangle; r0 += r * Y00[face][idx] * solidangle; r1 += r * Y1minus1[face][idx] * solidangle; r2 += r * Y10[face][idx] * solidangle; r3 += r * Y11[face][idx] * solidangle; r4 += r * Y2minus2[face][idx] * solidangle; r5 += r * Y2minus1[face][idx] * solidangle; r6 += r * Y20[face][idx] * solidangle; r7 += r * Y21[face][idx] * solidangle; r8 += r * Y22[face][idx] * solidangle; } } } m_blue_SH_coeff[0] = b0; m_blue_SH_coeff[1] = b1; m_blue_SH_coeff[2] = b2; m_blue_SH_coeff[3] = b3; m_blue_SH_coeff[4] = b4; m_blue_SH_coeff[5] = b5; m_blue_SH_coeff[6] = b6; m_blue_SH_coeff[7] = b7; m_blue_SH_coeff[8] = b8; m_red_SH_coeff[0] = r0; m_red_SH_coeff[1] = r1; m_red_SH_coeff[2] = r2; m_red_SH_coeff[3] = r3; m_red_SH_coeff[4] = r4; m_red_SH_coeff[5] = r5; m_red_SH_coeff[6] = r6; m_red_SH_coeff[7] = r7; m_red_SH_coeff[8] = r8; m_green_SH_coeff[0] = g0; m_green_SH_coeff[1] = g1; m_green_SH_coeff[2] = g2; m_green_SH_coeff[3] = g3; m_green_SH_coeff[4] = g4; m_green_SH_coeff[5] = g5; m_green_SH_coeff[6] = g6; m_green_SH_coeff[7] = g7; m_green_SH_coeff[8] = g8; } // projectSH // ---------------------------------------------------------------------------- /** Generate the 9 first SH coefficients for each color channel * using the cubemap provided by CubemapFace. * \param cubemap_face The 6 cubemap faces (float textures) * \param edge_size Size of the cubemap face */ void SphericalHarmonics::generateSphericalHarmonics(Color *cubemap_face[6], size_t edge_size) { float *Y00[6]; float *Y1minus1[6]; float *Y10[6]; float *Y11[6]; float *Y2minus2[6]; float *Y2minus1[6]; float *Y20[6]; float *Y21[6]; float *Y22[6]; for (unsigned face = 0; face < 6; face++) { Y00[face] = new float[edge_size * edge_size]; Y1minus1[face] = new float[edge_size * edge_size]; Y10[face] = new float[edge_size * edge_size]; Y11[face] = new float[edge_size * edge_size]; Y2minus2[face] = new float[edge_size * edge_size]; Y2minus1[face] = new float[edge_size * edge_size]; Y20[face] = new float[edge_size * edge_size]; Y21[face] = new float[edge_size * edge_size]; Y22[face] = new float[edge_size * edge_size]; getYml(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, edge_size, Y00[face], Y1minus1[face], Y10[face], Y11[face], Y2minus2[face], Y2minus1[face], Y20[face], Y21[face], Y22[face]); } projectSH(cubemap_face, edge_size, Y00, Y1minus1, Y10, Y11, Y2minus2, Y2minus1, Y20, Y21, Y22); for (unsigned face = 0; face < 6; face++) { delete[] Y00[face]; delete[] Y1minus1[face]; delete[] Y10[face]; delete[] Y11[face]; delete[] Y2minus2[face]; delete[] Y2minus1[face]; delete[] Y20[face]; delete[] Y21[face]; delete[] Y22[face]; } } // generateSphericalHarmonics // ---------------------------------------------------------------------------- SphericalHarmonics::SphericalHarmonics(const std::vector &spherical_harmonics_textures) { setTextures(spherical_harmonics_textures); } // ---------------------------------------------------------------------------- /** When spherical harmonics textures are not defined, SH coefficents are computed * from ambient light */ SphericalHarmonics::SphericalHarmonics(const video::SColor &ambient) { //make sure m_ambient and ambient are not equal m_ambient = (ambient==0) ? 1 : 0; setAmbientLight(ambient); } /** Compute spherical harmonics coefficients from 6 textures */ void SphericalHarmonics::setTextures(const std::vector &spherical_harmonics_textures) { assert(spherical_harmonics_textures.size() == 6); m_spherical_harmonics_textures = spherical_harmonics_textures; const unsigned texture_permutation[] = { 2, 3, 0, 1, 5, 4 }; unsigned char *sh_rgba[6]; unsigned sh_w = 0, sh_h = 0; for (unsigned i = 0; i < 6; i++) { sh_w = std::max(sh_w, m_spherical_harmonics_textures[i]->getSize().Width); sh_h = std::max(sh_h, m_spherical_harmonics_textures[i]->getSize().Height); } for (unsigned i = 0; i < 6; i++) sh_rgba[i] = new unsigned char[sh_w * sh_h * 4]; for (unsigned i = 0; i < 6; i++) { unsigned idx = texture_permutation[i]; video::IImage* image = irr_driver->getVideoDriver()->createImageFromData( m_spherical_harmonics_textures[idx]->getColorFormat(), m_spherical_harmonics_textures[idx]->getSize(), m_spherical_harmonics_textures[idx]->lock(), false ); m_spherical_harmonics_textures[idx]->unlock(); image->copyToScaling(sh_rgba[i], sh_w, sh_h); delete image; } //for (unsigned i = 0; i < 6; i++) Color *float_tex_cube[6]; convertToFloatTexture(sh_rgba, sh_w, sh_h, float_tex_cube); generateSphericalHarmonics(float_tex_cube, sh_w); for (unsigned i = 0; i < 6; i++) { delete[] sh_rgba[i]; delete[] float_tex_cube[i]; } } //setSphericalHarmonicsTextures /** Compute spherical harmonics coefficients from ambient light */ void SphericalHarmonics::setAmbientLight(const video::SColor &ambient) { //do not recompute SH coefficients if we already use the same ambient light if((m_spherical_harmonics_textures.size() != 6) && (ambient == m_ambient)) return; m_spherical_harmonics_textures.clear(); m_ambient = ambient; unsigned char *sh_rgba[6]; unsigned sh_w = 16; unsigned sh_h = 16; for (unsigned i = 0; i < 6; i++) { sh_rgba[i] = new unsigned char[sh_w * sh_h * 4]; for (unsigned j = 0; j < sh_w * sh_h * 4; j += 4) { sh_rgba[i][j] = ambient.getBlue(); sh_rgba[i][j + 1] = ambient.getGreen(); sh_rgba[i][j + 2] = ambient.getRed(); sh_rgba[i][j + 3] = 255; } } Color *float_tex_cube[6]; convertToFloatTexture(sh_rgba, sh_w, sh_h, float_tex_cube); generateSphericalHarmonics(float_tex_cube, sh_w); for (unsigned i = 0; i < 6; i++) { delete[] sh_rgba[i]; delete[] float_tex_cube[i]; } // Diffuse env map is x 0.25, compensate for (unsigned i = 0; i < 9; i++) { m_blue_SH_coeff[i] *= 4; m_green_SH_coeff[i] *= 4; m_red_SH_coeff[i] *= 4; } } //setAmbientLight // ---------------------------------------------------------------------------- /** Print spherical harmonics coefficients (debug) */ void SphericalHarmonics::printCoeff() { Log::debug("SphericalHarmonics", "Blue_SH:"); displayCoeff(m_blue_SH_coeff); Log::debug("SphericalHarmonics", "Green_SH:"); displayCoeff(m_green_SH_coeff); Log::debug("SphericalHarmonics", "Red_SH:"); displayCoeff(m_red_SH_coeff); } //printCoeff // ---------------------------------------------------------------------------- /** Compute the the environment map from the spherical harmonics coefficients * \param width The texture width * \param height The texture height * \param Yml The sphericals harmonics functions values * \param[out] output The environment map texels values */ void SphericalHarmonics::unprojectSH(size_t width, size_t height, float *Y00[], float *Y1minus1[], float *Y10[], float *Y11[], float *Y2minus2[], float *Y2minus1[], float *Y20[], float *Y21[], float *Y22[], float *output[]) { for (unsigned face = 0; face < 6; face++) { for (unsigned i = 0; i < width; i++) { for (unsigned j = 0; j < height; j++) { float fi = float(i), fj = float(j); fi /= width, fj /= height; fi = 2 * fi - 1, fj = 2 * fj - 1; output[face][4 * height * i + 4 * j + 2] = getTexelValue(i, j, width, height, m_red_SH_coeff, Y00[face], Y1minus1[face], Y10[face], Y11[face], Y2minus2[face], Y2minus1[face], Y20[face], Y21[face], Y22[face]); output[face][4 * height * i + 4 * j + 1] = getTexelValue(i, j, width, height, m_green_SH_coeff, Y00[face], Y1minus1[face], Y10[face], Y11[face], Y2minus2[face], Y2minus1[face], Y20[face], Y21[face], Y22[face]); output[face][4 * height * i + 4 * j] = getTexelValue(i, j, width, height, m_blue_SH_coeff, Y00[face], Y1minus1[face], Y10[face], Y11[face], Y2minus2[face], Y2minus1[face], Y20[face], Y21[face], Y22[face]); } } } } // unprojectSH