The compression of texture S3TC is a destructive compression (with losses) rather like the jpg format, destructive in the sense
that a part of the information contained by the non compressed texture is definitely lost after compression. For textures containing
color data (color-map or classical image) this loss of information is not a problem, but for textures containing normal vectors
(normal-map) this could be more problematic. We are going to see the problems caused by texture compression with normal maps and
some techniques to resolve these problems.
Figure 1 shows the rendering of the accompanying project (available at the bottom of the page).
We will use the Demoniak3D platform in order to test the various techniques to render
the normal-maps.
Fig. 1 - Overall viewThe different project files use the following vertex/pixel shader, written in GLSL (OpenGL Shading Language), to create the bump
mapping:
[Vertex_Shader]
varying vec3 lightVec;
varying vec3 viewVec;
varying vec2 texCoord;
attribute vec3 tangent;
void main(void)
{
gl_Position = ftransform();
texCoord = gl_MultiTexCoord0.xy;
vec3 n = normalize(gl_NormalMatrix * gl_Normal);
vec3 t = normalize(gl_NormalMatrix * tangent);
vec3 b = cross(n, t);
vec3 v;
vec3 vVertex = vec3(gl_ModelViewMatrix * gl_Vertex);
vec3 lVec = gl_LightSource[0].position.xyz - vVertex;
v.x = dot(lVec, t);
v.y = dot(lVec, b);
v.z = dot(lVec, n);
lightVec = v;
vec3 vVec = -vVertex;
v.x = dot(vVec, t);
v.y = dot(vVec, b);
v.z = dot(vVec, n);
viewVec = v;
}
[Pixel_Shader]
varying vec3 lightVec;
varying vec3 viewVec;
varying vec2 texCoord;
uniform sampler2D normalMap;
void main (void)
{
vec3 lVec = normalize(lightVec);
vec3 vVec = normalize(viewVec);
vec3 bump = texture2D(normalMap, texCoord).xyz * 2.0 - 1.0;
float diffuse = max( dot(lVec, bump), 0.0 );
float specular = pow(clamp(dot(reflect(-lVec, bump),vVec),
0.0, 1.0),
gl_FrontMaterial.shininess );
vec4 vAmbient = gl_LightSource[0].ambient *
gl_FrontMaterial.ambient;
vec4 vDiffuse = gl_LightSource[0].diffuse *
gl_FrontMaterial.diffuse *
diffuse;
vec4 vSpecular = gl_LightSource[0].specular *
gl_FrontMaterial.specular *
specular;
gl_FragColor = vAmbient + (vDiffuse + vSpecular);
}
This shader will serve as the reference for the demo’s different XML files. The variations will be found in the pixel shader along the following line:
vec3 bump = texture2D(normalMap, texCoord).xyz * 2.0 - 1.0;
Now let us make a closer analysis. To do this, load into Demoniak3D the xml
bump_map_uncompressed_non_normalized.xml file. The rendering should be the same as figure 2:
Fig. 2 - Uncompressed and non-normalized bump mapThen load the xml bump_map_compressed_non_normalized.xml file. This code loads a compressed normal-map (bulge_DXT5.o3tc) with the O3TC format.
For information, do not forget that the 03TC format possesses practically the same characteristics as the DDS format.
It supports the DXT1 and DXT5 compression and the mipmaps, in short, the essential elements for texture compression.
The O3TC format is created with the DXTViewer tool. See part 4 for more information on
O3TC format. A normal-map in DDS format will give the same result.
The shader used is always the same.
Fig. 3 - Compressed bump map (DXT5)We clearly see the damage due to compression. The specular highlights are very ugly, a sort of
grid pattern has appeared. The grid pattern is due to the DXT algorithm which uses internally blocks of 4 x 4 pixels.
Picture 4 clearly shows such a block. The large rectangle marks the block and the small rectangle marks a pixel.
This picture was obtained by suppressing the bilinear filtering by default: it suffices to insert the filtering_mode="NONE" attribute
in the texture element.
Fig. 4 - Block of 4 x 4 pixels used in the DXT1 and DXT5 algosIt is not really very visible in picture 4, but perform the test by disabling the texture filter and you will
easily see the blocks.