Sign in to follow this  

Spherical Harmonic Lighting, Basis Function

This topic is 3458 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi, A question about spherical harmonics in the ATI realtime Global Illumination demo. I'm messing around with that demo for quite some time now. Unfortunately, my limited math skills have a hard time with the spherical harmonics usage in that demo. Well, I finally come at a point where it seems to start working. However, its only working for 50%. I mean, the lighting is not correct for all directions. For testing, I fake the indirect light per probe by just using white light coming from all directions. Thus, the result should show a completely white scene. However, 50% is white, the other half is black (with a smooth transition between the two), as if the light is only coming from 3 directions instead of all 6 (the ATI demo is using cubeMaps == light coming from 6 directions). After billion checks I have found something strange. The ATI demo builds 4 cubeMaps with precomputed values for the SH basis function. The demo uses what they call 4 'SH bands' == 4 "basisCubes". If you like to see how they calculate the values, please check the code below. Anyway, I checked these cubeMaps by drawing them on a cube. If I understand it right, my renderings should show these funny 'bubble' shapes you often see in SH papers to illustrate polynomials / SH bands, right? I did so, and indeed, I saw these kinda shapes. It's very hard to tell if they are exactly correct though, but at least there was something in the cubeMaps. However, if I sum all the values in these cubeMaps, my totals are cubeMap 1 (band 1) float4{56.94 ,0,0,0} cubeMap 2 (band 2) float4{0,0,0,0} cubeMap 3 (band 3) float4{0,0,0,0} cubeMap 4 (band 4) float4{0,0,0,0} As you can see, only the first precomputed basis function value is not zero. At first I thought this would be correct. The cubeMap holds both positive as negative values. But if you take a look at the shader that produces the SH coefficients for each probe...
	// Shader loops through the basis- and environment CubeMap (both have the same
	// sizes). Each environment Map value gets multiplied with the basisCube
	// value. In the end, the average over the sum will be taken and stored
	// in the 3 target textures. Each SH band has 3 target textures to store these
	// results. The target textures are used in the final pass to calculate the
	// GI lighting per pixel.
	float4 c0 = 0;
	float4 c1 = 0;
	float4 c2 = 0;

	for (int i = 0; i < 6; i++)		// 6 cubeMap faces
	{
		int slice = In.slice + i;	// Multiple environment cubeMaps are stored in a 3D tex
		for (int y = 0; y < PROBE_SIZE; y++)	// PROBE_SIZE = 8. CubeMap = 8x8x6 pixels
		{
			[unroll]
			for (int x = 0; x < PROBE_SIZE; x++)
			{
				float3 envi  = enviProbe.Load(int4(x, y, slice, 0)).rgb;
                            // TEST. Simple white indirect lighting instead of
                            // using an environment map
                            envi = float3(1,1,1);     
				float4 basis = basisCube.Load(int4(x, y, i, 0));

				// Environment color * basis.xyzw
				c0 += envi.r * basis;	
				c1 += envi.g * basis;
				c2 += envi.b * basis;
			}
		}
	}

	// Average results
	const float nf = (1.0 / (PROBE_SIZE * PROBE_SIZE));

	PsOut Out;
	Out.rCoeffs = c0 * nf;
	Out.gCoeffs = c1 * nf;
	Out.bCoeffs = c2 * nf;
I'm not very familiar with HLSL, but when I read this, I suppose the output will always be zero since the sum of the basisfunctions is zero as well. Except for the very first value. This (expensive!) shader is called 4 times (for each basisCube). but it produces only black pixels (except with the first cube)... Something tells me this isn't right... Or do I miss something in the shader? I don't know the "tex.Load" function... maybe it does something with negative values? I don't think so, but you'll never know... There is one detail that might be important in the ATI code. When they calculate the BasisCubeMap, they store the values in an array with 'half4' types, instead of float4. The 'half' type is an unsigned short that represents the 16 bit floating format uses in the texture where the basisCube is stored. If you read the half type, you simply get the original value you put into it, but the internal format is different. Nevertheless, I guess the shader will also read the values I originally put into them. Greetings, Rick SH BasisCube code:
float factorial(const int x)
{
	float f = 1.0f;
	for (int i = 2; i <= x; i++)
	{
		f *= i;
	}

	return f;
}

// Evaluate an Associated Legendre Polynomial P(l, m, x) at x
float P(const int l, const int m, const float x)
{
	float pmm = 1.0f;
	if (m > 0)
	{
		float somx2 = sqrtf((1.0f - x) * (1.0f + x));

		float fact = 1.0;
		for (int i = 1; i <= m; i++)
		{
			pmm *= (-fact) * somx2;
			fact += 2.0;
		}
	}
	if (l == m) return pmm;

	float pmmp1 = x * (2.0f * m + 1.0f) * pmm;
	if (l == m + 1) return pmmp1;

	float pll = 0.0;
	for (int ll = m + 2; ll <= l; ++ll)
	{
		pll = ((2.0f * ll - 1.0f) * x * pmmp1 - (ll + m - 1.0f) * pmm) / (ll - m);
		pmm = pmmp1;
		pmmp1 = pll;
	}

	return pll;
}

