stk-code_catmod/src/graphics/sphericalHarmonics.cpp

522 lines
20 KiB
C++

// 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 <algorithm>
#include <cassert>
#include <irrlicht.h>
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<video::ITexture *> &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<video::ITexture *> &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