Cracks and Popping in Terrain

 
Post new topic   Reply to topic    OSMP Forum Index -> Ideas and Such
View previous topic :: View next topic  
Author Message
ajankevics



Joined: 26 Nov 2007
Posts: 6
Location: Massachusetts, US

PostPosted: Sun Dec 02, 2007 1:30 pm    Post subject: Cracks and Popping in Terrain Reply with quote

The terrain editor is pretty cool, however "cracks" appear between level of detail patches and each LOD patch pops as you move around. This is particularly noticeable with non-trivial terrains. I have a candidate rewrite of parts of the file RenderableHeightMap.cs. There are no instructions for submitting code, so I will paste the code below, ugly as that seems. The forum engine seems to disregard formatting, so I can send the file via email if anyone is interested.

I am not sure I understand the rendererpasses concept and how it works with your chunk concept. This submitted version works with a single renderer pass, but I have not tried it with more passes.

--------------- cut here ---------------

//
// Cached average of local normals, so we don't calculate it over
// and over while rendering.
//
public Vector3[,] avenormsperquad;

//=====================================================================
/// <summary>
/// Recalculates the normals for the heightmap in the rectangle defined by
/// (xleft, ytop, xright, ybottom).
/// </summary>
//=====================================================================
void terrain_HeightmapInPlaceEdited(int xleft, int ytop, int xright, int ybottom)
{
//LogFile.WriteLine("RHM data changed " + xleft + " " + ytop + " " + xright + " " + ybottom);
for (int mapx = xleft; mapx <= xright; mapx++)
{
for (int mapy = ytop; mapy <= ybottom; mapy++)
{
double dhdx, dhdy ;

if (mapx == 0)
dhdx = heightmap[mapx + 1, mapy] - heightmap[mapx, mapy];
else if (mapx == width)
dhdx = heightmap[mapx, mapy] - heightmap[mapx - 1, mapy];
else
dhdx = 0.5 * (heightmap[mapx+1, mapy] - heightmap[mapx-1, mapy] ) ;

if (mapy == 0)
dhdy = heightmap[mapx, mapy + 1] - heightmap[mapx, mapy];
else if (mapy == height)
dhdy = heightmap[mapx, mapy] - heightmap[mapx, mapy - 1];
else
dhdy = 0.5 * (heightmap[mapx, mapy+1] - heightmap[mapx,mapy-1] ) ;
//
// The cross product is trivial in this case.
//
Vector3 normal = new Vector3( -dhdx/xscale, -dhdy/yscale, 1.0 ).Normalize();
normalsperquad[mapx, mapy] = normal;
}
}

//
// Now average local normals so we don't need to do it over and over.
//
for ( int mapx = xleft-1 ; mapx <= xright+1 ; mapx++ )
{
if ( mapx <0>= width ) continue ;

for ( int mapy = ytop-1 ; mapy <= ybottom+1 ; mapy++ )
{
if ( mapy <0>= height ) continue ;

double xsum = 0.0, ysum = 0.0, zsum = 0.0 ;

for ( int ix = -1 ; ix <= 1 ; ix++ )
{
if ( mapx + ix <0>= width ) continue ;

for ( int iy = -1 ; iy <= 1 ; iy++ )
{
if ( mapy + iy <0>= height ) continue ;

xsum += normalsperquad[mapx+ix,mapy+iy].x ;
ysum += normalsperquad[mapx+ix,mapy+iy].y ;
zsum += normalsperquad[mapx+ix,mapy+iy].z ;
}
}

avenormsperquad[mapx,mapy] = new Vector3( xsum, ysum, zsum ).Normalize() ;
}
}
}

