Normal mapping / generating tangents for skinned m

Published March 12, 2007
Advertisement
*stretches out* Ok this is gonna be a long one....

Hopefully this will be the final graphical effect I'm going to add into the game, and it looks like it'll be a little while before I'm happy with it.

I've tried to add this into the game previously, each time getting closer, but utlimatly getting caught up on something.

First off, I'm forced to use a ooooold version of the DXSDK like 4 years old because any new versions fail to load almost all of my .X files . I've spent days trying to get that issue resolved in past, but I'm just going to tough it out for this game. Unfortunatly this also means I'm limited to PS2.0.<br><br>The other issue is that D3DXComputeTangent() fails for all of my skinned meshes, and I've spent days previously trying to work out that issue. Because the verison of the SDK I use doesn't even have the D3DXComputeTangentFrameEx() function.<br><br>So I decided I have to generate the tangents by hand. After a few days of coding / debugging issues I FINALLY have this working in my engine.<br><br>Google searchs for "skinned mesh tangents" returns nearly nothing, and I couldn't find any code/examples showing how to generate the tangents by hand for a skinned D3DXMESH. I would have been happy with, any functional code that just uses the D3DXTangent() functions with skinned meshes.<br><br>Well here you go google - This code should be pluggable into the SkinnedMesh class in the DXSDK, at least the version I have. No promises about any part of this . I just hope somebody will be helped out someday by this code…I couldn't find sh*t when I was googling around for code/etc. Also, the debug statments are obviously specifc to my game, so you gotta modify/take those out.<br><br><!–STARTSCRIPT–><!–source lang="cpp"–><div class="source"><pre><br><span class="cpp-keyword">void</span> CSkinnedMesh::GenerateTangents(LPD3DXFRAME pFrameBase)<br>{<br> <span class="cpp-comment">//DWORD NewFVF = (pMeshContainer->MeshData.pMesh->GetFVF() & D3DFVF_POSITION_MASK) | D3DFVF_NORMAL | D3DFVF_TEX2 | D3DFVF_LASTBETA_UBYTE4 | D3DFVF_TEXCOORDSIZE2(0) | D3DFVF_TEXCOORDSIZE4(1);</span><br> D3DXFRAME_DERIVED *pFrame = (D3DXFRAME_DERIVED*)pFrameBase;<br> D3DXMESHCONTAINER_DERIVED *pMeshContainer;<br> pMeshContainer = (D3DXMESHCONTAINER_DERIVED *)pFrame->pMeshContainer;<br> <br> <span class="cpp-keyword">while</span>(pMeshContainer != NULL)<br> {<br> <span class="cpp-keyword">int</span> NumVerts = pMeshContainer->MeshData.pMesh->GetNumVertices();<br> <span class="cpp-keyword">int</span> NumFaces = pMeshContainer->MeshData.pMesh->GetNumFaces();<br> <span class="cpp-keyword">int</span> NumBytesPerVertex = pMeshContainer->MeshData.pMesh->GetNumBytesPerVertex();<br> <span class="cpp-keyword">bool</span> MeshUses32BitIndices = (pMeshContainer->MeshData.pMesh->GetOptions() & D3DXMESH_32BIT);<br> <span class="cpp-keyword">int</span> NumBytesPerIndex = (MeshUses32BitIndices ? <span class="cpp-number">4</span>:<span class="cpp-number">2</span>);<br><br> <span class="cpp-comment">//Output mesh information</span><br> GameEngine.LogEntry(<span class="cpp-literal">"Generating Tangents %s - [NumFaces %d | NumBytesPerVert %d | NumBytesPerIndex %d | NumVerts %d | sizeof(MeshVertex) = %d]"</span>, pFrame->Name, NumFaces, NumBytesPerVertex, NumBytesPerIndex, NumVerts, <span class="cpp-keyword">sizeof</span>(SkinnedMeshVertex));<br><br> <span class="cpp-comment">//Check to make sure sizes of verts match up</span><br> <span class="cpp-keyword">if</span>(<span class="cpp-keyword">sizeof</span>(SkinnedMeshVertex) != NumBytesPerVertex)<br> {<br> GameEngine.LogEntry(<span class="cpp-literal">"WARNING! In GenerateTangents() [MeshVertex size (%d) != NumBytesPerVertex (%d)]"</span>, <span class="cpp-keyword">sizeof</span>(SkinnedMeshVertex), NumBytesPerVertex);<br> } <span class="cpp-comment">//end of if </span><br> <span class="cpp-keyword">if</span>(<span class="cpp-keyword">sizeof</span>(SkinnedMeshVertex) < NumBytesPerVertex) <span class="cpp-keyword">return</span>;<br><br> BYTE* pVertices = NULL;<br> WORD* pIndices = NULL;<br> <span class="cpp-keyword">long</span> <span class="cpp-keyword">int</span> ReadPosition = <span class="cpp-number">0</span>;<br><br> <span class="cpp-comment">//Allocate arrays to hold verts + faces</span><br> SkinnedMeshVertex *pMeshVerts = <span class="cpp-keyword">new</span> SkinnedMeshVertex[NumVerts];<br> SkinnedMeshTangentTriangle *pTangentTris = <span class="cpp-keyword">new</span> SkinnedMeshTangentTriangle[NumFaces];<br><br> <span class="cpp-comment">//Lock the mesh's VB</span><br> HRESULT hr = pMeshContainer->MeshData.pMesh->LockVertexBuffer(<span class="cpp-comment">/*D3DLOCK_READONLY*/</span><span class="cpp-number">0</span>, (LPVOID*)&pVertices);<br><br> <span class="cpp-keyword">if</span>(!FAILED(hr))<br> {<br> <span class="cpp-comment">//Copy the meshs' vertex data into the temp array using one memcpy</span><br> <span class="cpp-comment">//memcpy(pMeshVerts, pVertices, NumBytesPerVertex*NumVerts);</span><br><br> <span class="cpp-comment">//Loop through each vertex moving it into the temp array, and output it as well…</span><br> <span class="cpp-keyword">for</span>(<span class="cpp-keyword">int</span> i = <span class="cpp-number">0</span>; i < NumVerts; i++) <br> {<br> <span class="cpp-comment">//Copy the vertex from mesh to temp memory…</span><br> memcpy(&pMeshVerts<span style="font-weight:bold;">, pVertices+ReadPosition, NumBytesPerVertex);<br><br> <span class="cpp-comment">//Output vertex for debugging purposes…</span><br><span class="cpp-comment">/* GameEngine.LogEntry(" Vert %d - [Position %0.3f %0.3f %0.3f] [Normal %0.3f %0.3f %0.3f] [TexCoord %0.2f %0.2f] [Tangent %0.2f %0.2f %0.2f %0.2f] [BlendWeights %0.2f %0.2f %0.2f] [BlendIndices %d %d %d %d]", i, <br> pMeshVerts<span style="font-weight:bold;">.Position.x, pMeshVerts<span style="font-weight:bold;">.Position.y, pMeshVerts<span style="font-weight:bold;">.Position.z, <br> pMeshVerts<span style="font-weight:bold;">.Normal.x, pMeshVerts<span style="font-weight:bold;">.Normal.y, pMeshVerts<span style="font-weight:bold;">.Normal.z,<br> pMeshVerts<span style="font-weight:bold;">.TexCoord.x, pMeshVerts<span style="font-weight:bold;">.TexCoord.y,<br> pMeshVerts<span style="font-weight:bold;">.Tangent.x, pMeshVerts<span style="font-weight:bold;">.Tangent.y, pMeshVerts<span style="font-weight:bold;">.Tangent.z, pMeshVerts<span style="font-weight:bold;">.Tangent.w, <br> pMeshVerts<span style="font-weight:bold;">.BlendWeights.x, pMeshVerts<span style="font-weight:bold;">.BlendWeights.y, pMeshVerts<span style="font-weight:bold;">.BlendWeights.z,<br> pMeshVerts<span style="font-weight:bold;">.BlendIndices[0], pMeshVerts<span style="font-weight:bold;">.BlendIndices[1], pMeshVerts<span style="font-weight:bold;">.BlendIndices[2], pMeshVerts<span style="font-weight:bold;">.BlendIndices[3]);<br>*/</span><br> ReadPosition+=NumBytesPerVertex;<br> } <span class="cpp-comment">//end of for loop</span><br> <br> <span class="cpp-comment">//Now generate triangle array from the mesh's index buffer…</span><br> <span class="cpp-comment">//First lock the index buffer</span><br> pMeshContainer->MeshData.pMesh->LockIndexBuffer(<span class="cpp-number">0</span>, (LPVOID*)&pIndices);<br><br> <span class="cpp-comment">//For each face get the indices of the 3 verts that make up that face.</span><br> <span class="cpp-keyword">for</span>(i = <span class="cpp-number">0</span>; i < NumFaces; i++) <br> { <br> <span class="cpp-keyword">for</span>(<span class="cpp-keyword">int</span> j = <span class="cpp-number">0</span>; j < <span class="cpp-number">3</span>; j++)<br> { <br> <span class="cpp-keyword">if</span>(MeshUses32BitIndices)<br> {<br> <span class="cpp-comment">//32-bit indices </span><br> } <span class="cpp-comment">//end of if</span><br> <span class="cpp-keyword">else</span><br> {<br> <span class="cpp-comment">//16-bit indices</span><br> WORD tempIndex;<br> memcpy(&tempIndex, pIndices, NumBytesPerIndex);<br> pTangentTris<span style="font-weight:bold;">.Index[j] = tempIndex;<br> } <span class="cpp-comment">//end of else</span><br> pIndices++;<br> } <span class="cpp-comment">//end of for loop</span><br> <span class="cpp-comment">//GameEngine.LogEntry("Tri %d - [%d %d %d]", i, pTangentTris<span style="font-weight:bold;">.Index[0], pTangentTris<span style="font-weight:bold;">.Index[1], pTangentTris<span style="font-weight:bold;">.Index[2]);</span><br> } <span class="cpp-comment">//end of for loop</span><br><br> <span class="cpp-comment">//Unlock index buffer</span><br> pMeshContainer->MeshData.pMesh->UnlockVertexBuffer();<br><br> <span class="cpp-comment">//Actually compute tangents inside this function.</span><br> <span class="cpp-comment">//The array of verts will be filled with correct tangent data after this fucntion.</span><br> CalculateTangentArrayMeshVerts(NumVerts, NumFaces, pMeshVerts, pTangentTris);<br><br> <span class="cpp-comment">//Loop through each vertex and move the data [including the tangents] back into the mesh…</span><br> <span class="cpp-comment">//Could use one memcpy call, but doing it this way allows other per-vertex calculations to be done here.</span><br> ReadPosition = <span class="cpp-number">0</span>;<br> <span class="cpp-keyword">for</span>(i = <span class="cpp-number">0</span>; i < NumVerts; i++) <br> { <br> <span class="cpp-comment">//pMeshVerts<span style="font-weight:bold;">.TexCoord.x = 0;</span><br> <span class="cpp-comment">//pMeshVerts<span style="font-weight:bold;">.TexCoord.y = 0;</span><br><br> <span class="cpp-comment">//Output vertex for debugging purposes…</span><br> <span class="cpp-comment">/* GameEngine.LogEntry(" Vert %d - [Position %0.3f %0.3f %0.3f] [Normal %0.3f %0.3f %0.3f] [TexCoord %0.2f %0.2f] [Tangent %0.2f %0.2f %0.2f %0.2f] [BlendWeights %0.2f %0.2f %0.2f] [BlendIndices %d %d %d %d]", i, <br> pMeshVerts<span style="font-weight:bold;">.Position.x, pMeshVerts<span style="font-weight:bold;">.Position.y, pMeshVerts<span style="font-weight:bold;">.Position.z, <br> pMeshVerts<span style="font-weight:bold;">.Normal.x, pMeshVerts<span style="font-weight:bold;">.Normal.y, pMeshVerts<span style="font-weight:bold;">.Normal.z,<br> pMeshVerts<span style="font-weight:bold;">.TexCoord.x, pMeshVerts<span style="font-weight:bold;">.TexCoord.y,<br> pMeshVerts<span style="font-weight:bold;">.Tangent.x, pMeshVerts<span style="font-weight:bold;">.Tangent.y, pMeshVerts<span style="font-weight:bold;">.Tangent.z, pMeshVerts<span style="font-weight:bold;">.Tangent.w, <br> pMeshVerts<span style="font-weight:bold;">.BlendWeights.x, pMeshVerts<span style="font-weight:bold;">.BlendWeights.y, pMeshVerts<span style="font-weight:bold;">.BlendWeights.z,<br> pMeshVerts<span style="font-weight:bold;">.BlendIndices[0], pMeshVerts<span style="font-weight:bold;">.BlendIndices[1], pMeshVerts<span style="font-weight:bold;">.BlendIndices[2], pMeshVerts<span style="font-weight:bold;">.BlendIndices[3]);*/</span><br><br> <span class="cpp-comment">//Copy vertex data back into mesh…</span><br> memcpy(pVertices+ReadPosition, &pMeshVerts<span style="font-weight:bold;">, NumBytesPerVertex);<br><br> ReadPosition+=NumBytesPerVertex;<br> } <span class="cpp-comment">//end of for loop </span><br><br> <span class="cpp-comment">// Finished so unlock VBs</span><br> pMeshContainer->MeshData.pMesh->UnlockVertexBuffer();<br><br> <span class="cpp-comment">//Delete memory used to store verts/faces</span><br> <span class="cpp-keyword">delete</span>[] pMeshVerts;<br> <span class="cpp-keyword">delete</span>[] pTangentTris;<br> } <span class="cpp-comment">//end of if</span><br> <span class="cpp-keyword">else</span> <br> {<br> GameEngine.LogEntry(<span class="cpp-literal">"LockVertexBuffer() #1 FAILED!"</span>);<br> } <span class="cpp-comment">//end of else</span><br><br> pMeshContainer = (D3DXMESHCONTAINER_DERIVED *)pMeshContainer->pNextMeshContainer;<br> } <span class="cpp-comment">//end of while loop</span><br><br> <span class="cpp-keyword">if</span>(pFrame->pFrameSibling != NULL)<br> {<br> GenerateTangents(pFrame->pFrameSibling);<br> } <span class="cpp-comment">//end of if</span><br><br> <span class="cpp-keyword">if</span>(pFrame->pFrameFirstChild != NULL)<br> {<br> GenerateTangents(pFrame->pFrameFirstChild);<br> } <span class="cpp-comment">//end of if</span><br>} <span class="cpp-comment">//end of GenerateTangents function</span><br><br><span class="cpp-comment">//This function is a slight modification of the one found @ http://www.terathon.com/code/tangent.php [ by Eric Lengyel ]</span><br><span class="cpp-comment">//Now uses D3DXVECTOR3 & and the SkinnedMeshVertex / SkinnedMeshTangentTriangle structs which represent</span><br><span class="cpp-comment">//the FVF / Decl of the mesh. Also checks for divide by zero & other error checking.</span><br><span class="cpp-keyword">void</span> CalculateTangentArrayMeshVerts(<span class="cpp-keyword">unsigned</span> <span class="cpp-keyword">long</span> VertexCount, <span class="cpp-keyword">unsigned</span> <span class="cpp-keyword">long</span> TriangleCount, SkinnedMeshVertex *pVerts, <span class="cpp-keyword">const</span> SkinnedMeshTangentTriangle *pTriangles)<br>{<br> <span class="cpp-comment">//The code generates a four-component tangent T in which the handedness of </span><br> <span class="cpp-comment">//the local coordinate system is stored as +-1 in the w-coordinate. The bitangent </span><br> <span class="cpp-comment">//vector B is then given by B = (N x T)Tw. </span><br><br> <span class="cpp-comment">//For mathematical details, see http://www.terathon.com/books/mathgames2.html</span><br><br> D3DXVECTOR3 *tan1 = <span class="cpp-keyword">new</span> D3DXVECTOR3[VertexCount * <span class="cpp-number">2</span>];<br> D3DXVECTOR3 *tan2 = tan1 + VertexCount;<br><br> <span class="cpp-comment">//Clear the memory</span><br> memset(tan1, <span class="cpp-number">0</span>, <span class="cpp-keyword">sizeof</span>(D3DXVECTOR3) * VertexCount * <span class="cpp-number">2</span>);<br> <br> <span class="cpp-keyword">for</span>(<span class="cpp-keyword">long</span> a = <span class="cpp-number">0</span>; a < TriangleCount; a++)<br> {<br> <span class="cpp-keyword">unsigned</span> <span class="cpp-keyword">short</span> i1 = pTriangles->Index[<span class="cpp-number">0</span>];<br> <span class="cpp-keyword">unsigned</span> <span class="cpp-keyword">short</span> i2 = pTriangles->Index[<span class="cpp-number">1</span>];<br> <span class="cpp-keyword">unsigned</span> <span class="cpp-keyword">short</span> i3 = pTriangles->Index[<span class="cpp-number">2</span>];<br><br> <span class="cpp-comment">//Do bounds checking making sure each index is between 0 & VertexCount.</span><br> <span class="cpp-keyword">if</span>(i1 >= VertexCount || i1 < <span class="cpp-number">0</span> || i2 >= VertexCount || i2 < <span class="cpp-number">0</span> || i3 >= VertexCount || i3 < <span class="cpp-number">0</span>)<br> {<br> GameEngine.LogEntry(<span class="cpp-literal">"CalculateTangentArrayMeshVerts() ERROR! Bounds Exceeded! [%d %d %d]"</span>, i1, i2, i3);<br> <span class="cpp-keyword">return</span>;<br> } <span class="cpp-comment">//end of if</span><br> <br> <span class="cpp-keyword">const</span> D3DXVECTOR3& v1 = pVerts[i1].Position;<br> <span class="cpp-keyword">const</span> D3DXVECTOR3& v2 = pVerts[i2].Position;<br> <span class="cpp-keyword">const</span> D3DXVECTOR3& v3 = pVerts[i3].Position;<br> <br> <span class="cpp-keyword">const</span> D3DXVECTOR2& w1 = pVerts[i1].TexCoord;<br> <span class="cpp-keyword">const</span> D3DXVECTOR2& w2 = pVerts[i2].TexCoord;<br> <span class="cpp-keyword">const</span> D3DXVECTOR2& w3 = pVerts[i3].TexCoord;<br> <br> <span class="cpp-keyword">double</span> x1 = v2.x - v1.x;<br> <span class="cpp-keyword">double</span> x2 = v3.x - v1.x;<br> <span class="cpp-keyword">double</span> y1 = v2.y - v1.y;<br> <span class="cpp-keyword">double</span> y2 = v3.y - v1.y;<br> <span class="cpp-keyword">double</span> z1 = v2.z - v1.z;<br> <span class="cpp-keyword">double</span> z2 = v3.z - v1.z;<br> <br> <span class="cpp-keyword">double</span> s1 = w2.x - w1.x;<br> <span class="cpp-keyword">double</span> s2 = w3.x - w1.x;<br> <span class="cpp-keyword">double</span> t1 = w2.y - w1.y;<br> <span class="cpp-keyword">double</span> t2 = w3.y - w1.y;<br><br> <span class="cpp-keyword">double</span> DivValue = (s1 * t2 - s2 * t1);<br><br> <span class="cpp-comment">//Check for divide by zero…</span><br> <span class="cpp-keyword">if</span>(DivValue == <span class="cpp-number">0</span>.0f)<br> {<br> GameEngine.LogEntry(<span class="cpp-literal">"CalculateTangentArrayMeshVerts() WARNING! Divide by 0 detected! Indices = [%d %d %d] TexCoords = [%f %f] [%f %f] [%f %f]"</span>, i1, i2, i3, w1.x, w1.y, w2.x, w2.y, w3.x, w3.y); <br> DivValue = <span class="cpp-number">1</span>.0F;<br> } <span class="cpp-comment">//end of if</span><br><br> <span class="cpp-keyword">double</span> r = <span class="cpp-number">1</span>.0F / DivValue;<br> D3DXVECTOR3 sdir = D3DXVECTOR3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);<br> D3DXVECTOR3 tdir = D3DXVECTOR3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);<br> <br> tan1[i1] += sdir;<br> tan1[i2] += sdir;<br> tan1[i3] += sdir;<br> <br> tan2[i1] += tdir;<br> tan2[i2] += tdir;<br> tan2[i3] += tdir;<br> <br> pTriangles++;<br> } <span class="cpp-comment">//end of for loop</span><br> <br> <span class="cpp-keyword">for</span>(<span class="cpp-keyword">long</span> a = <span class="cpp-number">0</span>; a < VertexCount; a++)<br> {<br> <span class="cpp-keyword">const</span> D3DXVECTOR3& n = pVerts[a].Normal;<br> <span class="cpp-keyword">const</span> D3DXVECTOR3& t = tan1[a];<br> <br> <span class="cpp-comment">// Gram-Schmidt orthogonalize</span><br> D3DXVECTOR3 TempTangent = (t - n * D3DXVec3Dot(&n, &t));<br> D3DXVec3Normalize(&TempTangent, &TempTangent);<br> <br> <span class="cpp-comment">// Calculate handedness & set final tangent</span><br> pVerts[a].Tangent.x = TempTangent.x;<br> pVerts[a].Tangent.y = TempTangent.y;<br> pVerts[a].Tangent.z = TempTangent.z;<br> pVerts[a].Tangent.w = (D3DXVec3Dot(D3DXVec3Cross(&TempTangent, &n, &t), &tan2[a]) < <span class="cpp-number">0</span>.0F) ? -<span class="cpp-number">1</span>.0F : <span class="cpp-number">1</span>.0F; <br> <span class="cpp-comment">// GameEngine.LogEntry("%d = %f %f %f %f", a, pVerts[a].Tangent.x, pVerts[a].Tangent.y, pVerts[a].Tangent.z, pVerts[a].Tangent.w);</span><br> } <span class="cpp-comment">//end of for loop</span><br> <br> <span class="cpp-keyword">delete</span>[] tan1;<br>} <span class="cpp-comment">//end of CalculateTangentArrayMeshVerts function</span><br><br><br><br><br><br></pre></div><!–ENDSCRIPT–><br><br>As noted in the comments, the actual code that calculates the tangents is slightly modified from http://www.terathon.com/code/tangent.php [by Eric Lengyel, props to him].<br><br>Here are the structs used in the above code…<br><!–STARTSCRIPT–><!–source lang="cpp"–><div class="source"><pre><br><span class="cpp-keyword">struct</span> SkinnedMeshVertex<br>{<br> D3DXVECTOR3 Position;<br> D3DXVECTOR3 BlendWeights;<br> <span class="cpp-keyword">unsigned</span> <span class="cpp-keyword">char</span> BlendIndices[<span class="cpp-number">4</span>];<br> D3DXVECTOR3 Normal;<br> D3DXVECTOR2 TexCoord; <br> D3DXVECTOR4 Tangent;<br>};<br><br><span class="cpp-keyword">struct</span> SkinnedMeshTangentTriangle<br>{<br> <span class="cpp-keyword">unsigned</span> <span class="cpp-keyword">short</span> Index[<span class="cpp-number">3</span>];<br>};<br><br><br><br><br><br><br></pre></div><!–ENDSCRIPT–><br><br>You just load the skinned mesh as usual, then use this fucntion to make room for the tangents inside the mesh's vertex decl.<br><!–STARTSCRIPT–><!–source lang="cpp"–><div class="source"><pre><br><span class="cpp-keyword">void</span> CSkinnedMesh::ForceDeclRecursive(LPD3DXFRAME pFrameBase)<br>{<br> D3DXFRAME_DERIVED *pFrame = (D3DXFRAME_DERIVED*)pFrameBase;<br> D3DXMESHCONTAINER_DERIVED *pMeshContainer;<br> HRESULT hr;<br><br> pMeshContainer = (D3DXMESHCONTAINER_DERIVED *)pFrame->pMeshContainer;<br><br> <span class="cpp-keyword">while</span>(pMeshContainer != NULL)<br> {<br> <span class="cpp-comment">//Force Decl…</span><br> <span class="cpp-comment">//NumBytesPerVert = 64</span><br> <span class="cpp-comment">//Element #0 - D3DDECLUSAGE_POSITION | Offset 0 | Type 2 | Usage Index 0</span><br> <span class="cpp-comment">//Element #1 - D3DDECLUSAGE_BLENDWEIGHT | Offset 12 | Type 2 | Usage Index 0</span><br> <span class="cpp-comment">//Element #2 - D3DDECLUSAGE_BLENDINDICES | Offset 24 | Type 4 | Usage Index 0</span><br> <span class="cpp-comment">//Element #3 - D3DDECLUSAGE_NORMAL | Offset 28 | Type 2 | Usage Index 0</span><br> <span class="cpp-comment">//Element #4 - D3DDECLUSAGE_TEXCOORD | Offset 40 | Type 1 | Usage Index 0</span><br> <span class="cpp-comment">//Element #5 - D3DDECLUSAGE_TEXCOORD | Offset 48 | Type 3 | Usage Index 1</span><br><br> D3DVERTEXELEMENT9 NewVertexDecl[] =<br> {<br> { <span class="cpp-number">0</span>, <span class="cpp-number">0</span>, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, <span class="cpp-number">0</span> },<br> { <span class="cpp-number">0</span>, <span class="cpp-number">12</span>, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDWEIGHT, <span class="cpp-number">0</span> },<br> { <span class="cpp-number">0</span>, <span class="cpp-number">24</span>, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDINDICES, <span class="cpp-number">0</span> },<br> { <span class="cpp-number">0</span>, <span class="cpp-number">28</span>, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, <span class="cpp-number">0</span> },<br> { <span class="cpp-number">0</span>, <span class="cpp-number">40</span>, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, <span class="cpp-number">0</span> },<br> { <span class="cpp-number">0</span>, <span class="cpp-number">48</span>, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, <span class="cpp-number">1</span> },<br><br> D3DDECL_END()<br> }; <br><br> LPD3DXMESH pMesh2;<br> hr = pMeshContainer->MeshData.pMesh->CloneMesh(pMeshContainer->MeshData.pMesh->GetOptions(), NewVertexDecl, D3DDEVICE, &pMesh2);<br> <span class="cpp-keyword">if</span>(!FAILED(hr))<br> {<br> pMeshContainer->MeshData.pMesh->Release();<br> pMeshContainer->MeshData.pMesh = pMesh2;<br> pMesh2 = NULL;<br> } <span class="cpp-comment">//end of if</span><br> <span class="cpp-keyword">else</span><br> {<br> GameEngine.LogEntry(<span class="cpp-literal">"CSkinnedMesh::[CloneMesh() failed in ForceDeclRecursive()]"</span>);<br> <span class="cpp-keyword">return</span>;<br> } <span class="cpp-comment">//end of eles</span><br><br> hr = pMeshContainer->MeshData.pMesh->UpdateSemantics(NewVertexDecl);<br> <span class="cpp-keyword">if</span>(FAILED(hr))<br> {<br> GameEngine.LogEntry(<span class="cpp-literal">"CSkinnedMesh::[ERROR: UpdateSemantics() failed in ForceDeclRecursive() %d]"</span>, hr);<br> <span class="cpp-keyword">return</span>;<br> } <span class="cpp-comment">//end of if</span><br><br> pMeshContainer = (D3DXMESHCONTAINER_DERIVED *)pMeshContainer->pNextMeshContainer;<br> } <span class="cpp-comment">//end of while loop</span><br><br> <span class="cpp-keyword">if</span>(pFrame->pFrameSibling != NULL)<br> {<br> ForceDeclRecursive(pFrame->pFrameSibling);<br> } <span class="cpp-comment">//end of if</span><br><br> <span class="cpp-keyword">if</span>(pFrame->pFrameFirstChild != NULL)<br> {<br> ForceDeclRecursive(pFrame->pFrameFirstChild);<br> } <span class="cpp-comment">//end of if</span><br>} <span class="cpp-comment">//end of ForceDeclRecursive function</span><br><br><br><br><br><br><br></pre></div><!–ENDSCRIPT–><br><br>After you call ForceDeclRecursive() to make room for the tangents, then you can call ComputeTangents() to actually calculate them.<br><br>Another function that was somewhat helpful is this one which can be used for outputting the meshes vertex declaration…to make sure things look how they should after the conversion…just noticed that I should have used a switch() here…eh…*shrugs*<br><!–STARTSCRIPT–><!–source lang="cpp"–><div class="source"><pre><br><span class="cpp-keyword">void</span> OutputDecl(LPD3DXMESH pMesh)<br>{<br> HRESULT hr;<br> <span class="cpp-keyword">if</span>(pMesh == NULL)<br> {<br> GameEngine.LogEntry(<span class="cpp-literal">"CSkinnedMesh::[ERROR in OutputDecl() pMesh == NULL"</span>);<br> <span class="cpp-keyword">return</span>;<br> } <span class="cpp-comment">//end of if</span><br><br> D3DVERTEXELEMENT9 elems[MAX_FVF_DECL_SIZE];<br> hr = pMesh->GetDeclaration(elems);<br> <span class="cpp-keyword">if</span>(SUCCEEDED(hr))<br> {<br> GameEngine.LogEntry(<span class="cpp-literal">" Outputting Vertex Declaration…"</span>);<br> GameEngine.LogEntry(<span class="cpp-literal">" —————————————-"</span>);<br> GameEngine.LogEntry(<span class="cpp-literal">" NumBytesPerVert = %d"</span>, pMesh->GetNumBytesPerVertex());<br> <span class="cpp-keyword">for</span>(<span class="cpp-keyword">int</span> i = <span class="cpp-number">0</span>; i < MAX_FVF_DECL_SIZE; ++i)<br> {<br> <span class="cpp-comment">// Did we reach D3DDECL_END() {0xFF,0,D3DDECLTYPE_UNUSED, 0,0,0}?</span><br> <span class="cpp-keyword">if</span>(elems<span style="font-weight:bold;">.Stream == 0xff)<br> <span class="cpp-keyword">break</span>;<br><br> <span class="cpp-keyword">if</span>(elems<span style="font-weight:bold;">.Usage == D3DDECLUSAGE_POSITION)<br> {<br> GameEngine.LogEntry(<span class="cpp-literal">" Element #%d - D3DDECLUSAGE_POSITION | Offset %d | Type %d | Usage Index %d"</span>, i, elems<span style="font-weight:bold;">.Offset, elems<span style="font-weight:bold;">.Type, elems<span style="font-weight:bold;">.UsageIndex);<br> } <span class="cpp-comment">//end of if</span><br> <span class="cpp-keyword">else</span> <span class="cpp-keyword">if</span>(elems<span style="font-weight:bold;">.Usage == D3DDECLUSAGE_NORMAL)<br> {<br> GameEngine.LogEntry(<span class="cpp-literal">" Element #%d - D3DDECLUSAGE_NORMAL | Offset %d | Type %d | Usage Index %d"</span>, i, elems<span style="font-weight:bold;">.Offset, elems<span style="font-weight:bold;">.Type, elems<span style="font-weight:bold;">.UsageIndex);<br> } <span class="cpp-comment">//end of if</span><br> <span class="cpp-keyword">else</span> <span class="cpp-keyword">if</span>(elems<span style="font-weight:bold;">.Usage == D3DDECLUSAGE_TEXCOORD)<br> {<br> GameEngine.LogEntry(<span class="cpp-literal">" Element #%d - D3DDECLUSAGE_TEXCOORD | Offset %d | Type %d | Usage Index %d"</span>, i, elems<span style="font-weight:bold;">.Offset, elems<span style="font-weight:bold;">.Type, elems<span style="font-weight:bold;">.UsageIndex);<br> } <span class="cpp-comment">//end of if</span><br> <span class="cpp-keyword">else</span> <span class="cpp-keyword">if</span>(elems<span style="font-weight:bold;">.Usage == D3DDECLUSAGE_BLENDWEIGHT)<br> {<br> GameEngine.LogEntry(<span class="cpp-literal">" Element #%d - D3DDECLUSAGE_BLENDWEIGHT | Offset %d | Type %d | Usage Index %d"</span>, i, elems<span style="font-weight:bold;">.Offset, elems<span style="font-weight:bold;">.Type, elems<span style="font-weight:bold;">.UsageIndex);<br> } <span class="cpp-comment">//end of if</span><br> <span class="cpp-keyword">else</span> <span class="cpp-keyword">if</span>(elems<span style="font-weight:bold;">.Usage == D3DDECLUSAGE_TANGENT)<br> {<br> GameEngine.LogEntry(<span class="cpp-literal">" Element #%d - D3DDECLUSAGE_TANGENT | Offset %d | Type %d | Usage Index %d"</span>, i, elems<span style="font-weight:bold;">.Offset, elems<span style="font-weight:bold;">.Type, elems<span style="font-weight:bold;">.UsageIndex);<br> } <span class="cpp-comment">//end of if</span><br> <span class="cpp-keyword">else</span> <span class="cpp-keyword">if</span>(elems<span style="font-weight:bold;">.Usage == D3DDECLUSAGE_BLENDINDICES)<br> {<br> GameEngine.LogEntry(<span class="cpp-literal">" Element #%d - D3DDECLUSAGE_BLENDINDICES | Offset %d | Type %d | Usage Index %d"</span>, i, elems<span style="font-weight:bold;">.Offset, elems<span style="font-weight:bold;">.Type, elems<span style="font-weight:bold;">.UsageIndex);<br> } <span class="cpp-comment">//end of if </span><br> <span class="cpp-keyword">else</span><br> {<br> GameEngine.LogEntry(<span class="cpp-literal">" Element #%d contains unsupported usage…%d"</span>, i, elems<span style="font-weight:bold;">.Usage);<br> } <span class="cpp-comment">//end of else</span><br> } <span class="cpp-comment">//end of for loop </span><br> } <span class="cpp-comment">//end of if</span><br> <span class="cpp-keyword">else</span><br> { <br> GameEngine.LogEntry(<span class="cpp-literal">"CSkinnedMesh::[Failed to load vertex declaration]"</span>);<br> }<span class="cpp-comment">//end of else</span><br>} <span class="cpp-comment">//end of if</span><br><br><br><br><br><br><br></pre></div><!–ENDSCRIPT–><br><br>Like I said earlier, I just threw that out there with the hope of maybe saving somebody some time in the future.<br><br>Now for the results :-D<br><br>I purchased several "test models" since I don't have any normal maps for my in-game characters, I wanted to make sure I had everything working correctly before I spent time making the normal maps for my characters…I'll decribe the process I have to go through to create these normal maps later in the post.<br><br>I think it's working ;-)<br>This model is ~9k triangles with a 2k^2 color & a 2k^2 normal map.<br><IMG SRC="http://www.radioactive-software.com/gangwar/Mar11/BumpMaptest.jpg" border="0"><br><IMG SRC="http://www.radioactive-software.com/gangwar/Mar11/BumpMapTest4.jpg" border="0"><br><br>I also got this police-officer-dude, he's ~4.5k triangles with a 1024x2048 normal & color map. [Left is with no normal mapping, right is with it enabled].<br><IMG SRC="http://www.radioactive-software.com/gangwar/Mar11/NormalMapCompare2.jpg" border="0"><br><br>I have notice slight artifacts sometimes, I might have a issue in my shader, but I'm pretty sure the tangents are being generated correctly. <br><br>Ok like I said earlier, my meshes weren't created with normal maps, or even with normal mapping in mind. Thankfully Nvidia has that awesome tool which can generate a normal map from a texture. There are all kinds of tutorials on the net about how to use it.<br><br>Here is a image of the results of that tool….<br><IMG SRC="http://www.radioactive-software.com/gangwar/Mar11/DiffBumpMaps.jpg" border="0"><br><br>I'm going to have to go through by hand touching up each of the converted normal maps…sounds like fun, hopefully I'll be able to get good results. The face is one of the areas that doesn't look right at all, straight outa the tool. Also I have to add more texture to the clothes in the normal map.<br><br>And the gangster in-game…not quite as impressive as the other 2 models, but it's an improvement [grin]<br><IMG SRC="http://www.radioactive-software.com/gangwar/Mar11/NormalMapCompare.jpg" border="0"><br><br>Another angle…<br><IMG SRC="http://www.radioactive-software.com/gangwar/Mar11/GangsterNormalMap.jpg" border="0"><br><br>Well I'm tired of working on this now…but tommorow is another day, with more progress to be made. I'm devoting this week towards implementing the new order giving system into the game, and making a final push to complete the gameplay + interface.<br><br> - Dan<br><br>Ohhhh yea, here is 1 more screenshot…I think I have an idea for my next game lol ZOMG MONSTAR WARS.<br><br><IMG SRC="http://www.radioactive-software.com/gangwar/Mar11/BumpMapTest1.jpg" border="0"><br><br><IMG SRC="http://www.radioactive-software.com/gangwar/Mar11/BumpMapTest3.jpg" border="0"><div> </div>
0 likes 4 comments

Comments

SimmerD
Computing a basic tangent space is not hard, and Eric's code is fine for that. The challenge, however, is in properly smoothing the tangent space. Smoothing across mirrored and stretched tex coords is the tricky part.

You may look at NVMeshMender, which I co-wrote with Clint Brewer.

If your model is totally unwrapped, you won't have many seam or stretch issues, but if it is mirrored, you will see lighting issues unless you take that into account when you smooth the basis vectors across neigbhor tris.

March 12, 2007 06:32 AM
dgreen02
Yes sir :-D

I forgot to mention that my step in between trying to use only D3DX functions to compute the tangents & calculating them myself was to use the meshmender lib in the Nvidia SDK.

I spent a day or two studying/playing with it, though I don't recall the show-stopper that made me give up move towards this option. Hmmmm.

Infact I was using the MeshMenderSimpleExample to preview the normal maps on my .x models when I was creating them...so it must have been something odd that caused me to give up.
March 12, 2007 12:55 PM
Gaheris
I like normal mapping. ;-) The character does look tremendously better, especially the shirt. Good job on that, too bad you had so many problems with it.
March 12, 2007 01:27 PM
Drilian
Hurry up and finish Gang War so you can make that monster game ;D

I've been tracking this journal for a while, and you never fail to impress! Keep on rockin!
March 13, 2007 02:38 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement