Specular Lighting with Texture Shaders (artifacts)

Hi

I try to implement specular lighting with texture shaders.
What i do:
First i create a texture which stores at each texel the value of POW ([(NH)/sqrt(HH)], exponent).
Where N is a normal and H is a half-angle vector.
For NH i use the range 0.0 to 1.0 and for HH it´s the same.

In TU 0 i put my bumpmap. In TU 3 i put that precalculated specular texture. TU 0 has ordinary 2D texcoords, TU 1 to 3 get the half-angle vector as texcoords.
Now i first fetch from the bumpmap my normal. In the second stage i just pass through the half-angle vector. Stage three performs NH (and creates one texcoord for later use). Stage four performs HH as the second texcoord and then fetches the value of the specular texture, which is than added to the framebuffer.

This works quite well. But not perfect. Here some screenshots:
http://www.artifactgames.de/IMAGES/Screenshot39.JPG http://www.artifactgames.de/IMAGES/Screenshot40.JPG http://www.artifactgames.de/IMAGES/Screenshot41.JPG http://www.artifactgames.de/IMAGES/Screenshot42.JPG http://www.artifactgames.de/IMAGES/Screenshot43.JPG http://www.artifactgames.de/IMAGES/Screenshot44.JPG

The first screenshot is the “best case”, and is very rare. Usually it looks as shown in the other screenshots. As you can see the highlight does not have a circular of ellipsoidal (?) shape, but it looks as if it was cut into pieces and those pieces got flipped around or just put into the wrong order. Furthermore, when the viewer moves the highlight rotates and the artifacts get worse/better.
Changing the resolution of the specular texture does not change this situation (in the screenshot i use a resolution of 512*512 and an exponent of 50). Different filter-modes for the texture don´t change it either.

I am really out of ideas, what could be the problem. Maybe one of you knows what it could be, or has done the same thing. Note that this happens on all surfaces, but when the surface has a good bumpmap, the highlight gets heavily distorted and the artifacts are not noticable anymore. Therefore the surface in the screenshots has a flat bumpmap (all normals point straight up).

Thanks in advance,
Jan.

sorry, no help here, but the naming is great… artefactgames and you cry because you have artefacts? sorry… i would like to be able to help…

i think showing the texshader could help others a bit…

I didn´t mean SUCH artifacts!

Well, here you go:

glEnable (GL_TEXTURE_SHADER_NV);

TexMan.ActivateTextureUnit (0);
glTexEnvi (GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_TEXTURE_2D);

TexMan.ActivateTextureUnit (1);
glTexEnvi (GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_PASS_THROUGH_NV);

TexMan.ActivateTextureUnit (2);
glTexEnvi (GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_DOT_PRODUCT_NV);
glTexEnvi (GL_TEXTURE_SHADER_NV, GL_PREVIOUS_TEXTURE_INPUT_NV, GL_TEXTURE0_ARB);
glTexEnvi (GL_TEXTURE_SHADER_NV, GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV, GL_EXPAND_NORMAL_NV);

TexMan.ActivateTextureUnit (3);
glTexEnvi (GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_DOT_PRODUCT_TEXTURE_2D_NV);
glTexEnvi (GL_TEXTURE_SHADER_NV, GL_PREVIOUS_TEXTURE_INPUT_NV, GL_TEXTURE1_ARB);
glTexEnvi (GL_TEXTURE_SHADER_NV, GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV, GL_EXPAND_NORMAL_NV);

//********** REG COMB

glEnable (GL_REGISTER_COMBINERS_NV);
glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 1);

float fColor0[4] = {1.0f, 1.0f, 1.0f, 1.0f};
glCombinerParameterfvNV (GL_CONSTANT_COLOR0_NV, (float*) fColor0);

glCombinerInputNV (GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE3_ARB,
GL_UNSIGNED_IDENTITY_NV, GL_RGB);
glCombinerInputNV (GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_CONSTANT_COLOR0_NV,
GL_UNSIGNED_IDENTITY_NV, GL_RGB);