//=====================================================================
/// <summary>
/// Renders the height map with LOD depending on distance from the
/// camerapos. This version avoids "cracks" and excessive popping.
/// Not sure this works properly with multiple render passes.
/// </summary>
//=====================================================================
public void Render(Vector3 camerapos)
{
// Console.WriteLine("---------- Render New heightmap width {0Very Happy} height {0Very Happy}", width, height);

Gl.glPushMatrix();
FrustrumCulling culling = FrustrumCulling.GetInstance();
GraphicsHelperGl g = new GraphicsHelperGl();
g.SetMaterialColor(new Color(1.0, 1.0, 1.0));
Gl.glDepthFunc(Gl.GL_LEQUAL);
g.EnableBlendSrcAlpha();

foreach( RendererPass rendererpass in rendererpasses )
{

rendererpass.Apply();

//
// The following parameter defines the angle (in radians) that produces acceptable
// popping or discontinuities. Make it too small and the terrain renders
// beautifully in too much detail out to too large a distance, but very
// slowly. Make it too large and the user will see large triangles popping
// nearby, but will be very fast. The user should be able to modify
// this to tune their experience.
//
double a = 0.02 ;

//
// Calculate the radius from the current viewing position inside of
// which we will render with full heightmap resolution.
//
double r = xscale / a ;

int inx0, iny0 ;
int inx1, iny1 ;
int outx0, outx1 ;
int outy0, outy1 ;

inx0 = (int) camerapos.x ;
inx1 = inx0 ;
iny0 = (int) camerapos.y ;
iny1 = iny0 ;

int stride = 1 ;
int bigger = 2*stride ;

//
// Calculate the rectangle on the height map with indices that
// are a multiple of 2*stride that is inside our outer radius.
//
outx0 = (int) ( camerapos.x - stride*r ) ;
outy0 = (int) ( camerapos.y - stride*r ) ;
outx1 = (int) ( camerapos.x + stride*r ) ;
outy1 = (int) ( camerapos.y + stride*r ) ;

outx0 = (outx0/bigger)*bigger ;
outy0 = (outy0/bigger)*bigger ;
outx1 = (outx1/bigger)*bigger ;
outy1 = (outy1/bigger)*bigger ;
if ( outx0 < 0 ) outx0 = 0 ;
if ( outy0 <0>= width-1 ) outx1 = ( (width-1) / bigger ) * bigger ;
if ( outy1 >= height-1 ) outy1 = ( (height-1) / bigger ) * bigger ;

//
// Render the full resolution patch.
//
RenderPatch( outx0, outy0, outx1, outy1, stride ) ;

//
// Now we proceed to render each ring outward at successively lower
// resolution.
// (outx1,outy1)
// +----------------------+
// | |
// | (inx1,iny1) |
// | +--------+ |
// | | | |
// | | | |
// | | | |
// | +--------+ |
// | (inx0,iny0) |
// | |
// +----------------------+
//(outx0,outy0)
for ( int step = 0 ; step < 10 ; step++ )
{
//
// Get ready for the next ring with 2x bigger radius.
//
inx0 = outx0 ;
iny0 = outy0 ;
inx1 = outx1 ;
iny1 = outy1 ;
stride = 2*stride ;
bigger = 2*bigger ;

//
// We might be done.
//
if ( inx0 <= 0 && iny0 <0>= width - bigger && iny1 >= width - stride ) break ;

//
// Calculate the rectangle on the height map with indices that
// are a multiple of bigger that is inside stride*radius.
//
outx0 = (int) ( camerapos.x - stride*r ) ;
outy0 = (int) ( camerapos.y - stride*r ) ;
outx1 = (int) ( camerapos.x + stride*r ) ;
outy1 = (int) ( camerapos.y + stride*r ) ;

outx0 = (outx0 / bigger) * bigger ;
outy0 = (outy0 / bigger) * bigger ;
outx1 = (outx1 / bigger) * bigger ;
outy1 = (outy1 / bigger) * bigger ;
if ( outx0 < 0 ) outx0 = 0 ;
if ( outy0 <0>= width - 1 ) outx1 = ( (width - 1) / bigger ) * bigger ;
if ( outy1 >= height - 1 ) outy1 = ( (height - 1) / bigger ) * bigger ;

//
// Render a strip all around our inner rectangle that glues
// the higher res inside to the lower res outside without
// any holes.
//
RenderPatchGlueLeft( inx0, iny0, iny1, stride ) ;
RenderPatchGlueRight( inx1, iny0, iny1, stride ) ;
RenderPatchGlueBottom( iny0, inx0, inx1, stride ) ;
RenderPatchGlueTop( iny1, inx0, inx1, stride ) ;

//
// Adjust our inner rectangle so that there is a gap of stride indicies
// all the way around it.
//
inx0 = inx0 - stride ;
iny0 = iny0 - stride ;
inx1 = inx1 + stride ;
iny1 = iny1 + stride ;
if ( inx0 < 0) inx0 = 0 ;
if ( iny0 <0>= width - 1 ) inx1 = ( (width - 1) / stride ) * stride ;
if ( iny1 >= height - 1 ) iny1 = ( (height - 1) / stride ) * stride ;

//
// Render 4 patches around what we just rendered at higher resolution
// with a gap of stride pixels all around the inside patch.
//
RenderPatch( outx0, outy0, outx1, iny0, stride ) ; // below
RenderPatch( inx1, iny0, outx1, iny1, stride ) ; // right side
RenderPatch( outx0, iny1, outx1, outy1, stride ) ; // on top
RenderPatch( outx0, iny0, inx0, iny1, stride ) ; // left side
}
}
for (int i = maxtexels - 1; i >= 0; i--)
{
g.ActiveTexture(i);
g.SetTextureScale(1 );
g.DisableTexture2d();
}

Gl.glDepthFunc(Gl.GL_LESS);
Gl.glDisable(Gl.GL_BLEND);
g.EnableModulate();
Gl.glPopMatrix();
}

//=====================================================================
/// <summary>
/// Renders the height map, every quad, no LOD. Inefficient and slow,
/// but it shows you what it looks like without any LOD compromises.
/// </summary>
//=====================================================================
public void RenderAll(Vector3 camerapos)
{
// Console.WriteLine("---------- Render All width {0Very Happy} height {0Very Happy}", width, height );

RenderPatch(0, 0, width-1, height-1, 1);
}

//=====================================================================
/// <summary>
/// Renders a rectangular patch of the height map. It undersamples
/// or skips by 'stride' steps in the heightmap indices.
/// This version just uses GL_QUADS and can be improved by using
/// triangle strips.
/// </summary>
//=====================================================================
void RenderPatch( int ix0, int iy0, int ix1, int iy1, int stride )
{
// Console.WriteLine( "---------- RenderPatch() " + ix0 + " " + iy0 + " " + ix1 + " " + iy1 + " " + stride ) ;

Gl.glBegin( Gl.GL_QUADS ) ;

for ( int ix = ix0 ; ix < ix1 ; ix += stride )
{
for ( int iy = iy0 ; iy < iy1 ; iy += stride )
{
RenderOneVertex( ix, iy ) ;
RenderOneVertex( ix + stride, iy ) ;
RenderOneVertex( ix + stride, iy + stride ) ;
RenderOneVertex( ix, iy + stride ) ;
}
}

Gl.glEnd();
}

//=====================================================================
/// <summary>
/// Renders a strip on the left side of a high resolution inner patch
/// that glues it to a lower resolution outer patch without any holes.
/// +---+ iy1
/// | \ |
/// | +
/// | / |
/// +---+
/// | \ |
/// | +
/// | / |
/// +---+ iy0
/// ix1
/// </summary>
//=====================================================================
void RenderPatchGlueLeft( int ix1, int iy0, int iy1, int stride )
{
int ix0 = ix1 - stride ;

if ( ix0 < 0 ) return ;

int half = stride/2 ;

//
// Render each square with 3 triangles.
//
Gl.glBegin( Gl.GL_TRIANGLES ) ;

for ( int iy = iy0 ; iy <iy1>= width ) return ;

int half = stride/2 ;

//
// Render each square with 3 triangles.
//
Gl.glBegin( Gl.GL_TRIANGLES ) ;

for ( int iy = iy0 ; iy < iy1 ; iy += stride )
{
RenderOneVertex( ix0, iy ) ;
RenderOneVertex( ix1, iy ) ;
RenderOneVertex( ix0, iy + half ) ;

RenderOneVertex( ix1, iy ) ;
RenderOneVertex( ix1, iy + stride ) ;
RenderOneVertex( ix0, iy + half ) ;

RenderOneVertex( ix0, iy + half ) ;
RenderOneVertex( ix1, iy + stride ) ;
RenderOneVertex( ix0, iy + stride ) ;
}

Gl.glEnd() ;
}

//=====================================================================
//=====================================================================
void RenderPatchGlueBottom( int iy1, int ix0, int ix1, int stride )
{
int iy0 = iy1 - stride ;

if ( iy0 < 0 ) return ;

int half = stride/2 ;

//
// Render each square with 3 triangles.
//
Gl.glBegin( Gl.GL_TRIANGLES ) ;

for ( int ix = ix0 ; ix <ix1>= 0 )
{
RenderOneVertex( ix0 - stride, iy0 ) ;
RenderOneVertex( ix0, iy0 ) ;
RenderOneVertex( ix0, iy1 ) ;
RenderOneVertex( ix0 - stride, iy1 ) ;
}

if ( ix1 + stride <width>= height ) return ;

int half = stride/2 ;

//
// Render each square with 3 triangles.
//
Gl.glBegin( Gl.GL_TRIANGLES ) ;

for ( int ix = ix0 ; ix <ix1>= 0 )
{
RenderOneVertex( ix0 - stride, iy0 ) ;
RenderOneVertex( ix0, iy0 ) ;
RenderOneVertex( ix0, iy1 ) ;
RenderOneVertex( ix0 - stride, iy1 ) ;
}

if ( ix1 + stride < width )
{
RenderOneVertex( ix1, iy0 ) ;
RenderOneVertex( ix1+stride, iy0 ) ;
RenderOneVertex( ix1+stride, iy1 ) ;
RenderOneVertex( ix1, iy1 ) ;
}

Gl.glEnd() ;
}

//=====================================================================
/// <summary>
/// Performs the grunt work of specifying one vertex with normal and
/// textures at map index (ixm,iym).
/// </summary>
//=====================================================================
void RenderOneVertex( int ixm, int iym )
{
double x, y, z ;
double nx, ny, nz ;

x = ixm * xscale ;
y = iym * yscale ;
z = heightmap[ixm,iym] ;
nx = avenormsperquad[ixm,iym].x ;
ny = avenormsperquad[ixm,iym].y ;
nz = avenormsperquad[ixm,iym].z ;
//
// GetNormal() averages local normals for a better look, but
// uses up a lot of cycles. The avenormsperquad[]
// array has already calculated the averages.
//
// Vector3 norm = GetNormal( ixm, iym ) ;
// nx = norm.x ;
// ny = norm.y ;
// nz = norm.z ;
Gl.glNormal3d( nx, ny, nz ) ;

Gl.glTexCoord2f( ixm, iym ) ;
Gl.glMultiTexCoord2fARB( Gl.GL_TEXTURE1_ARB, ixm, iym ) ;
Gl.glMultiTexCoord2fARB( Gl.GL_TEXTURE2_ARB, ixm, iym ) ;
Gl.glMultiTexCoord2fARB( Gl.GL_TEXTURE3_ARB, ixm, iym ) ;

Gl.glVertex3d( x, y, z ) ;
}
}
}

--------------- cut here ---------------
_________________
Andy J.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    OSMP Forum Index -> Ideas and Such All times are GMT - 8 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group