Terrain normals interpolation. Is this code correct?

Started by
7 comments, last by _Sergey_ 17 years, 1 month ago
I'm trying to do a bilinear normals interpolation on a heightmap and wrote this sort of code:

vec3 HeightMap::GetNormal( float X, float Y ) const
{
   int I;
   int J;
   float FracI;
   float FracJ;

   WorldToData( X, Y, I, J, FracI, FracJ ); // transforms world X, Y into heightmap integer coords I, J and their fractional parts

   vec3 Normal0 = GetDataNormal( I,   J   );
   vec3 Normal1 = GetDataNormal( I,   J+1 );
   vec3 Normal2 = GetDataNormal( I+1, J   );
   vec3 Normal3 = GetDataNormal( I+1, J+1 );

   if ( FracI + FracJ >= 1 ) Normal0 = Normal3;

   // bilinear interpolation
   vec3 Edge1 = Normal0 + (Normal1-Normal0) * FracJ;
   vec3 Hypot = Normal2 + (Normal1-Normal2) * FracJ;

   vec3 Normal = Edge1 + (Hypot - Edge1) * FracI;

   return Normal.GetNormalized();
}
Helper function:

vec3 HeightMap::GetDataNormal( int I, int J ) const
{
   // find heights
   vec3 H0 = DataToWorld( I,   J   );  // returns X,Y,Z world coordinates of the point
   vec3 H1 = DataToWorld( I,   J+1 ); // for simplicity consider this I, J, Height(I,J)
   vec3 H2 = DataToWorld( I+1, J   ); // but actualy some scaling is done

   vec3 Edge1 = H0-H2;
   vec3 Edge2 = H0-H1;

   vec3 Normal = ( Edge2.Cross( Edge1 ) ).GetNormalized();

   if ( Normal.Z < 0 ) Normal *= -1.0f;

   return Normal;
}
The normals for integer values of X and Y are ok, but as soon as i try to supply float coordinates to GetNormal() the result is wrong. :( What could be wrong with this code?
--Sergey K. Linderdaum Project Coordinator http://www.linderdaum.comsupport@linderdaum.com
Advertisement
This has always worked for me:

Normal.x = Height[x-1][y] - Height[x+1][y]Normal.y = Height[x][y-1] - Height[x][y+1]Normal.z = Distance_Between_Grid_Samples * 2.0Normalise(&Normal)

But normals generated this way will not rotate continuously to follow the landscape, will they?
--Sergey K. Linderdaum Project Coordinator http://www.linderdaum.comsupport@linderdaum.com
heres a link to an algorithm for smooth terrain normals

http://www.devmaster.net/forums/showthread.php?t=1963

Again, that example works with integer grid and doesn't do any interpolation for floating point coordinates. :(
--Sergey K. Linderdaum Project Coordinator http://www.linderdaum.comsupport@linderdaum.com
it seems to me ur makin this abit mroe complicated than it is... you shouldnt be interpolating the normals.. instead simply interpolate the heightvalues and use those to calculate the normals...

EDIT:: not that its wrong to interpolate the normals... but u get better quality with cosine interpolation of the heightfield

		float Interpolate(float a, float b, float x)		{			float f  = ( 1.0f - (float)cos ( x * PI ) ) * 0.5f;			return ( a * ( 1.0f - f ) + b * f );		}		float GetInterpolatedHeight(float x, float z)		{			int ix1 = (int) x;			float fx1 = x - ix1;			int iz1 = (int) z;			float fz1 = z - iz1;			float v1 = GetHeight(ix1, iz1);			float v2 = GetHeight(ix1+1, iz1);			float v3 = GetHeight(ix1, iz1+1);			float v4 = GetHeight(ix1+1, iz1+1);			float v12 = Interpolate(v1,v2,fx1);			float v34 = Interpolate(v2,v3,fx1);			float v1234 = Interpolate(v12,v34,fz1);//(1.0f-fz1)*v12 + fz1*v34;			return v1234;				}


[Edited by - Dragon_Strike on March 9, 2007 3:59:58 PM]
Floating point coordinates should easily transform properly. The problem might be with whether you are getting the lowest integer value or the highest integer value:-

eg:-

float x1 = 0.1f;
float y1 = 0.1f;

float x2 = 1.1f;
float y2 = 1.1f;

now:

floor(x1) == 0.0f;
and
ceil(x1) == 1.0f;

while
floor( x2 ) == 1.0f == ceil( x1 );

additionally, you might have a different range than the above numbers which are 1.0f apart.

x1 = 0.1f;
x2 = 1.8f;

then you need to compute the distance between the min and the max values like:-

xdist = xmax - xmin;

and then divide by the number of samples

xdiff = xdist / num_samples;

for example:-
xmin = 0.1;
xmax = 217.7;

xdist = 217.7 - 0.1 = 217.6;

for 128 samples :-

xdiff = xdist / num_samples = 217.6 / 128 = 1.7

then finally the adjusted x value is

x1_adj = ( x1 - xmin ) / xdiff = ( 0.1 - 0.1 ) / 1.7 = 0.0f;

and for x2

x2_adj = ( x2 - xmin ) / xdiff = ( 1.8 - 0.1 ) / 1.7 = 1.0f;

and for x3 which would be 3.5

x3_adj = ( x3 - xmin ) / xdiff = ( 3.5 - 0.1 ) / 1.7 = 2.0f;

then by mathematical induction it is easy to show that:-

xn_adj = ( xn - xmin )/ xdiff;



I hope this works or at least helps somehow!


edit: corrected a dodgy bracket
apparently the samplers in pixel shaders use bilinear filtering all the time without too many problems.

what is the difference between a bilinear filtered normal map and a bilinear filtered height map that has had normals recalculated?

I'd be interested to see the result :)

Quote:Original post by cyber_wiz_2007

what is the difference between a bilinear filtered normal map and a bilinear filtered height map that has had normals recalculated?


In my case normals are used not for rendering, but for the physics simulation of vehicles on a landscape.
--Sergey K. Linderdaum Project Coordinator http://www.linderdaum.comsupport@linderdaum.com

This topic is closed to new replies.

Advertisement