glCombinerOutputNV (GL_COMBINER0_NV, GL_RGB, GL_SPARE0_NV, GL_DISCARD_NV,
GL_DISCARD_NV, GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);

glFinalCombinerInputNV(GL_VARIABLE_A_NV, GL_SPARE0_NV,
GL_UNSIGNED_IDENTITY_NV, GL_RGB);
glFinalCombinerInputNV(GL_VARIABLE_B_NV, GL_ZERO,
GL_UNSIGNED_INVERT_NV, GL_ALPHA);

glFinalCombinerInputNV(GL_VARIABLE_C_NV, GL_ZERO,
GL_UNSIGNED_IDENTITY_NV, GL_RGB);
glFinalCombinerInputNV(GL_VARIABLE_D_NV, GL_ZERO,
GL_UNSIGNED_IDENTITY_NV, GL_RGB);

glFinalCombinerInputNV(GL_VARIABLE_G_NV, GL_ZERO,
GL_UNSIGNED_INVERT_NV, GL_ALPHA);

And here is, how i create my specular texture:

void CreateSpecularTexture (int *iTexture, int iResolution, int iExponent)
{
float fImage = new float[iResolutioniResolution];

double fS, fT, fValue;
for (int t=0; t<iResolution; t++)
{
	fT = (double) (t+1) / (double) (iResolution);

	for (int s=0; s<iResolution; s++)
	{
		if (fT == 0.0)
			fValue = 0.0;
		else
		{
			fS = (double) (s+1) / (double) (iResolution);

			fValue = fS / (sqrt (fT));
			fValue = POW (fValue, iExponent);
		}

		if (fValue > 1.0f)
			fValue = 1.0f;

		fImage[(t * iResolution) + s] = (float) (fValue);
	}
}

ActivateTextureUnit (0);

GLuint glName;
glGenTextures (1, &glName);

glBindTexture (GL_TEXTURE_2D, glName);

glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

gluBuild2DMipmaps (GL_TEXTURE_2D, GL_LUMINANCE16, iResolution, iResolution, GL_LUMINANCE, GL_FLOAT, fImage);

*iTexture = glName;
}

Jan.

Are there any errors generated?
Just a guess: Try to use an RGBA or HILO texture instead of a Luminance one. (Are Mipmaps necessary?)

kon

P.S.: And delete [] fImage; after texture generation.

Your artifact reminds me something I encountered once myself, maybe this will help:
When calculating contents of LUT texture that is supposed to be sampled with linear filter, then you should evaluate your function at centers of texels - not at corners, as you do.
So, instead of this:

fT = (double) (t+1) / (double) (iResolution);
fS = (double) (s+1) / (double) (iResolution);

try this:

fT = (t + 0.5) / iResolution;
fS = (s + 0.5) / iResolution;

There are no errors generated. Creating the texture works fine. I mapped the texture onto a quad and it looked like a quadratic curve, which seems to be correct.
With an RGBA texture the result is the same. A hilo texture is not necessary, i don´t need signed high-precision values, and the fetched texel does not get further used in the texture-shader.
I get the same result with and without mipmaps.
And i delete the memory afterwards, i just forgot to post that part of the code.

That´s why i am really out of ideas. I tried nearly everything. I also tried it with half-angle vectors, which were not normalized in the VP, but that yields to very strange results.

Any other ideas?
Jan.

@MZ: I tried it, but it still does not change. Thanks though.

I really have the impression, that it has something to do with the half-angle vector (or its interpolation). That´s the only thing that changes when the viewer moves. And the shape of the highlight DOES change (and rotate around itself) when the viewer moves.
However i am absolutely sure, that i calculate it correctly. I use the same VP, i used when i did simpler specular lighting only with reg. combiners.

Here is my VP:

!!ARBvp1.0
OPTION ARB_position_invariant;

