Generate Normal map from heightmap algorithm

Started by
9 comments, last by mrbastard 16 years, 4 months ago
I have searched google and can't find a site that has a tutorial on generating normalmaps from greyscale heightmaps. Would anyone be willing to share there code or a good tutorial site? Thanks
Advertisement
I've never implemented one myself, but I believe that many normalmap generators are based on finding the gradients of the image, ala edge detection. For instance, the Sobel operator is a classic example even though it might not be the most accurate. The implementation would be pretty straightforward from there I would think.
There's a normal map plugin for the paint.net image program that comes with source code that uses the sobel operator.
Search in this site : http://paintdotnet.forumer.com/viewforum.php?f=16.
Well, this needs a little of math :) i like math.

What are you doing when you need to get normal for triangle?
You get two tangent vectors from second point to first point and from third point to first point. Then you do cross product between them and normalize the result - you get a normal vector.

So what will you do, when you need to get normalmap from heightmap?

You'll do the same, but not for trianges, you'll do that for every 2x2 pixels in heightmap - a quad.

I hope this is understandable. Dont know if this is the best solution, but i tried it and it's pretty fast (i do it as pre-pass) and correct. I cloud post a program for you (with source) if you'd like (and don't know how).

My current blog on programming, linux and stuff - http://gameprogrammerdiary.blogspot.com

Thanks for all the replies. What can I do with the TBN matrix I have now for my terrain? How can I encode those vectors into a normalmap in tangent space?

Thanks
Quote:Original post by MARS_999
Thanks for all the replies. What can I do with the TBN matrix I have now for my terrain? How can I encode those vectors into a normalmap in tangent space?

Thanks


normalize(N)
R = N.x
G = N.y
B = N.z
What you usually do is only store the per-texel normal (which is already in tangent space using the appropriate gradient operator), and then generate a single TBN matrix for the entire textured surface at runtime. This matrix is used to transform into tangent space. It doesn't really matter what this TBN matrix is as long as N is perpendicular to the surface, since the traditional lighting equations are based on a Lambertian (N.L) reflectance model that is rotationally invariant around the surface normal.

The typical conversions from vector-space to 8-bit color-space and vice versa are:
[X, Y, Z] → [int(127.5*(X + 1.0)), int(127.5*(Y + 1.0)), int(127.5*(Z + 1.0))]
[R, G, B] → [(R/127.5)-1.0, (G/127.5)-1.0, (B/127.5)-1.0]

We can also use the fact that Z = √(1 - X2 - Y2), and add an alpha channel, to get 16-bit precision for X and Y using 8.8 fixed-point (at a higher computational cost of course):
[X, Y, Z] → [int(127.5*(X + 1.0)), 255*frac(127.5*(X + 1.0)), int(127.5*(Y + 1.0)), 255*frac(127.5*(Y + 1.0))]
[R, G, B, A] → [(R/127.5 + G/32512.5)-1.0), (B/127.5 + A/32512.5)-1.0, √(1 - X*X - Y*Y)]

