Prefiltering Cubemaps

1. Create PMREM(Prefiltered Mipmaped Radiance Environment map).
a) Get the hdr-map, as an example pisa.hdr(0) (bunch of here (1)).

pisa_latlong
b) Download HDR Shop (2) free edition.
c) Open HDR Shop.

  • open your .hdr – go to Image -> Panorama -> Panoramic Transform

Image

  • settings Source : Longitude,  Dest : Cube Env(Vertical Cross) – Convert

Transform

  • Save as .. *.HDR -> pisa_cross.hdr

d) Download ModifiedCubeMapGen-1_66 (3), many thanks to Sebastien Lagarde (4).

  • Load Cube Cross (choose pisa_cross.hdr)
  • Click on Filter Cubemap(to recieve better IBL resutl try to change settings)

Check

  •  Click CHECK on Save MipChain and SaveCubeMap(.dds) – pisa_cross.dds

2. Generate Sh-Coeff.
a) Upload to engine our PMREM(pisa_cross.dds)
b) Calculate coefficients of spherical harmonics from cube texture

Example with functions:restore lighting value from sh-coeff, for direction,  add and scale.

void sphericalHarmonicsEvaluateDirection(float * result, int order,
const Math::Vector3 & dir)
{
result[0] = 0.282095;
result[1] = 0.488603 * dir.y;
result[2] = 0.488603 * dir.z;
result[3] = 0.488603 * dir.x;
result[4] = 1.092548 * dir.x*dir.y;
result[5] = 1.092548 * dir.y*dir.z;
result[6] = 0.315392 * (3.f*dir.z*dir.z - 1.f);
result[7] = 1.092548 * dir.x * dir.z;
result[8] = 0.546274 * (dir.x*dir.x - dir.y*dir.y);
}

void sphericalHarmonicsAdd(float * result, int order,
const float * inputA, const float * inputB)
{
const int numCoeff = order * order;
for (int i = 0; i < numCoeff; i++)
{
result[i] = inputA[i] + inputB[i];
}
}

void sphericalHarmonicsScale(float * result, int order,
const float * input, float scale)
{
const int numCoeff = order * order;
for (int i = 0; i < numCoeff; i++)
{
result[i] = input[i] * scale;
}
}

void sphericalHarmonicsFromTexture(GLuint cubeTexture,
std::vector<Math::Vector3> & output, const uint order)
{
const uint sqOrder = order*order;

// allocate memory for calculations
output.resize(sqOrder);
std::vector<float> resultR(sqOrder);
std::vector<float> resultG(sqOrder);
std::vector<float> resultB(sqOrder);

// variables that describe current face of cube texture
GLubyte* data;
GLint width, height;
GLint internalFormat;
GLint numComponents;

// initialize values
float fWt = 0.0f;
for (uint i = 0; i < sqOrder; i++)
{
output[i].x = 0;
output[i].y = 0;
output[i].z = 0;
resultR[i] = 0;
resultG[i] = 0;
resultB[i] = 0;
}
std::vector<float> shBuff(sqOrder);
std::vector<float> shBuffB(sqOrder);

const GLenum cubeSides[6] = {
GL_TEXTURE_CUBE_MAP_POSITIVE_Y, // Top
GL_TEXTURE_CUBE_MAP_NEGATIVE_X, // Left
GL_TEXTURE_CUBE_MAP_POSITIVE_Z, // Front
GL_TEXTURE_CUBE_MAP_POSITIVE_X, // Right
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, // Back
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y // Bottom
};

// bind current texture
glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture);

