Terrain: Fixing the holes caused by dropping the LOD

Started by
9 comments, last by zppz 18 years, 8 months ago
I have an array of floats (512x512) which represent the heightmap. What I do is then pass the coordinates (0,0) and (512,512) to my function that renders the terrain's "land". If all of the quad is outside the view bail out. Otherwise, check to see if it is lower quality (in terms of size - so 512x512 is low quality!) than the current allowable LOD (calculated based on distance from the player to the centre of the quad). If it isn't, just draw a quad across those four points, if it is then split it into four smaller quads and call the function on each of those four smaller ones. This works pretty well and pretty fast apart from one thing - when a set of larger quads are drawn next to a bunch of smaller split quads there is a "hole" where they don't line up properly. (The central edge of the larger quad is not at the correct height). Is there an easy way around this? The method seemed to be the easiest and clearest to me when I wrote it, but I can't see a simple way to fix those holes!

[Website] [+++ Divide By Cucumber Error. Please Reinstall Universe And Reboot +++]

Advertisement
I don't think there is a very easy way around it. One of the most used solutions is adding 'skirts' around that lod-level. That should give you some ideas if you google for it.
see
http://vterrain.org/LOD/Papers/index.html
or
http://vterrain.org/LOD/Implementations/index.html
If I understand you correctly the problem is called T-junction problem.

Lower LOD
------|\   || \  ||  \ ||   \|------


Higher LOD
---------|\  |\  || \ | \ ||  \|  \|---------|\  |\  || \ | \ ||  \|  \|---------


One soultion is to make this split instead

Higher LOD #2
--------|\    /|| \  / ||  \/  ||  /\  || /  \ ||/    \|--------
only pushes the basic problem one LOD up:

next LOD:

-----
|\|/|
|---|
|/|\|
-----
^will cause cracks!
Quote:Original post by hooomer
see
http://vterrain.org/LOD/Papers/index.html
or
http://vterrain.org/LOD/Implementations/index.html
Those look useful, thanks. [smile]

[Website] [+++ Divide By Cucumber Error. Please Reinstall Universe And Reboot +++]

there are essentially 2 approaches to solve cracks: 1) add skirts to fill cracks, 2) use different indices for different LODs / neighbours.

try this for skirts and this for a clever index trick.
[size="1"]
I tackled this problem in my engine. I found a not so simple solution. I'll paste in some code, if you can follow it, and please don't pick out the faults it was an early morning hack:


This class basically generate a triangle fan for me, a lot more hacky and specific case than it should be, but it works for the mean time:

