Skip to main content
GameDev.net gamedev.net
🔒 Locked

DXT5 Normal Map Compression Issues

Started by 67rtyus Mar 24, 2009 at 11:41 AM 2 replies 2.7k views
Original Post
67rtyus
67rtyus
Hi; I have been trying to cover my terrain mesh with a normal map for a long time now. I firstly tried regular tangent space normal maps; but the GeoMipMapping and GeoMorphing algorithms I am using proved it to be useless. (The vertex normals change between LOD levels, causing terrible popping artifacts.) Therefore I decided to use object space normal mapping instead. My terrain mesh consists of 1025x1025 vertices and for the normal mapping to be meaningful, the normal map texture must be huge in my case. For this purpose I am using a 4096x4096 sized texture holding an object space normal in its every texel. This texture covers the whole terrain, providing detail and light information. Of course, I needed to compress this huge texture, in pure ARGB format with its mip-map levels,it eats up to 80 MB of video memory! For DXT normal map compression, I found the following articles: developer.nvidia.com/object/real-time-normal-map-dxt-compression.html developer.nvidia.com/object/bump_map_compression.html ati.amd.com/developer/NormalMapCompression.pdf There are two approaches to the DXT5; in the first one, I put the x component of the vectors into the alpha channel, the y component into the green channel and the z component into the blue channel. In the pixel shader, I renormalize the vectors. The problem here is the banding artifacts, there are 32 different possibilities of directions in a 4x4 DXT block, but still the rough banding transitions between DXT blocks are visible. The second option is to derive the z component of the normal in the pixel shader. Although I use object space normal mapping, it is guaranteed that the z components of the normals will point into the positive direction. The reason is that on the terrain, there will never be a surface which will face towards the underground. I get the z by normal.z=sqrt(1.0 - (normal.x*normal.x +normal.y*normal.y) ). This method works almost fine, the banding is nearly non-existent, but sometimes the term (normal.x*normal.x +normal.y*normal.y) becomes greater than 1.0 and the sqrt operation gets totally messed up. The result is small-paper burn like stains on the terrain. Theoretically, (normal.x*normal.x +normal.y*normal.y) must be always smaller than 1 because of the normalized vectors. I believe this problem stems from the DXT compressor's way of building DXT blocks. (I use the D3DXCreateCubeTextureFromFileEx function of the DirectX runtime to build DXT5 format texture.) What should I do in this situation? I want to use the second method but the (normal.x*normal.x +normal.y*normal.y)>1 issue is a nuisance. How can I get rid of this problem? Thanks in advance
Adam_42
Adam_42
You need to renormalize after doing the square root. As well as fixing any compression artefacts that also handles errors introduced by the bilinear filter.

EDIT: Code should look something like:

float4 nTex = tex2D(texture, uv);float3 nReal = normalize(float3(nTex.g, nTex.a, sqrt(1-(saturate(nTex.g * nTex.g + nTex.a * nTex.a)))));



You could also try this trick to avoid the extra square root.

http://diaryofagraphicsprogrammer.blogspot.com/2009/01/partial-derivative-normal-maps.html
67rtyus
67rtyus
Thank you for your reply.

I immediately tried your code; but unfortunately the "stain" problem didn't go away. Here is a screenshot showing the errors:
http://img7.imageshack.us/img7/5536/errorsz.jpg

This screen shows almost the same area as the first one, this time the pixels in which normal.x * normal.x + normal.y * normal.y > 1.0 are drawn as red:
http://img7.imageshack.us/img7/3884/errors2.jpg

And here is my code in the pixel shader:
        float3 main_texture_color=tex2D(TerrainTexture,input.uv1);	float3 normal=float3(tex2D(NormalMap,input.uv2).ag,0.0f);	normal=(2.0f*normal-1.0f);        float3 normal_renorm = normalize(float3(normal.x, normal.y, sqrt(1-(saturate(normal.x * normal.x + normal.y * normal.y)))));        float diffuse=max(dot(normalizedLightDirection,normal_renorm),0.0f);        output.finalcolor=float4(diffuse*(main_texture_color),1.0f);


What am I doing wrong?
zeux
zeux
float3(normal.x, normal.y, sqrt(saturate(1 - normal.x*normal.x - normal.y*normal.y))) (note the order of operations) should work fine, you don't need any futher renormalization.

Well, as a matter of fact, I've thought 1 - saturate() should work too, but who knows.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.