// Normalization constant
float K(const int l, const int m)
{
	return sqrtf(((2.0f * l + 1.0f) * factorial(l - m)) / (4.0f * PI * factorial(l + m)));
}

// SH coefficient computation
float SH(const int l, const int m, const float theta, const float phi)
{
	const float sqrt2 = 1.4142135623731f;

	if (m == 0)
		return K(l, 0) * P(l, m, cosf(theta));
	else if (m > 0)
		return sqrt2 * K(l, m) * cosf(m * phi) * P(l, m, cosf(theta));
	else
		return sqrt2 * K(l, -m) * sinf(-m * phi) * P(l, -m, cosf(theta));
}


float SH_A(const int l, const int m, const float3 &pos)
{
	float d = dot(pos, pos);
	float len = sqrtf(d);

	float p = atan2f(pos.z, pos.x);
	float t = acosf(pos.y / len);

	return SH(l, m, t, p) * powf(d, -1.5f);
}


//========================================================================================


const int PROBE_SIZE = 8;
const int SH_COEFF_VECTORS = 4;


bool CreateSHTextures()
{
	// tex1 = SH basis function cubemap
	// In the ATI demo its used to create a (RGB16F) 3D texture with 6 slices (==cubeMap)
	// !!! Datatype is 'half'. ATI framework defines the half type as an unsigned short.
	// When giving it a value, it will be converted to a 16 bit floating point format.
	half4 *tex1 = new half4[PROBE_SIZE * PROBE_SIZE * 6];

	int l = 0;
	int m = 0;
	for (int i = 0; i < SH_COEFF_VECTORS; i++)
	{
		// Compute l & m for the next 4 coefficients
		int l4[4], m4[4];
		for (int k = 0; k < 4; k++)
		{
			l4[k] = l;
			m4[k] = m;
			if (m >= l)
			{
				l++;
				m = -l;
			}
			else
			{
				m++;
			}
		}

		uint index1 = 0;
		float3 v;
		float4 sh;

		// Positive & negative X faces
		for (v.x = 1; v.x >= -1; v.x -= 2)
		{
			for (int y = 0; y < PROBE_SIZE; y++)
			{
				for (int z = 0; z < PROBE_SIZE; z++)
				{
					v.y =  1 - 2 * float(y + 0.5f) / PROBE_SIZE;
					v.z = (1 - 2 * float(z + 0.5f) / PROBE_SIZE) * v.x;

					sh.x = SH_A(l4[0], m4[0], v);
					sh.y = SH_A(l4[1], m4[1], v);
					sh.z = SH_A(l4[2], m4[2], v);
					sh.w = SH_A(l4[3], m4[3], v);
					tex1[index1++] = sh;
				}
			}
		}
		// Positive & negative Y faces
		for (v.y = 1; v.y >= -1; v.y -= 2)
		{
			for (int z = 0; z < PROBE_SIZE; z++)
			{
				for (int x = 0; x < PROBE_SIZE; x++)
				{
					v.x =  2 * float(x + 0.5f) / PROBE_SIZE - 1;
					v.z = (2 * float(z + 0.5f) / PROBE_SIZE - 1) * v.y;

					sh.x = SH_A(l4[0], m4[0], v);
					sh.y = SH_A(l4[1], m4[1], v);
					sh.z = SH_A(l4[2], m4[2], v);
					sh.w = SH_A(l4[3], m4[3], v);
					tex1[index1++] = sh;
				}
			}
		}
		// Positive & negative Z faces
		for (v.z = 1; v.z >= -1; v.z -= 2)
		{
			for (int y = 0; y < PROBE_SIZE; y++)
			{
				for (int x = 0; x < PROBE_SIZE; x++)
				{
					v.x = (2 * float(x + 0.5f) / PROBE_SIZE - 1) * v.z;
					v.y =  1 - 2 * float(y + 0.5f) / PROBE_SIZE;
					sh.x = SH_A(l4[0], m4[0], v);
					sh.y = SH_A(l4[1], m4[1], v);
					sh.z = SH_A(l4[2], m4[2], v);
					sh.w = SH_A(l4[3], m4[3], v);
					tex1[index1++] = sh;
				}
			}
		}

		// TEST, SUM
		float t1,t2,t3,t4;

		t1=t2=t3=t4=0;
		for (int i=0; i<PROBE_SIZE * PROBE_SIZE * 6; i++)
		{
			t1 += tex1[i].x;
			t2 += tex1[i].y;
			t3 += tex1[i].z;
			t4 += tex1[i].w;
		}
		// Here the SUM results are always zero, except for the first cubeMap x value
		printf( "SUM %.2f  %.2f  %.2f  %.2f \n", t1,t2,t3,t4 );
	}


	delete tex1;

	return true;
}
...forgot the tag to put this code in a nice box with syntax highlighting

Share this post


Link to post
Share on other sites

This topic is 3458 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this