ATTRIB inPosition = vertex.position;
ATTRIB inNormal = vertex.normal;
ATTRIB inColor = vertex.color;
ATTRIB inTexCoord0 = vertex.texcoord[0];
ATTRIB inTexCoord1 = vertex.texcoord[1];
ATTRIB inTexCoord2 = vertex.texcoord[2];
ATTRIB inTexCoord3 = vertex.texcoord[3];

PARAM LightPositionPlus = program.env[0];
PARAM OneDivLightRadius = program.env[1];
PARAM LightCenter = program.env[2];
PARAM ViewerPosition = program.env[3];

OUTPUT outColor = result.color;
OUTPUT outTexCoord0 = result.texcoord[0];
OUTPUT outTexCoord1 = result.texcoord[1];
OUTPUT outTexCoord2 = result.texcoord[2];
OUTPUT outTexCoord3 = result.texcoord[3];

TEMP LightToVertex;
SUB LightToVertex, LightCenter, inPosition;

TEMP ViewerToVertex;
SUB ViewerToVertex, ViewerPosition, inPosition;

TEMP HalfAngle;
ADD HalfAngle, LightToVertex, ViewerToVertex;

TEMP TangentHalfAngle;
DP3 TangentHalfAngle.x, HalfAngle, inTexCoord1;
SUB TangentHalfAngle.x, 1.0, TangentHalfAngle.x;
DP3 TangentHalfAngle.y, HalfAngle, inTexCoord2;
DP3 TangentHalfAngle.z, HalfAngle, inNormal;

TEMP HalfAngleNormalized;
DP3 HalfAngleNormalized.w, TangentHalfAngle, TangentHalfAngle;
RSQ HalfAngleNormalized.w, HalfAngleNormalized.w;
MUL HalfAngleNormalized.xyz, HalfAngleNormalized.w, TangentHalfAngle;

TEMP HalfAngleIn0To1Range;
ADD HalfAngleIn0To1Range, HalfAngleNormalized, 1.0;
MUL HalfAngleIn0To1Range, HalfAngleIn0To1Range, 0.5;

MOV outColor, inColor;
MOV outTexCoord0, inTexCoord0;
MOV outTexCoord1, HalfAngleIn0To1Range;
MOV outTexCoord2, HalfAngleNormalized;
MOV outTexCoord3, HalfAngleNormalized;

END

Jan.

Try setting HalfAngleNormalized.w to 1 after the multilpication.

kon

Hm, neither setting it to 1.0 nor setting it to 0.0 has any effect (i tried it for both HalfAngleNormalized and HalfAngleIn0To1Range).

Jan.

What is the TangentHalfAngle good for? Comment that out and just output the computed (normalized) HalfAngle.

kon

What card are you running this on?

TangentHalfAngle is the Half-Angle translated into tangent-space. Since i do bumpmapping in tangent-space, this is absolutely necessary. I tried it as you said, but that messes the whole thing completely up.

I am using a Geforce Ti 4200.

Jan.

Are you sure GF4 supports the Lum16 format?

Yes, i am sure. And i tried it with several other texture-formats, too, it always looks the same.

Jan.

The only reason I ask, is because it looks like a precision problem…

Try this:

Create normalization cube map in HILO format (just drop z).
Sample it in 2nd stage with H texcoords, instead of doing the passthrough.
Instead of N.H / sqrt(H.H) calculate N.H / H.H.
Be sure to do selfshadowing in RC or else specular highlight will appear on backfaces too (because H sampled from HILO cubemap will be look as if it had abs() applied to z its component)

Just a quick post to all the kind people who tried to help me: I “solved” it.
Actually i did nothing wrong, but as someone told me, this is an interpolation issue and nVidia´s texture-shaders are not capable of doing it better.
In fact that means, that it always looks that crappy. The only real way is to use two different approaches: The texture-shader approach for surfaces, which have a very rough bumpmap, and some other approach with a cubemap for surfaces, which are not bumpmapped.
OR to switch to fragment shaders and do it properly per pixel, but since i don´t have such a card, and that is computationally very expensive, at least i won´t do that, yet.

Seems as if we have to wait one or two more years, before specular lighting will look really good.

Thanks,
Jan.