Where int() returns the integer portion of a floating-point value (basically float-to-int conversion/truncation), and frac() returns the fractional portion of a number (or N - int(N), which results in a floating-point).
Quote:Original post by Zipster
I've never implemented one myself, but I believe that many normalmap generators are based on finding the gradients of the image, ala edge detection. For instance, the Sobel operator is a classic example even though it might not be the most accurate. The implementation would be pretty straightforward from there I would think.
Sobel filter is pretty straight forward to implement and the quality is ok. For reference, here's my SM4 code that implements 3 approaches to fetching a per-pixel normal:
float3 FetchNormalVector( float2 tc, uniform bool readFromTexture, uniform bool useSobelFilter ){	if( readFromTexture )	{		// Use the simple pre-computed look-up		float3 n = texNormalMap.Sample( DefaultSampler, tc ).rgb;		return normalize( n * 2.0f - 1.0f );	}	else	{		if( useSobelFilter )		{   			/*			Coordinates are laid out as follows:						    0,0 | 1,0 | 2,0			    ----+-----+----			    0,1 | 1,1 | 2,1			    ----+-----+----			    0,2 | 1,2 | 2,2			*/						// Compute the necessary offsets:			float2 o00 = tc + float2( -vPixelSize.x, -vPixelSize.y );			float2 o10 = tc + float2(          0.0f, -vPixelSize.y );			float2 o20 = tc + float2(  vPixelSize.x, -vPixelSize.y );			float2 o01 = tc + float2( -vPixelSize.x, 0.0f          );			float2 o21 = tc + float2(  vPixelSize.x, 0.0f          );			float2 o02 = tc + float2( -vPixelSize.x,  vPixelSize.y );			float2 o12 = tc + float2(          0.0f,  vPixelSize.y );			float2 o22 = tc + float2(  vPixelSize.x,  vPixelSize.y );			// Use of the sobel filter requires the eight samples			// surrounding the current pixel:			float h00 = texHeightMap.Sample( DefaultSampler, o00 ).r;			float h10 = texHeightMap.Sample( DefaultSampler, o10 ).r;			float h20 = texHeightMap.Sample( DefaultSampler, o20 ).r;			float h01 = texHeightMap.Sample( DefaultSampler, o01 ).r;			float h21 = texHeightMap.Sample( DefaultSampler, o21 ).r;			float h02 = texHeightMap.Sample( DefaultSampler, o02 ).r;			float h12 = texHeightMap.Sample( DefaultSampler, o12 ).r;			float h22 = texHeightMap.Sample( DefaultSampler, o22 ).r;						// The Sobel X kernel is:			//			// [ 1.0  0.0  -1.0 ]			// [ 2.0  0.0  -2.0 ]			// [ 1.0  0.0  -1.0 ]						float Gx = h00 - h20 + 2.0f * h01 - 2.0f * h21 + h02 - h22;									// The Sobel Y kernel is:			//			// [  1.0    2.0    1.0 ]			// [  0.0    0.0    0.0 ]			// [ -1.0   -2.0   -1.0 ]						float Gy = h00 + 2.0f * h10 + h20 - h02 - 2.0f * h12 - h22;						// Generate the missing Z component - tangent			// space normals are +Z which makes things easier			// The 0.5f leading coefficient can be used to control			// how pronounced the bumps are - less than 1.0 enhances			// and greater than 1.0 smoothes.			float Gz = 0.5f * sqrt( 1.0f - Gx * Gx - Gy * Gy );			// Make sure the returned normal is of unit length			return normalize( float3( 2.0f * Gx, 2.0f * Gy, Gz ) );		}		else		{			// Determine the offsets			float2 o1 = float2( vPixelSize.x, 0.0f         );			float2 o2 = float2( 0.0f,         vPixelSize.y );			// Take three samples to determine two vectors that can be			// use to generate the normal at this pixel			float h0 = texHeightMap.Sample( DefaultSampler, tc ).r;			float h1 = texHeightMap.Sample( DefaultSampler, tc + o1 ).r;			float h2 = texHeightMap.Sample( DefaultSampler, tc + o2 ).r;									float3 v01 = float3( o1, h1 - h0 );			float3 v02 = float3( o2, h2 - h0 );			float3 n = cross( v01, v02 );			// Can be useful to scale the Z component to tweak the			// amount bumps show up, less than 1.0 will make them			// more apparent, greater than 1.0 will smooth them out			n.z *= 0.5f;			return normalize( n );		}	}}
Should be pretty straight-forward which bits are relevant. In general the pre-generated normal map from a file (first branch) was highest quality with Sobel second highest and the third the worst (but still not terrible). Performance was acceptable in all cases, but obviously the TMU usage for Sobel makes it a little slower.

hth
Jack

<hr align="left" width="25%" />
Jack Hoxley <small>[</small><small> Forum FAQ | Revised FAQ | MVP Profile | Developer Journal ]</small>

Offshoot: please, Please, PLEASE generate these from high and low res models if possible..
Holy crap I started a blog - http://unobvious.typepad.com/
void constructNormalMap(CArray2Df *basemap, CArray2D3f *normalmap, float spacing){    if(!basemap || !normalmap) return;    int w,h;    w=basemap->getWidth();    h=basemap->getHeight();    if(w<=0 || h<=0) return;    if(normalmap->getWidth() != w || normalmap->getHeight()!=h)    {        normalmap->init(w,h);    }    int x,z;    for(x=0; x<w; ++x)    {        for(z=0; z<h; ++z)        {            CVec3f n;            n[1]=1.0f;            if(x==0 || z==0 || x==w-1 || z==h-1)            {                n[0]=0;                n[2]=0;            }            else            {                n[0]=(basemap->get(x-1,z) - basemap->get(x+1,z)) / (spacing/4.0f);                n[2]=(basemap->get(x,z-1) - basemap->get(x,z+1)) / (spacing/4.0f);                n.normalize();            }            normalmap->set(x,z,n);        }    }}


This was based on the algorithm I found in David Eberly's 3D engine book. Involves setting the 'up' component (Y axis, or n[2] in this code) to 1.0, then taking the difference in value of adjacent elements in the X direction (X-1,Z)-(X+1,Z) and in the Z direction (X,Z-1)-(X,Z+1) to calculate the X and Z components respectively, then normalizing to unit length.

This topic is closed to new replies.

Advertisement