So, I've gone through this paper (http://www.inf.ufrgs.br/~oliveira/pubs_files/Slomp_Oliveira_Patricio-Tutorial-PRT.pdf). It's all perfectly clear how everything works (or at least I think so), I implemented the stuff - it works. The only thing that bugged was that I had to do pretty lot of computation to just project some analytical lights (as the code in the paper shows you integrate the light function through a number of samples, which could be a lot) and I was pretty sure I had seen SH to be "simpler". And I found chapter 2.15 in ShaderX3. This is the code that author uses for projecting a single directional light into SH:
static void CalculateCoefficentsPerLight(LightStruct *light)
{
SHCoeff[0].red += light->colour[0] * fConst1;
SHCoeff[0].green += light->colour[1] * fConst1;
SHCoeff[0].blue += light->colour[2] * fConst1;
SHCoeff[1].red += light->colour[0] * fConst2 * light->direction[0];
SHCoeff[1].green += light->colour[1] * fConst2 * light->direction[0];
SHCoeff[1].blue += light->colour[2] * fConst2 * light->direction[0];
SHCoeff[2].red += light->colour[0] * fConst2 * light->direction[1];
SHCoeff[2].green += light->colour[1] * fConst2 * light->direction[1];
SHCoeff[2].blue += light->colour[2] * fConst2 * light->direction[1];
SHCoeff[3].red += light->colour[0] * fConst2 * light->direction[2];
SHCoeff[3].green += light->colour[1] * fConst2 * light->direction[2];
SHCoeff[3].blue += light->colour[2] * fConst2 * light->direction[2];
SHCoeff[4].red += light->colour[0] * fConst3 * (light->direction[0] * light->direction[2]);
SHCoeff[4].green += light->colour[1] * fConst3 * (light->direction[0] * light->direction[2]);
SHCoeff[4].blue += light->colour[2] * fConst3 * (light->direction[0] * light->direction[2]);
SHCoeff[5].red += light->colour[0] * fConst3 * (light->direction[2] * light->direction[1]);
SHCoeff[5].green += light->colour[1] * fConst3 * (light->direction[2] * light->direction[1]);
SHCoeff[5].blue += light->colour[2] * fConst3 * (light->direction[2] * light->direction[1]);
SHCoeff[6].red += light->colour[0] * fConst3 * (light->direction[1] * light->direction[0]);
SHCoeff[6].green += light->colour[1] * fConst3 * (light->direction[1] * light->direction[0]);
SHCoeff[6].blue += light->colour[2] * fConst3 * (light->direction[1] * light->direction[0]);
SHCoeff[7].red += light->colour[0] * fConst4 * (3.0f * light->direction[2] * light->direction[2] - 1.0f);
SHCoeff[7].green += light->colour[1] * fConst4 * (3.0f * light->direction[2] * light->direction[2] - 1.0f);
SHCoeff[7].blue += light->colour[2] * fConst4 * (3.0f * light->direction[2] * light->direction[2] - 1.0f);
SHCoeff[8].red += light->colour[0] * fConst5 * (light->direction[0] * light->direction[0] - light->direction[1] * light->direction[1]);
SHCoeff[8].green += light->colour[1] * fConst5 * (light->direction[0] * light->direction[0] - light->direction[1] * light->direction[1]);
SHCoeff[8].blue += light->colour[2] * fConst5 * (light->direction[0] * light->direction[0] - light->direction[1] * light->direction[1]);
}
Later on, these nine SHCoeffs are passed to the vertex shader to compute the final illumination:
vec3 norm = gl_Normal;
color = Coefficients[0];
color += Coefficients[1] * norm.x;
color += Coefficients[2] * norm.y;
color += Coefficients[3] * norm.z;
color += Coefficients[4] * norm.x * norm.z;
color += Coefficients[5] * norm.y * norm.z;
color += Coefficients[6] * norm.x * norm.y;
color += Coefficients[7] * (3.0 * norm.z * norm.z - 1.0);
color += Coefficients[8] * (norm.x * norm.x - norm.y * norm.y);
This stuff looks much simpler and I don't see any need for sophisticated integration, nor even any dot(lightVector, normal) term (which is present in the PRT Tutorial paper in the projection of the transfer function). So my question is simply about the derivation of the ShaderX3 article's constants and how this relates to the "general" solution presented in the PRT Tutorial paper?