int level = 0;
// for each face of cube texture
for (int face = 0; face < 6; face++)
{
// get width and height
glGetTexLevelParameteriv(cubeSides[face], level, GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(cubeSides[face], level, GL_TEXTURE_HEIGHT, &height);

if (width != height)
{
return;
}

// get format of data in texture

glGetTexLevelParameteriv(cubeSides[face], level,
GL_TEXTURE_INTERNAL_FORMAT, &internalFormat);

// get data from texture
if (internalFormat == GL_RGBA)
{
numComponents = 4;
data = new GLubyte[numComponents * width * width];
}
else if (internalFormat == GL_RGB)
{
numComponents = 3;
data = new GLubyte[numComponents * width * width];
}
else
{
return;
}
glGetTexImage(cubeSides[face], level, internalFormat, GL_UNSIGNED_BYTE, data);

// step between two texels for range [0, 1]
float invWidth = 1.0f / float(width);
// initial negative bound for range [-1, 1]
float negativeBound = -1.0f + invWidth;
// step between two texels for range [-1, 1]
float invWidthBy2 = 2.0f / float(width);

for (int y = 0; y < width; y++)
{
// texture coordinate V in range [-1 to 1]
const float fV = negativeBound + float(y) * invWidthBy2;

for (int x = 0; x < width; x++)
{
// texture coordinate U in range [-1 to 1]
const float fU = negativeBound + float(x) * invWidthBy2;

// determine direction from center of cube texture to current texel
Math::Vector3 dir;
switch (cubeSides[face])
{
case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
dir.x = 1.0f;
dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth);
dir.z = 1.0f - (invWidthBy2 * float(x) + invWidth);
//dir = -dir;
break;
case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
dir.x = -1.0f;
dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth);
dir.z = -1.0f + (invWidthBy2 * float(x) + invWidth);
//dir = dir;
break;
case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
dir.x = -1.0f + (invWidthBy2 * float(x) + invWidth);
dir.y = 1.0f;
dir.z = -1.0f + (invWidthBy2 * float(y) + invWidth);
//dir = dir;
break;
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
dir.x = -1.0f + (invWidthBy2 * float(x) + invWidth);
dir.y = -1.0f;
dir.z = 1.0f - (invWidthBy2 * float(y) + invWidth);
//dir = dir; //!
break;
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
dir.x = -1.0f + (invWidthBy2 * float(x) + invWidth);
dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth);
dir.z = 1.0f;
break;
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
dir.x = 1.0f - (invWidthBy2 * float(x) + invWidth);
dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth);
dir.z = -1.0f;
break;
default:
return;
}

// normalize direction
dir = Math::Normalize(dir);
// scale factor depending on distance from center of the face
const float fDiffSolid = 4.0f / ((1.0f + fU*fU + fV*fV) *
sqrtf(1.0f + fU*fU + fV*fV));
fWt += fDiffSolid;

// calculate coefficients of spherical harmonics for current direction
sphericalHarmonicsEvaluateDirection(shBuff.data(), order, dir);

// index of texel in texture
uint pixOffsetIndex = (x + y * width) * numComponents;
// get color from texture and map to range [0, 1]
Math::Vector3 clr(
float(data[pixOffsetIndex]) / 255,
float(data[pixOffsetIndex + 1]) / 255,
float(data[pixOffsetIndex + 2]) / 255
);

//clr.x = pow(clr.x, 1.1f);
//clr.y = pow(clr.y, 1.1f);
//clr.z = pow(clr.z, 1.1f);

clr.x = clr.x*0.5f;
clr.y = clr.y*0.5f;
clr.z = clr.z*0.5f;

// scale color and add to previously accumulated coefficients
sphericalHarmonicsScale(shBuffB.data(), order,
shBuff.data(), clr.x * fDiffSolid);
sphericalHarmonicsAdd(resultR.data(), order,
resultR.data(), shBuffB.data());

sphericalHarmonicsScale(shBuffB.data(), order,
shBuff.data(), clr.y * fDiffSolid);
sphericalHarmonicsAdd(resultG.data(), order,
resultG.data(), shBuffB.data());

sphericalHarmonicsScale(shBuffB.data(), order,
shBuff.data(), clr.z * fDiffSolid);
sphericalHarmonicsAdd(resultB.data(), order,
resultB.data(), shBuffB.data());
}
}

delete[] data;
}

// final scale for coefficients
const float fNormProj = (4.0f * Math::PI<float>()) / fWt;
sphericalHarmonicsScale(resultR.data(), order, resultR.data(), fNormProj);
sphericalHarmonicsScale(resultG.data(), order, resultG.data(), fNormProj);
sphericalHarmonicsScale(resultB.data(), order, resultB.data(), fNormProj);

// save result
for (uint i = 0; i < sqOrder; i++)
{
output[i].x = resultR[i];
output[i].y = resultG[i];
output[i].z = resultB[i];
}

//glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
}

Thanks to Reev(5) from gamedev.ru

Example in-game SH-coeff shader:

const float PhongRandsConsts[32] =
{
0,
188,
137,
225,
99,
207,
165,
241,
71,
198,
151,
233,
120,
216,
177,
248,
50,
193,
145,
229,
110,
212,
171,
244,
86,
203,
158,
237,
129,
220,
182,
252
};

inline float tosRGBFloat(float rgba)
{
float srgb = (rgba*rgba)*(rgba*0.2848f + 0.7152f);
return srgb;
}

Math::Vector4* GetPhongRands()
{
static Math::Vector4 rands[32];
float r1 = 0.032f;

for (int it = 0, end = 32; it != end; ++it)
{
float r2 = tosRGBFloat(PhongRandsConsts[it] / 255.f);

//float r2 = Platform::Random::RandFloat(0.f, 1.0f);

rands[it].x = r1;
rands[it].y = r2;
rands[it].z = Math::Cos(2.f * Math::PI<float>() * r2);
rands[it].w = Math::Sin(2.f * Math::PI<float>() * r2);

r1 += 0.032f;
}

return rands;
}

Leave a Reply

Your email address will not be published. Required fields are marked *