bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<glm::vec3> & output, const uint order) { int width = cubeTexture.getWidth(); if(width != cubeTexture.getHeight()) { return false; } PROFILE_RANGE(render_gpu, "sphericalHarmonicsFromTexture"); auto mipFormat = cubeTexture.getStoredMipFormat(); std::function<glm::vec3(uint32)> unpackFunc; switch (mipFormat.getSemantic()) { case gpu::R11G11B10: unpackFunc = glm::unpackF2x11_1x10; break; case gpu::RGB9E5: unpackFunc = glm::unpackF3x9_E1x5; break; default: assert(false); break; } 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); // initialize values float fWt = 0.0f; for(uint i=0; i < sqOrder; i++) { output[i] = glm::vec3(0.0f); resultR[i] = 0.0f; resultG[i] = 0; resultB[i] = 0; } std::vector<float> shBuff(sqOrder); std::vector<float> shBuffB(sqOrder); // We trade accuracy for speed by breaking the image into 32x32 parts // and approximating the distance for all the pixels in each part to be // the distance to the part's center. int numDivisionsPerSide = 32; if (width < numDivisionsPerSide) { numDivisionsPerSide = width; } int stride = width / numDivisionsPerSide; int halfStride = stride / 2; // for each face of cube texture for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { PROFILE_RANGE(render_gpu, "ProcessFace"); auto data = reinterpret_cast<const uint32*>( cubeTexture.accessStoredMipFace(0, face)->readData() ); if (data == nullptr) { continue; } // 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=halfStride; y < width-halfStride; y += stride) { // texture coordinate V in range [-1 to 1] const float fV = negativeBound + float(y) * invWidthBy2; for(int x=halfStride; x < width - halfStride; x += stride) { // 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 glm::vec3 dir; switch(face) { case gpu::Texture::CUBE_FACE_RIGHT_POS_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 gpu::Texture::CUBE_FACE_LEFT_NEG_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 gpu::Texture::CUBE_FACE_TOP_POS_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 gpu::Texture::CUBE_FACE_BOTTOM_NEG_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 gpu::Texture::CUBE_FACE_BACK_POS_Z: { dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); dir.z = 1.0f; break; } case gpu::Texture::CUBE_FACE_FRONT_NEG_Z: { dir.x = 1.0f - (invWidthBy2 * float(x) + invWidth); dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); dir.z = - 1.0f; break; } default: return false; } // normalize direction dir = glm::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 // get color from texture glm::vec3 color{ 0.0f, 0.0f, 0.0f }; for (int i = 0; i < stride; ++i) { for (int j = 0; j < stride; ++j) { int k = (int)(x + i - halfStride + (y + j - halfStride) * width); color += unpackFunc(data[k]); } } // scale color and add to previously accumulated coefficients // red sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.r * fDiffSolid); sphericalHarmonicsAdd(resultR.data(), order, resultR.data(), shBuffB.data()); // green sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.g * fDiffSolid); sphericalHarmonicsAdd(resultG.data(), order, resultG.data(), shBuffB.data()); // blue sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.b * fDiffSolid); sphericalHarmonicsAdd(resultB.data(), order, resultB.data(), shBuffB.data()); } } } // final scale for coefficients const float fNormProj = (4.0f * glm::pi<float>()) / (fWt * (float)(stride * stride)); 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] = glm::vec3(resultR[i], resultG[i], resultB[i]); } return true; }
bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<glm::vec3> & 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); int width, height; // initialize values float fWt = 0.0f; for(uint i=0; i < sqOrder; i++) { output[i] = glm::vec3(0.0f); resultR[i] = 0.0f; resultG[i] = 0; resultB[i] = 0; } std::vector<float> shBuff(sqOrder); std::vector<float> shBuffB(sqOrder); // get width and height width = height = cubeTexture.getWidth(); if(width != height) { return false; } const float UCHAR_TO_FLOAT = 1.0f / float(std::numeric_limits<unsigned char>::max()); // for each face of cube texture for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { auto numComponents = cubeTexture.accessStoredMipFace(0,face)->_format.getDimensionCount(); auto data = cubeTexture.accessStoredMipFace(0,face)->_sysmem.readData(); if (data == nullptr) { continue; } // 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 glm::vec3 dir; switch(face) { case gpu::Texture::CUBE_FACE_RIGHT_POS_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 gpu::Texture::CUBE_FACE_LEFT_NEG_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 gpu::Texture::CUBE_FACE_TOP_POS_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 gpu::Texture::CUBE_FACE_BOTTOM_NEG_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 gpu::Texture::CUBE_FACE_BACK_POS_Z: { dir.x = - 1.0f + (invWidthBy2 * float(x) + invWidth); dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); dir.z = 1.0f; break; } case gpu::Texture::CUBE_FACE_FRONT_NEG_Z: { dir.x = 1.0f - (invWidthBy2 * float(x) + invWidth); dir.y = 1.0f - (invWidthBy2 * float(y) + invWidth); dir.z = - 1.0f; break; } default: return false; } // normalize direction dir = glm::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] glm::vec3 clr(float(data[pixOffsetIndex]) * UCHAR_TO_FLOAT, float(data[pixOffsetIndex+1]) * UCHAR_TO_FLOAT, float(data[pixOffsetIndex+2]) * UCHAR_TO_FLOAT); // Gamma correct clr = sRGBToLinear(clr); // scale color and add to previously accumulated coefficients sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.r * fDiffSolid); sphericalHarmonicsAdd(resultR.data(), order, resultR.data(), shBuffB.data()); sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.g * fDiffSolid); sphericalHarmonicsAdd(resultG.data(), order, resultG.data(), shBuffB.data()); sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.b * fDiffSolid); sphericalHarmonicsAdd(resultB.data(), order, resultB.data(), shBuffB.data()); } } } // final scale for coefficients const float fNormProj = (4.0f * glm::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++) { // gamma Correct // output[i] = linearTosRGB(glm::vec3(resultR[i], resultG[i], resultB[i])); output[i] = glm::vec3(resultR[i], resultG[i], resultB[i]); } return true; }