class cTriangleFan{private:public:	unsigned int m_CenterX;	unsigned int m_CenterZ;	unsigned int m_pSides[4][16];	int m_NumOnSide[4];		cTriangleFan()	{		for (int i=0; i<4; ++i)			m_NumOnSide = 0;	}		eGenericError Initilise(int l_NumOnSide[4], int l_CenterX, int l_CenterZ)	{		for (int i=0; i<4; ++i)		{			m_NumOnSide = l_NumOnSide;		}		m_CenterX = l_CenterX;		m_CenterZ = l_CenterZ;		return SUCCESS;	}		unsigned int ReturnIndex(int i, int j, int pitch)	{		if (i >= pitch)			i = pitch-1;		if (j >= pitch)			j = pitch-1;		if (i < 0)			i = 0;		if (j < 0)			j = 0;		return (i * pitch) + j;	}	void GenerateTriangles(unsigned int* l_pIndices, int& l_Index, int l_ArrayPitch, int l_LODFactor)	{		// Loop through sides		for (int i=0; i<4; ++i)		{			int l_Iter = (l_LODFactor*2) / (m_NumOnSide - 1);			// Loop through points - 1			for (int j=0; j<m_NumOnSide-1; ++j)			{				switch(i)				{				case 0:					l_pIndices[l_Index] = ReturnIndex(m_CenterX, m_CenterZ, l_ArrayPitch); ++l_Index;					l_pIndices[l_Index] = ReturnIndex(m_CenterX+(-l_LODFactor+((j+0)*l_Iter)), m_CenterZ-l_LODFactor, l_ArrayPitch); ++l_Index;					//l_pIndices[l_Index] = ReturnIndex(m_CenterX, m_CenterZ, l_ArrayPitch); ++l_Index;					l_pIndices[l_Index] = ReturnIndex(m_CenterX+(-l_LODFactor+((j+1)*l_Iter)), m_CenterZ-l_LODFactor, l_ArrayPitch); ++l_Index;					//l_pIndices[l_Index] = ReturnIndex(m_CenterX+(-l_LODFactor+((j+0)*l_Iter)), m_CenterZ-l_LODFactor, l_ArrayPitch); ++l_Index;					//l_pIndices[l_Index] = ReturnIndex(m_CenterX+(-l_LODFactor+((j+1)*l_Iter)), m_CenterZ-l_LODFactor, l_ArrayPitch); ++l_Index;					break;				case 1:					l_pIndices[l_Index] = ReturnIndex(m_CenterX, m_CenterZ, l_ArrayPitch); ++l_Index;					l_pIndices[l_Index] = ReturnIndex(m_CenterX+(-l_LODFactor+((j+1)*l_Iter)), m_CenterZ+l_LODFactor, l_ArrayPitch); ++l_Index;					//l_pIndices[l_Index] = ReturnIndex(m_CenterX, m_CenterZ, l_ArrayPitch); ++l_Index;					l_pIndices[l_Index] = ReturnIndex(m_CenterX+(-l_LODFactor+((j+0)*l_Iter)), m_CenterZ+l_LODFactor, l_ArrayPitch); ++l_Index;					//l_pIndices[l_Index] = ReturnIndex(m_CenterX+(-l_LODFactor+((j+1)*l_Iter)), m_CenterZ+l_LODFactor, l_ArrayPitch); ++l_Index;					//l_pIndices[l_Index] = ReturnIndex(m_CenterX+(-l_LODFactor+((j+0)*l_Iter)), m_CenterZ+l_LODFactor, l_ArrayPitch); ++l_Index;					break;				case 2:					l_pIndices[l_Index] = ReturnIndex(m_CenterX, m_CenterZ, l_ArrayPitch); ++l_Index;					l_pIndices[l_Index] = ReturnIndex(m_CenterX-l_LODFactor, m_CenterZ+(-l_LODFactor+((j+1)*l_Iter)), l_ArrayPitch); ++l_Index;					//l_pIndices[l_Index] = ReturnIndex(m_CenterX, m_CenterZ, l_ArrayPitch); ++l_Index;					l_pIndices[l_Index] = ReturnIndex(m_CenterX-l_LODFactor, m_CenterZ+(-l_LODFactor+((j+0)*l_Iter)), l_ArrayPitch); ++l_Index;					//l_pIndices[l_Index] = ReturnIndex(m_CenterX-l_LODFactor, m_CenterZ+(-l_LODFactor+((j+0)*l_Iter)), l_ArrayPitch); ++l_Index;					//l_pIndices[l_Index] = ReturnIndex(m_CenterX-l_LODFactor, m_CenterZ+(-l_LODFactor+((j+1)*l_Iter)), l_ArrayPitch); ++l_Index;					break;				case 3:					l_pIndices[l_Index] = ReturnIndex(m_CenterX, m_CenterZ, l_ArrayPitch); ++l_Index;					l_pIndices[l_Index] = ReturnIndex(m_CenterX+l_LODFactor, m_CenterZ+(-l_LODFactor+((j+0)*l_Iter)), l_ArrayPitch); ++l_Index;					//l_pIndices[l_Index] = ReturnIndex(m_CenterX, m_CenterZ, l_ArrayPitch); ++l_Index;					l_pIndices[l_Index] = ReturnIndex(m_CenterX+l_LODFactor, m_CenterZ+(-l_LODFactor+((j+1)*l_Iter)), l_ArrayPitch); ++l_Index;					//l_pIndices[l_Index] = ReturnIndex(m_CenterX+l_LODFactor, m_CenterZ+(-l_LODFactor+((j+1)*l_Iter)), l_ArrayPitch); ++l_Index;					//l_pIndices[l_Index] = ReturnIndex(m_CenterX+l_LODFactor, m_CenterZ+(-l_LODFactor+((j+0)*l_Iter)), l_ArrayPitch); ++l_Index;					break;				};			}		}	}};


This is the code my terrain patch class uses to generate a patch where the edges match nicely with adjacent patches:

void cTerrainFragment::Generate(){	int index=0;	m_ArrayPitch = 512;	int l_NewSideLOD;		if (m_LODFactor == m_LODPrev)	{		// Ok, we havent changed, have our neighbours?		bool changed=false;		for (int i=0;i<4;++i)		{			// Has it changed?			if (m_pSides!=NULL)			{				l_NewSideLOD = m_pSides->m_LODFactor;				if (m_SideLOD != l_NewSideLOD)				{					m_SideLOD = l_NewSideLOD;					changed = true;				}			}		}		if (!changed)			return;	}	m_LODPrev = m_LODFactor;	int l_SizeX = ((m_MaxX - m_MinX) / m_LODFactor) / 2;	int l_SizeZ = ((m_MaxZ - m_MinZ) / m_LODFactor) / 2;	int l_NumOnSide[4];	cTriangleFan l_Fan;	for (int i=0; i<l_SizeX; ++i)	{		for (int j=0; j<l_SizeZ; ++j)		{			// Get LOD for each side			for (int k=0; k<4; ++k)			{				l_NumOnSide[k] = (m_LODFactor / (GetSideLOD(k, i ,j, l_SizeX, l_SizeZ)) * 2) + 1;			}			// Pass in some info			l_Fan.Initilise(l_NumOnSide, m_MinX + m_LODFactor + (i*m_LODFactor*2), m_MinZ + m_LODFactor + (j*m_LODFactor*2));			// Add individual triangles to index list			l_Fan.GenerateTriangles(m_pIndices, index, m_ArrayPitch, m_LODFactor);		}	}	m_NumIndices = index;}


Actually come to think of it, pick my code to bits its a hacky mess which was just for testing purposes for now.
Adventures of a Pro & Hobby Games Programmer - http://neilo-gd.blogspot.com/Twitter - http://twitter.com/neilogd
I'm currently just wasting some time in a computer lab, so I don't really have much access to resources or links or anything, but I did find logs for an IRC lecture I held awhile back about terrain engines.

http://andyc.org/lecture/viewlog.php?log=Future%20of%20Terrain%20Programming

I'm near-positive that I discussed crack-fixing techniques at some point in the lecture. There should be some decent information (or, perhaps, even links) but, you know, it's been a couple years. My memory may be fading.
What I do is to shift around the indices of the higher detail patches so that they don't create cracks. I'm a little vague on exactly how your system works though, so I'm not sure if this is appropriate or not...if you want more details visit me in IRC.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.

This topic is closed to new replies.

Advertisement