🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Silhouette Article

Published January 06, 2007
Advertisement
My third article was posted to the GameDev.net front page a couple of days ago. It presents a new silhouette rendering technique and you can see it here: Fast Silhouettes. I wrote the article a while back, but now its up for everyone to use. Hopefully it helps a few people out.

I did receive a couple of requests for the pre-processor code that I use in the demo. It is written in my engine code, so it may not be the easiest to understand, but here is the function that I use to generate the new edge mesh from a given instance of a normal triangle mesh:

//--------------------------------------------------------------------------------CTriMesh* CTriMesh::generateFinMesh(){	CTriMesh* pMesh = NULL;	if ( ( getElement( "POSITION" ) != NULL  ) && ( getElement( "NORMAL" ) != NULL  ) )	{		// create storage for the new vertex elements		CVertexElement* pNewPositions = new CVertexElement( 3, faceCount() * 3 * 2 );		pNewPositions->m_Name = "POSITION";		pNewPositions->m_Type = FLOAT3;		pNewPositions->m_Method = DEFAULT;		pNewPositions->m_Usage = POSITION;		pNewPositions->m_UsageIndex = 0;		CVertexElement* pNewNormals = new CVertexElement( 3, faceCount() * 3 * 2 );		pNewNormals->m_Name = "NORMAL";		pNewNormals->m_Type = FLOAT3;		pNewNormals->m_Method = DEFAULT;		pNewNormals->m_Usage = NORMAL;		pNewNormals->m_UsageIndex = 0;		CVertexElement* pNewTangents = new CVertexElement( 3, faceCount() * 3 * 2 );		pNewTangents->m_Name = "TANGENT";		pNewTangents->m_Type = FLOAT3;		pNewTangents->m_Method = DEFAULT;		pNewTangents->m_Usage = TANGENT;		pNewTangents->m_UsageIndex = 0;		CVertexElement* pNewTexcoords = new CVertexElement( 1, faceCount() * 3 * 2 );		pNewTexcoords->m_Name = "TEXCOORD0";		pNewTexcoords->m_Type = FLOAT1;		pNewTexcoords->m_Method = DEFAULT;		pNewTexcoords->m_Usage = TEXCOORD;		pNewTexcoords->m_UsageIndex = 0;		// get pointers to the new data elements		CVector3f* pNewPos = (CVector3f*)((*pNewPositions)[0]);		CVector3f* pNewNrm = (CVector3f*)((*pNewNormals)[0]);		CVector3f* pNewTan = (CVector3f*)((*pNewTangents)[0]);		float* pNewTex = (float*)((*pNewTexcoords)[0]);		// get pointers to the original mesh elements		CVertexElement* pPositions = getElement( "POSITION" );		CVertexElement* pNormals = getElement( "NORMAL" );		CVector3f* pPos = (CVector3f*)((*pPositions)[0]);		CVector3f* pNrm = (CVector3f*)((*pNormals)[0]);		CSegment line;		// create the new mesh that will eventually be returned		pMesh = new CTriMesh();		pMesh->addElement( pNewPositions );		pMesh->addElement( pNewNormals );		pMesh->addElement( pNewTangents );		pMesh->addElement( pNewTexcoords );		for ( int i = 0; i < faceCount(); i++ )		{			pNewPos[6*i+0] = pPos[ m_faces.P1() ];			pNewPos[6*i+1] = pPos[ m_faces.P2() ];			pNewPos[6*i+2] = pPos[ m_faces.P2() ];			pNewPos[6*i+3] = pPos[ m_faces.P3() ];			pNewPos[6*i+4] = pPos[ m_faces.P3() ];			pNewPos[6*i+5] = pPos[ m_faces.P1() ];			pNewNrm[6*i+0] = pNrm[ m_faces.P1() ];			pNewNrm[6*i+1] = pNrm[ m_faces.P2() ];			pNewNrm[6*i+2] = pNrm[ m_faces.P2() ];			pNewNrm[6*i+3] = pNrm[ m_faces.P3() ];			pNewNrm[6*i+4] = pNrm[ m_faces.P3() ];			pNewNrm[6*i+5] = pNrm[ m_faces.P1() ];			pNewTan[6*i+0] = pNrm[ m_faces.P1() ];			pNewTan[6*i+1] = pNrm[ m_faces.P2() ];			pNewTan[6*i+2] = pNrm[ m_faces.P2() ];			pNewTan[6*i+3] = pNrm[ m_faces.P3() ];			pNewTan[6*i+4] = pNrm[ m_faces.P3() ];			pNewTan[6*i+5] = pNrm[ m_faces.P1() ];			line.P1() = 6*i+0;			line.P2() = 6*i+1;			pMesh->addLine( line );			line.P1() = 6*i+2;			line.P2() = 6*i+3;			pMesh->addLine( line );			line.P1() = 6*i+4;			line.P2() = 6*i+5;			pMesh->addLine( line );		}		TArray<unsigned int>* aliases = NULL;		aliases = new TArray<unsigned int>[pMesh->lineCount() * 2];		for ( int i = 0; i < pMesh->lineCount() * 2; i++ )		{			for ( int j = 0; j < pMesh->lineCount() * 2; j++ )			{				if ( i != j )				{					if ( pNewPos == pNewPos[j] )						aliases.add( j );				}			}		}		CTriangle face;		for ( int i = 0; i < pMesh->lineCount(); i++ )		{			for ( int j = 0; j < pMesh->lineCount(); j++ )			{				if ( i != j )				{					unsigned int p1 = pMesh->getLine(j)->P1();					unsigned int p2 = pMesh->getLine(j)->P2();					if ( aliases[2*i+0].contains( p1 ) && aliases[2*i+1].contains( p2 ) )					{						pNewTan[2*i+0] = pNewNrm[p1];						pNewTan[2*i+1] = pNewNrm[p2];						face.P1() = 2*i+0;						face.P2() = p1;						face.P3() = 2*i+1;						pMesh->addFace( face );						face.P1() = 2*i+1;						face.P2() = p1;						face.P3() = p2;						pMesh->addFace( face );					}					else					{						if ( aliases[2*i+0].contains( p2 ) && aliases[2*i+1].contains( p1 ) )							{							pNewTan[2*i+0] = pNewNrm[p2];							pNewTan[2*i+1] = pNewNrm[p1];							face.P1() = 2*i+0;							face.P2() = p1;							face.P3() = 2*i+1;							pMesh->addFace( face );							face.P1() = 2*i+1;							face.P2() = p1;							face.P3() = p2;							pMesh->addFace( face );						}					}				}			}		}		const float fThresRidge = cosf(  1.57f - 0.5f );		// the valley and ridges use the same angle to measure the threshold		//const float fThresValley = cosf( 2.57f );		for ( int i = 0; i < pMesh->lineCount(); i++ )		{			pNewNrm[2*i+0].Normalize();			pNewTan[2*i+0].Normalize();			if ( pNewNrm[2*i+0].dot( pNewTan[2*i+0] ) < fThresRidge )				pNewTex[2*i+0] = 1.0f;			else				pNewTex[2*i+0] = 0.0f;			pNewNrm[2*i+1].Normalize();			pNewTan[2*i+1].Normalize();						if ( pNewNrm[2*i+1].dot( pNewTan[2*i+1] ) < fThresRidge )				pNewTex[2*i+1] = 1.0f;			else				pNewTex[2*i+1] = 0.0f;		}		pMesh->removeAllLines();		delete[] aliases;	}	return( pMesh );}//--------------------------------------------------------------------------------
There is a lot of ways to optimize the code or do it differently, but this works for me. One caveat is that the mesh must be closed or else it won't properly detect the open edges. This could be corrected by simply saying if an edge doesn't have any aliases to automatically generate the edge quad. I'll leave that to the next guy to implement [grin]

If anyone has any feedback I would be happy to hear it!
Previous Entry Progress being made
Next Entry Time for an update
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement