ajankevics
Joined: 26 Nov 2007 Posts: 6 Location: Massachusetts, US
|
Posted: Sun Dec 02, 2007 1:30 pm Post subject: Cracks and Popping in Terrain |
|
|
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 {0 } height {0 }", 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 {0 } height {0 }", 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. |
|