• entries
    316
  • comments
    485
  • views
    321587

Silhouette Article

Sign in to follow this  
Jason Z

144 views

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!
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now