sunLoadingImage
whowedImag
decoration left 1
decoration left 2
transhome
transprojects
transgallery
transarticles
decoration rigth
English
Українська
Show/Hide search bar
black cat logo variable logo
[12 Вер 2013]

Розрахунок коефіцієнтів сферичної гармоніки з кубічної текстури (карти освітлення)

У цьому уроці показано як з кубічної текстури, в якій збережено освітлення навколо точки в просторі сцени, розрахувати 9 коефіцієнтів сферичних гармонік. Коефіцієнти сферичних гармонік можуть бути використані для реконструкції дифузного та заповнюючого освітлення в шейдері. Розрахунок коефіцієнтів сферичних гармонік є швидшим ніж розмивання кубічної текстури для методу image-based lighting, а отже може бути використаний для динамічного оновлення параметрів освітлення після оновлення сцени. Для відтворення дифузного освітлення потрібно розрахувати коефіцієнти тільки для перших трьох смуг сферичних гармонік, тобто 9 коефіцієнтів. Так як коефіцієти розраховуються окремо для кожного RGB канала, то потрібно 27 чисел з плаваючою комою (9 vec3 значень).
Функція sphericalHarmonicsFromTexture() розраховує коефіцієнти сферичних гармонік для кубічної текстури. Спочатку виділяється та занулюється необхідна кількість пам'яті для збереження коефіцієнтів та проміжних даних. Далі кубічна текстура встановлюється як поточна. Для кожної грані кубічної текстури дістаються дані про всі текселі. Далі для кожного текселя розраховується напрям від центру кубічної текстури до текселя (на одиничному кубі). Розраховується коефіцієнт масштабування кольору. Напрям до поточного текселя проектується на кожну базисну функцію сферичних гармонік. Далі дістається колір з текстури і переводиться з 8 бітного представлення в floating point представлення. Колір масштабується залежно від відстані текселя до центру текстури (щоб правильно враховувати більшу кількість текселів на одиницю простору в місцях, що ближче до ребер кубічної текстури). Розраховані коефіцієнти для кожного RGB каналу зберігаються в відповідному буфері. В кінці сумарні коефіцієнти сферичних гармонік нормалізуються для приведення в межі, які відповідають значенням в обробленій кубічній текстурі.
Реконструкція кольору з коефіцієнтів сферичних гармонік наведена в функції colorFromSphericalHarmonics(). Параметрами для функції є 9 коефіцієнтів сферичних гармонік, розрахованих функцією sphericalHarmonicsFromTexture().
Функція для обрахунку коефіцієнтів сферичних гармонік:
void sphericalHarmonicsFromTexture(TextureCube * cubeTexture,
                                    std::vector<glm::vec3> & output, const uint order)
{
   const uint sqOrder = order*order;

   // виділити пам'ять для збереження коефіцієнтів
   output.resize(sqOrder);
   std::vector<float> resultR(sqOrder);
   std::vector<float> resultG(sqOrder);
   std::vector<float> resultB(sqOrder);

   // змінні для зчитання даних з текстури, та ін
   std::unique_ptr data;
   GLint width, height;
   GLint internalFormat;
   GLint numComponents;

   // онулити змінні
   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);

   // встановити поточну текстуру
   glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture->texture());
   // для кожної грані кубічної текстури
   for(int face=0; face < 6; face++)
   {
      // дістати ширину і висоту
      glGetTexLevelParameteriv(cubeSides[face], 0, GL_TEXTURE_WIDTH, &width);
      glGetTexLevelParameteriv(cubeSides[face], 0, GL_TEXTURE_HEIGHT, &height);

      if(width != height)
      {
         return;
      }

      // дістати формат даних в текстурі
      glGetTexLevelParameteriv(cubeSides[face], 0,
            GL_TEXTURE_INTERNAL_FORMAT, &internalFormat);

      // дістати данні з текстури
      if(internalFormat == GL_RGBA)
      {
         numComponents = 4;
         data = std::unique_ptr(new GLubyte[numComponents * width * width]);
      }
      else if(internalFormat == GL_RGB)
      {
         numComponents = 3;
         data = std::unique_ptr(new GLubyte[numComponents * width * width]);
      }
      else
      {
         return;
      }
      glGetTexImage(cubeSides[face], 0, internalFormat, GL_UNSIGNED_BYTE, data.get());

      // крок між двома текселями в межі [0, 1]
      float invWidth = 1.0f / float(width);
      // початкова негативна координата в межі [-1, 1]
      float negativeBound = -1.0f + invWidth;
      // крок між двома текселями в межі [-1, 1]
      float invWidthBy2 = 2.0f / float(width);

      for(int y=0; y < width; y++)
      {
         // текстурна координата V в межах [-1 to 1]
         const float fV = negativeBound + float(y) * invWidthBy2;

         for(int x=0; x < width; x++)
         {
            // текстурна координата U в межах [-1 to 1]
            const float fU = negativeBound + float(x) * invWidthBy2;

            // напрям від центра кубічної текстури до поточного текселя
            glm::vec3 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;
            }

            // нормалізувати напрямок
            dir = glm::normalize(dir);

            // маштабування кольору в залежності від відстані до центра текстури
            const float fDiffSolid = 4.0f / ((1.0f + fU*fU + fV*fV) *
                                       sqrtf(1.0f + fU*fU + fV*fV));
            fWt += fDiffSolid;

            // розрахувати коефіцієнти сферичних гармонік для поточного напрямку
            sphericalHarmonicsEvaluateDirection(shBuff.data(), order, dir);

            // індекс текселя в текстурі
            uint pixOffsetIndex = (x + y * width) * numComponents;
            // дістати колір з текстури і перетворити в межі [0, 1]
            glm::vec3 clr(
                  float(data[pixOffsetIndex]) / 255,
                  float(data[pixOffsetIndex+1]) / 255,
                  float(data[pixOffsetIndex+2]) / 255
               );

            // масштабувати колір і додати коефіцієнти до попередніх коефіцієнтів
            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());
         }
      }
   }

   // фінальне масштабування коефіцієнтів
   const float fNormProj = (4.0f * M_PI) / fWt;
   sphericalHarmonicsScale(resultR.data(), order, resultR.data(), fNormProj);
   sphericalHarmonicsScale(resultG.data(), order, resultG.data(), fNormProj);
   sphericalHarmonicsScale(resultB.data(), order, resultB.data(), fNormProj);

   // зберегти результат
   for(uint i=0; i < sqOrder; i++)
   {
      output[i].r = resultR[i];
      output[i].g = resultG[i];
      output[i].b = resultB[i];
   }
}
Функція для відновлення значення освітлення зі сферичних гармонік:
glm::vec3 sphericalHarmonicsFromTexture(glm::vec3 & N, std::vector & coef)
{
   return

      // константна базисна функція, не залежить від напряму
      C4 * coef[0] +

      // базисні функції орієнтовані вздовж осей
      2.0 * C2 * coef[1] * N.y +
      2.0 * C2 * coef[2] * N.z +
      2.0 * C2 * coef[3] * N.x +

      // квадратичні базисні функції
      2.0 * C1 * coef[4] * N.x * N.y +
      2.0 * C1 * coef[5] * N.y * N.z +
      C3 * coef[6] * N.z * N.z - C5 * coef[6] +
      2.0 * C1 * coef[7] * N.x * N.z +
      C1 * coef[8] * (N.x * N.x - N.y * N.y);
}
Функція для розрахунку коефіцієнтів сферичних гармонік для напрямку:
void sphericalHarmonicsEvaluateDirection(float * result, int order,
                              const glm::vec3 & dir)
{
   // розрахунок коефіцієнтів для перших трьох смуг сферичних гармонік
   double p_0_0 = 0.282094791773878140;
   double p_1_0 = 0.488602511902919920 * dir.z;
   double p_1_1 = -0.488602511902919920;
   double p_2_0 = 0.946174695757560080 * dir.z * dir.z - 0.315391565252520050;
   double p_2_1 = -1.092548430592079200 * dir.z;
   double p_2_2 = 0.546274215296039590;
   result[0] = p_0_0;
   result[1] = p_1_1 * dir.y;
   result[2] = p_1_0;
   result[3] = p_1_1 * dir.x;
   result[4] = p_2_2 * (dir.x * dir.y + dir.y * dir.x);
   result[5] = p_2_1 * dir.y;
   result[6] = p_2_0;
   result[7] = p_2_1 * dir.x;
   result[8] = p_2_2 * (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;
   }
}



Sun and Black Cat- Ігор Дихта (igor dykhta email) © 2007-2014