Not logged in, Join Here! or Log In Below:  
News Articles Search    

 Home / 3D Theory & Graphics / Per pixel specular lighting using HLSL 1.4 Account Manager
Archive Notice: This thread is old and no longer active. It is here for reference purposes. This thread was created on an older version of the flipcode forums, before the site closed in 2005. Please keep that in mind as you view this thread, as many of the topics and opinions may be outdated.

March 28, 2005, 07:55 AM

I'm trying to do per pixel specular lighting using HLSL ps 1.4.

When using ps 2.0 I can use "normalize" and "pow", but they are not available on ps 1.4. I can do both of them using texture lookups instead, but the problem is thatthis introduces 2 dependent texture reads, while there's only support for one... Anyone who have tried to do this and found a solution?

Here's my shader, which doesn't work with 1.4 because of the dependent texture reads:

  2. float4 PS_DirectionalLight(float2 uv : TEXCOORD0,float3 viewDirection:TEXCOORD1,float3 normal:TEXCOORD2):COLOR
  3.         {      
  4.         float diffuse=saturate(dot(normal,light_Direction));
  5.         float3 reflection=(2 * diffuse * normal - light_Direction);
  6.         reflection=(texCUBE(sampler_NormalizationMap,reflection)-0.5f)*2;    
  7.         float specular=saturate(dot(reflection, viewDirection));
  8.         specular=tex2D(sampler_PowerMap,float2(specular,material_SpecularPower*powerMapScale));
  9.         float4 result=light_Color*material_Diffuse*diffuse + light_Color*material_Specular*specular;
  10.         return float4(result.rgb,1);
  11.         }


March 28, 2005, 09:40 AM

Also, I tried to do Schlick specular instead of Phong, by basically doing:

  2. float RdotV=saturate(dot(reflection, viewDirection));
  3. float specular=RdotV/(material_SpecularPower - (material_SpecularPower*RdotV) + RdotV);

And again, this works with shader 2.0, but I can't do a divide in 1.4. I COULD do it using a texture lookup, but then I'd be back to 2 dependent texture lookups... Argh!


March 28, 2005, 03:51 PM

You can use a quick arithmetic hack (basically one Newton-Raphson iteration) to normalize the vectors... This gives a better result than a cubemap anyway.

  1. half3 quicknrm( in half3 vec )
  2. {
  3.         half lenSq = dot(vec, vec);
  5.         return (vec*(1-lenSq) + vec*2)/2;
  6. }

For the pow() I would recommend a texture, since it is flexible, fast, and gives very accurate results.
However, you could also use an arithmetic hack here... basically some scale-and-bias functions, combined with clamping, which will approximate the curve of the actual pow() function... as an example, here are two classics for pow(16):

  1. pow(x, 16) -> (x^2-0.75)*4
  2. pow(x, 16) -> (2*(x^2-0.5))^4

Alternatively you could also combine the pow and normalization in a single 2d-texture lookup... Basically you'll index it something like this: texture( dot(N,H), dot(N,N) );

And there are probably plenty more tricks :)


March 28, 2005, 05:48 PM

Actually that second param should have been dot(H,H) I believe... :)


April 01, 2005, 05:02 AM

Thanks for that. However, the quicknrm thing doesn't work, it doesn't give a result that is similar to normalize or using a normalization cube map. If anyone can spot an error in that calculation, or know of a similar formula, I'd be happy to hear about it.

For now, I'm just compiling different shaders for different values of the exponents, and simply multiplies it by itself the correct number of times... but this is not ideal, I'd prefer to do it in all in the one shader, but I'm happy to use approximations of the values.


April 01, 2005, 09:31 AM

That quicknrm should work, I've copy-pasted it from the shaders I actually use in my everyday code... I took the original code from an NVIDIA paper.
Be aware though that it doesn't work for very unnormalized normals.
The idea is that you do per-vertex normalization, and then use this to counter the per-pixel denormalization that occurs during interpolation.
When you use it like that, it will give smoother results than a cubemap.
I did some testing a while ago... see the screenshots here:

The ps2.0 screenshot does everything 'correctly', so a true pow() and nrm() instruction.
The botton ps1.4 screenshot uses this normalization and a texture to perform pow(). As you can see, there is no real visible difference in image quality, both have perfectly smooth highlights.
The ps1.1 screenshot uses a cubemap to renormalize instead, which is not smooth, as you can see. Which is why I prefer the quicknrm on ps1.4 if I can spare the two instructions it takes.


April 05, 2005, 10:15 AM

Ok, that explains things... see, the part I want to replace is

  2. float3 reflection=(2 * diffuse * normal - light_Direction);
  3. reflection=normalize(reflection);    

There's no guarantee that the calculated reflection vector is normalized... so that explains why the quicknrm fails in this case. When using it for other things it works fine.


April 05, 2005, 02:39 PM

If you are using the reflection vector to do a cubemap lookup, then it doesn't have to be normalized... Else, well yes, you have to use a robust way to normalize it. Best I can think of is a cubemap in that case.
Ofcourse you could perform QuickNrm() a few times, it should converge to the normalized vector... but it will eat up instruction slots quickly.


April 06, 2005, 03:11 AM

Well, I can't really use the cubemap lookup, because I use a lookup for the pow() too... and that would mean two dependent texture reads, which is not supported on 1.4.

But that's alright, I'll just use cube map for normalizing the reflection vector, and have separate shaders for different values of specular power, and determine at runtime which one to use.

Thanks for all the help


April 06, 2005, 06:14 AM

If this is for per-pixel phong lighting or something similar, you might want to consider a slightly different model, like blinn... where you can interpolate eg a 'halfangle' vector rather than calcing the reflection vector per-pixel.
Then you can use the QuickNrm() again.


April 06, 2005, 03:10 PM

Actually, computing the reflection vector per vertex and linearly interpolating it should give better results than with a half-angle vector.

Linearly interpolating the reflection vector (= 2*N*(L dot N) - L) and then normalizing per-pixel is geometrically correct, whereas linearly interpolating the half-angle vector will give incorrect results if L and N have very different magnitudes.


April 06, 2005, 03:27 PM

Hmm.. I also have a normal map that I sample at each pixel.. would there be a way to use that to adjust such an interpolated reflection vector? Or am I stuck doing the reflection calculation per pixel?


April 06, 2005, 04:13 PM

Well, I assume per-vertex normalization anyway, so all vertices will be unit-length before interpolating. So there is no difference in magnitude.


April 06, 2005, 05:09 PM

Oh...sorry, I forgot about the normal map. Yeah, if you have a normal map there is pretty much no way to avoid computing the reflection vector per-pixel.

This thread contains 14 messages.
Hosting by Solid Eight Studios, maker of PhotoTangler Collage Maker.