Doom3world

Doom 3 - Quake 4 - ET:QW - Prey - Rage
It is currently Sat Nov 10, 2007 12:15 pm

All times are UTC




Post new topic Reply to topic  [ 131 posts ]  Go to page Previous  1, 2, 3, 4, 5, 6, 7  Next
Author Message
 Post subject:
PostPosted: Sat Jan 31, 2004 8:01 pm 
Offline
Plasma Spammer
User avatar

Joined: Fri Jun 27, 2003 10:24 pm
Posts: 2508
Location: Munich, Germany
Edit on 2004-08-12: Skip to this post on page 4 to get the latest version:

http://www.doom3world.org/phpbb2/viewto ... 3755#33755


(edited out my old link)

There are a few example scene files that use cameras, so you can see how to include that in the scene.txt.
Pressing Space will toggle between the normal camera and the animation-controlled camera.
Pressing "T" toggles skeleton drawing, which now also displays the camera(s) floating around in the scene.

_________________
Image Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5


Last edited by der_ton on Thu Aug 12, 2004 12:58 pm, edited 4 times in total.

Top
 Profile  
 
 Post subject:
PostPosted: Sat Jan 31, 2004 9:06 pm 
Offline
Site Admin
User avatar

Joined: Tue May 28, 2002 5:17 pm
Posts: 8880
Location: Munich / Germany
Uploaded:

http://www.doom3world.org/doom3/der_ton/modelviewer.rar

_________________
Image Staff - The world is yours, soon in 6 degrees of freedom!
Visit ModWiki


Top
 Profile  
 
 Post subject:
PostPosted: Wed Mar 03, 2004 5:47 pm 
Offline
is connecting to Doom3world.org

Joined: Sat Nov 09, 2002 2:57 am
Posts: 4
one suggestion.( if its not there already )

could you add a way of turning off the text on screen?

like say i press the tab button to toggle the text on and off.

cheers.
its a fine model viewer tho :)


Top
 Profile  
 
 Post subject:
PostPosted: Wed Mar 03, 2004 6:14 pm 
Offline
Plasma Spammer
User avatar

Joined: Fri Jun 27, 2003 10:24 pm
Posts: 2508
Location: Munich, Germany
Thanks for the feedback.
I haven´t worked on it for a while now. I think the next thing it needs is a GUI, the on-screen text is likely to become irrelevant then anyway. :)

_________________
Image Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5


Top
 Profile  
 
 Post subject: Fix MD5 Models
PostPosted: Mon Apr 19, 2004 12:02 pm 
Offline
has joined the game

Joined: Mon Aug 04, 2003 5:46 pm
Posts: 38
Location: Somewhere else
Hello der_ton,

Can you explain how you "fix" MD5 models at loading for the Stencil Shadow :?:

I'm using an algo (partially derived from the one in md2shader) but it is far to be perfect :(

Thanks...

_________________
"Haa, the good old time" - KPixel.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Apr 19, 2004 1:36 pm 
Offline
Plasma Spammer
User avatar

Joined: Fri Jun 27, 2003 10:24 pm
Posts: 2508
Location: Munich, Germany
First of all, I recommend these articles on stencil shadowing:
http://www.gamasutra.com/features/20021 ... yel_01.htm
http://developer.nvidia.com/object/robu ... lumes.html

The only data that is built upon loading a md5 model for stencil shadowing is an edge-list. An edge is a struct that has two vertex-indices, two triangle-indices and a bool that will hold the information of wether it´s a silhouette edge or not.

Doom3´s models are not necessarily closed, so there can be edges that have only one triangle (usually they have two). Such a "one-winged" edge is a silhouette-edge if its triangle faces the light. Normal "two-winged" edges are silhouette edges if one triangle faces the light and the other one doesn´t.

To get the edge-list, a more complicated approach is needed than what is described in the gamasutra article (Kilgard´s paper doesn´t describe the edge creation process at all). But you will want to read that gamasutra article first because it makes it easier to understand why the following algorithm works for non-closed meshes.
You have to go through the triangles in two passes. First, collect the edges where the first vertex has a lower index than the second one, and create an edge entry for each of those (ignore the others). Then you have to go through the triangles again and look for edges where the first vertex has a higher number than the second one. If that edge has been found in the first pass, then you have just found the second triangle of a two-winged edge. If that edge has not been found in the first pass, then you have found a one-winged edge. At the end of the second pass you may have edges that have been found in the first pass but not in the second, these are one-winged, too.

The model is "open" along those one-winged edges, and my viewer shows them in the "geometry debug" mode as thick yellow lines.

_________________
Image Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 20, 2004 3:36 pm 
Offline
has joined the game

Joined: Mon Aug 04, 2003 5:46 pm
Posts: 38
Location: Somewhere else
So, you don't do any "correction" of the IB ?!? (like Remove degenerate triangles and Close open triangle groups...)

der_ton wrote:
You have to go through the triangles in two passes.

First, collect the edges where the first vertex has a lower index than the second one,
and create an edge entry for each of those (ignore the others).

Then you have to go through the triangles again and look for edges
where the first vertex has a higher number than the second one.
If that edge has been found in the first pass, then you have just found the second triangle of a two-winged edge.
If that edge has not been found in the first pass, then you have found a one-winged edge.

At the end of the second pass you may have edges that have been found in the first pass but not in the second, these are one-winged, too.

The model is "open" along those one-winged edges, and my viewer shows them in the "geometry debug" mode as thick yellow lines.


Can you translate this into an algo ? (English is too evasive for me, lol :D)

What I am actually doing to computes edges is :

Code:
    // For each face, Find the 3 adjacents faces
    for(DWORD i=0; i<dwNumFace; i++)
        for(DWORD j=0; j<3; j++) // For each Edge
        {
            Face::_Edge& ActualEdge = pFaces[i].Edge[j];
            if(ActualEdge.idAdj==-1) // => Not found
                FindAdjacentFace( pIB, pFaces, dwNumFace, i, j,
                                  &ActualEdge.idAdj, &ActualEdge.idAdjEdge );
        }


And FindAdjacentFace() works like this :

Code:
void FindAdjacentFace( WORD* pIB, Face* pFaces, DWORD dwNumFace, long idFace, long idFaceEdge,
                       long* pidAdj, long* pidAdjEdge )
{
    // Compute idVerts
    DWORD idVert1 = 3*idFace + idFaceEdge; // The position in the IB
    DWORD idVert2 = (idFaceEdge!=2 ? idVert1+1 : 3*idFace+0);
    idVert1 = pIB[idVert1];
    idVert2 = pIB[idVert2];

    for(DWORD i=0; i<dwNumFace; i++) // For each face
    {
        if((long)i==idFace) continue; // skip the face for wich we search the adjancent
        for(DWORD j=0; j<3; j++) // For each Edge
        {
            DWORD id1 = 3*i + j;
            DWORD id2 = ( j!=2 ? id1+1 : id1-2 );
            if( (pIB[id1]==idVert1 && pIB[id2]==idVert2) // if they share the same edge
             || (pIB[id1]==idVert2 && pIB[id2]==idVert1) )
            {
                // Store the AdjTri and its Edge
                *pidAdj = i;
                *pidAdjEdge = j;
                pFaces[i].Edge[j].idAdj = idFace; // To avoid re-computing it for the AdjTri,
                pFaces[i].Edge[j].idAdjEdge = idFaceEdge; // we can store it now...
                return; // End
            }
        }
    }

    return; // => Not found
}

_________________
"Haa, the good old time" - KPixel.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 20, 2004 10:23 pm 
Offline
Plasma Spammer
User avatar

Joined: Fri Jun 27, 2003 10:24 pm
Posts: 2508
Location: Munich, Germany
What´s an "IB"?
Yes the viewer does analyze and correct (if necessary) the topology of the mesh. That´s necessary because of the reorganization for tangent-space bumpmapping anyway (vertices have to be duplicated at the symmetry-seam, and duplicated vertices that are not located at the seam have to be welded). But you asked what my viewer does in preparation for the stencil shadowing. And the edge list creation is the only thing in that regard, and I will not post all the other load-time initialization code. :wink:

Yours is not an efficient way to store the edge data for shadowing. You don´t end up with a list of edges, but just with "tri-edges", that is edges that belong to a triangle. One and the same edge is represented multiple times, in each triangle that uses that edge (usually two). But edges in the shadowing kind of sense are a thing of their own.
You want to have a quick way to determine the silhouette of an object. The silhouette is a collection of edges (pairs of vertices, basically), a sub-collection of all the edges of the model. Your representation of edges is unefficient because they are stored within the triangles, but an edge can belong to more than one triangle, and thus shouldn´t be stored like that.

Here´s how the viewer does it. The result of a call to createedgelist is a filled edgelist[meshnumber]. An edge is a struct that contains two vertex-indices, two triangle-indices and a bool that flags that the edge is a silhouette edge.

Code:
void MD5Model::appendedge(int meshnumber, int v1, int v2, int tri)
{
   numedges[meshnumber]++;
   edgelist[meshnumber]=(edge*) realloc(edgelist[meshnumber], numedges[meshnumber]*sizeof(edge));
   edge* newedge = &edgelist[meshnumber][numedges[meshnumber]-1];
   newedge->silhouette=0;
   newedge->tri1=tri;
   newedge->vert1=v1;
   newedge->vert2=v2;
   newedge->tri2=-1;   //no second triangle yet
}
void MD5Model::findcreatematchingedge(int meshnumber, int v1, int v2, int tri)
{
   for (int i=0; i<numedges[meshnumber]; i++)
   {
      if (edgelist[meshnumber][i].vert1==v2 && edgelist[meshnumber][i].vert2==v1)
      {
         if (edgelist[meshnumber][i].tri2==-1)
         {
            edgelist[meshnumber][i].tri2=tri;
            return;
         }
         else
            fprintf(debugfile(), "geometryanalyzer: edge with more than two tris: meshnumber, v1, v2, tri: %i %i %i %i\n", meshnumber, v1, v2, tri);
      }
   }
   //we haven´t found a match for this edge, so we create it as a new one
   appendedge(meshnumber, v1, v2, tri);
}

void MD5Model::createedgelist(int meshnumber)
{
   for (int i=0; i<numtris[meshnumber]; i++)
   {
      trianglestruct* tri = &meshtrisarray[meshnumber][i];
      if (tri->vertex1<tri->vertex2)
         appendedge(meshnumber, tri->vertex1, tri->vertex2, i);

      if (tri->vertex2<tri->vertex3)
         appendedge(meshnumber, tri->vertex2, tri->vertex3, i);

      if (tri->vertex3<tri->vertex1)
         appendedge(meshnumber, tri->vertex3, tri->vertex1, i);

   }

   for (i=0; i<numtris[meshnumber]; i++)
   {
      trianglestruct* tri = &meshtrisarray[meshnumber][i];
      if (tri->vertex1>tri->vertex2)
         findcreatematchingedge(meshnumber, tri->vertex1, tri->vertex2, i);

      if (tri->vertex2>tri->vertex3)
         findcreatematchingedge(meshnumber, tri->vertex2, tri->vertex3, i);

      if (tri->vertex3>tri->vertex1)
         findcreatematchingedge(meshnumber, tri->vertex3, tri->vertex1, i);

   }
}


How the viewer uses that edgelist at runtime should be self-explanatory if you have understood stencil-shadowing. First, for each triangle we do a light-test (tri-normal dot L), so that each triangle knows whether it faces the light or not. Then we determine the silhouette edges, by looking at the two tris of an edge (or only one for one-winged edges). The result is that the silhouette boolean members of the edges get filled. Then we´re ready to draw the shadow volumes.

_________________
Image Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 21, 2004 1:32 pm 
Offline
has joined the game

Joined: Mon Aug 04, 2003 5:46 pm
Posts: 38
Location: Somewhere else
der_ton wrote:
What´s an "IB"?


"IB" means "Index Buffer"; it's the name used by DirectX to call the list of indices...

der_ton wrote:
But you asked what my viewer does in preparation for the stencil shadowing. And the edge list creation is the only thing in that regard, and I will not post all the other load-time initialization code. :wink:

Yours is not an efficient way to store the edge data for shadowing. [...]


OK, I think I will be busy this night, trying all this stuff :wink:
(this approach is really different of mine...)

Thanks :)

_________________
"Haa, the good old time" - KPixel.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 22, 2004 2:39 pm 
Offline
has joined the game

Joined: Mon Aug 04, 2003 5:46 pm
Posts: 38
Location: Somewhere else
OK, it is up and running :wink:

der_ton wrote:
Yes the viewer does analyze and correct (if necessary) the topology of the mesh. That´s necessary because of the reorganization for tangent-space bumpmapping anyway (vertices have to be duplicated at the symmetry-seam, and duplicated vertices that are not located at the seam have to be welded).


I think this correction also has an impact on the shadow because in mine, there is some holes here and there (eg: between the head and the body); they aren't very visible, but they are :D

Hum, for your appendedge(), you should really pre-compute the size of numedges[meshnumber] because realloc() can be call MANY times and it kills performance for big models; my result is :

Code:
HRESULT XKGraphics::GenConnectivityInfos(IB* pIB)
{
    BeginMethod("GenConnectivityInfos()");
    AssertParam(pIB!=NULL);
    DWORD i;

    const DWORD dwNumFace = pIB->dwNumIndices/3;

    // For optimization, we pre-compute the required number of Edge
    DWORD dwNumEdge = 0;
    for(i=0; i<dwNumFace; i++) // For each face
    {
        const DWORD id = i*3;
        if(pIB->pIB[id+0] < pIB->pIB[id+1])
            dwNumEdge++;
        if(pIB->pIB[id+1] < pIB->pIB[id+2])
            dwNumEdge++;
        if(pIB->pIB[id+2] < pIB->pIB[id+0])
            dwNumEdge++;
    }

    // So we allocate it only one time
    pIB->tabEdge.Size() = dwNumEdge;
    pIB->tabEdge.Ptr() = new Edge[pIB->tabEdge.Size()];
    AssertMem(pIB->tabEdge.Ptr());

    dwNumEdge = 0;
    for(i=0; i<dwNumFace; i++) // For each face
    {
        const DWORD id = i*3;
        if(pIB->pIB[id+0] < pIB->pIB[id+1])
        {
            T( WriteEdge(pIB, i, pIB->pIB[id+0], pIB->pIB[id+1], dwNumEdge) );
            dwNumEdge++;
        }
        if(pIB->pIB[id+1] < pIB->pIB[id+2])
        {
            T( WriteEdge(pIB, i, pIB->pIB[id+1], pIB->pIB[id+2], dwNumEdge) );
            dwNumEdge++;
        }
        if(pIB->pIB[id+2] < pIB->pIB[id+0])
        {
            T( WriteEdge(pIB, i, pIB->pIB[id+2], pIB->pIB[id+0], dwNumEdge) );
            dwNumEdge++;
        }
    }

    Assert(pIB->tabEdge.Size() == dwNumEdge); // :)

    // Compute the number of Edge that will be added

    for(i=0; i<dwNumFace; i++) // For each face
    {
        const DWORD id = i*3;
        if(pIB->pIB[id+0] > pIB->pIB[id+1])
            if( MatchingEdgeNotFound(pIB, i, pIB->pIB[id+0], pIB->pIB[id+1]) )
                dwNumEdge++;
        if(pIB->pIB[id+1] > pIB->pIB[id+2])
            if( MatchingEdgeNotFound(pIB, i, pIB->pIB[id+1], pIB->pIB[id+2]) )
                dwNumEdge++;
        if(pIB->pIB[id+2] > pIB->pIB[id+0])
            if( MatchingEdgeNotFound(pIB, i, pIB->pIB[id+2], pIB->pIB[id+0]) )
                dwNumEdge++;
    }

    // Re-allocate
    if(dwNumEdge > pIB->tabEdge.Size())
    {
        Edge* pE = new Edge[dwNumEdge];
        AssertMem(pE);
        for(i=0; i<pIB->tabEdge.Size(); i++) // Copy old Edges
        {
            pE[i].idVert1 = pIB->tabEdge[i].idVert1;
            pE[i].idVert2 = pIB->tabEdge[i].idVert2;
            pE[i].idTri1 = pIB->tabEdge[i].idTri1;
            pE[i].idTri2 = pIB->tabEdge[i].idTri2;
        }
        XK_DELETE_ARRAY(pIB->tabEdge.Ptr());
        pIB->tabEdge.Ptr() = pE;
        DWORD dwTemp = dwNumEdge;
        dwNumEdge = pIB->tabEdge.Size(); // Take its old value
        pIB->tabEdge.Size() = dwTemp;
    }

    for(i=0; i<dwNumFace; i++) // For each face
    {
        const DWORD id = i*3;
        if(pIB->pIB[id+0] > pIB->pIB[id+1])
        {
            if( FindCreateMatchingEdge(pIB, i, pIB->pIB[id+0], pIB->pIB[id+1], dwNumEdge) )
                dwNumEdge++;
        }
        if(pIB->pIB[id+1] > pIB->pIB[id+2])
        {
            if( FindCreateMatchingEdge(pIB, i, pIB->pIB[id+1], pIB->pIB[id+2], dwNumEdge) )
                dwNumEdge++;
        }
        if(pIB->pIB[id+2] > pIB->pIB[id+0])
        {
            if( FindCreateMatchingEdge(pIB, i, pIB->pIB[id+2], pIB->pIB[id+0], dwNumEdge) )
                dwNumEdge++;
        }
    }

    // The compute of the number of Edge that will be added is generally too big...
    pIB->tabEdge.Size() = dwNumEdge;

    return S_OK;
}













bool XKGraphics::WriteEdge(IB* pIB, DWORD idTri, WORD idVert1, WORD idVert2, DWORD idEdge)
{
    // Init the new Edge
    Edge& NewEdge = pIB->tabEdge[idEdge];
    NewEdge.idVert1 = idVert1;
    NewEdge.idVert2 = idVert2;
    NewEdge.idTri1 = idTri;
    NewEdge.idTri2 = -1; // No second triangle yet

    return true;
}







bool XKGraphics::MatchingEdgeNotFound(IB* pIB, DWORD idTri, WORD idVert1, WORD idVert2)
{
    Edge* pEdge = pIB->tabEdge.Ptr();
    for(DWORD i=0; i<pIB->tabEdge.Size(); i++)
    {
        if(pEdge[i].idVert1==idVert2 && pEdge[i].idVert2==idVert1)
        {
            if( pEdge[i].idTri2==-1 || pEdge[i].idTri2==(long)idTri )
            {
                pEdge[i].idTri2 = idTri; // Found and OK
                return true;
            }
        }
    }

    // Not found
    return false;
}






bool XKGraphics::FindCreateMatchingEdge(IB* pIB, DWORD idTri, WORD idVert1, WORD idVert2, DWORD idEdge)
{
    Edge* pEdge = pIB->tabEdge.Ptr();
    for(DWORD i=0; i<pIB->tabEdge.Size(); i++)
    {
        if(pEdge[i].idVert1==idVert2 && pEdge[i].idVert2==idVert1)
        {
            if( pEdge[i].idTri2==-1 || pEdge[i].idTri2==(long)idTri )
            {
                pEdge[i].idTri2 = idTri; // Found and OK
                return false;
            }
#ifdef _DEBUG
            else
            {
                BeginFunction("FindCreateMatchingEdge()");
                String s;
                s << _T("Edge[") << i << _T("] has more than two tris :")
                  << _T("  idVert1 = ") << pEdge[i].idVert1
                  << _T("; idVert2 = ") << pEdge[i].idVert2
                  << _T("; idTri1 = ")  << pEdge[i].idTri1
                  << _T("; idTri2 = ")  << pEdge[i].idTri2
                  << _T("; idTri3 = ")  << idTri;
                Warning(s);
            }
#endif
        }
    }

    // Not found; so we create it as a new one
    return WriteEdge(pIB, idTri, idVert1, idVert2, idEdge);
}

_________________
"Haa, the good old time" - KPixel.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 22, 2004 3:41 pm 
Offline
Plasma Spammer
User avatar

Joined: Fri Jun 27, 2003 10:24 pm
Posts: 2508
Location: Munich, Germany
KPixel wrote:
der_ton wrote:
Yes the viewer does analyze and correct (if necessary) the topology of the mesh. That´s necessary because of the reorganization for tangent-space bumpmapping anyway (vertices have to be duplicated at the symmetry-seam, and duplicated vertices that are not located at the seam have to be welded).


I think this correction also has an impact on the shadow because in mine, there is some holes here and there (eg: between the head and the body); they aren't very visible, but they are :D

Hum, for your appendedge(), you should really pre-compute the size of numedges[meshnumber] because realloc() can be call MANY times and it kills performance for big models;


The vertex indices that the edge generation algorithm relies on is actually not the corrected mesh. And my viewer doesn´t show these holes. Or does it? I don´t think so.

Yeah the realloc can kill performance (startup time performance that is), but I think it allocates a little more space to prevent exactly that problem. It´s not really that bad and I was too lazy to precalculate the number of edges.
BTW, an even faster way (and easier to implement) would be to allocate enough space to hold numtris*3 edges (the theoretical maximum number of edges), then fill up the edgelist, and in the end, when we know the actual number of edges, realloc that memory with that (smaller) size. Sorry, I should have told you that before...

_________________
Image Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 22, 2004 11:57 pm 
Offline
is connecting to Doom3world.org

Joined: Thu Apr 22, 2004 11:51 pm
Posts: 7
der_ton, how are you handling the changes in the tangent space (normal, tangent, binormal vectors for each vertex) when animating the MD5?

Do you compute those vectors every frame? Or do you transform them using bones and weights as with the vertex' position?


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 23, 2004 6:15 am 
Offline
Plasma Spammer
User avatar

Joined: Fri Jun 27, 2003 10:24 pm
Posts: 2508
Location: Munich, Germany
Mig wrote:
der_ton, how are you handling the changes in the tangent space (normal, tangent, binormal vectors for each vertex) when animating the MD5?

Do you compute those vectors every frame? Or do you transform them using bones and weights as with the vertex' position?


I compute them every frame from the changing vertex coordinates. Computing them for the bind-pose at startup and then re-deriving them from the bone animations is more cpu work, I think. But it should work, too.

_________________
Image Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 23, 2004 10:59 am 
Offline
has joined the game

Joined: Mon Aug 04, 2003 5:46 pm
Posts: 38
Location: Somewhere else
der_ton wrote:
The vertex indices that the edge generation algorithm relies on is actually not the corrected mesh. And my viewer doesn´t show these holes. Or does it? I don´t think so.


Yes, It doesn't show them...

Try the file "models/weapons/impfireball/anims/tumble.md5mesh"
You will see a "little" problem with "one-winged" edge...
You can see the biggest square when you rotate at right (that's not a problem because you just need to turn of the culling to see always see it).
But if you move the light in the other side, the shadow of this square doesn't show up (it is only visible in the first side...) :wink:

That's probably why I have "holes" between parts of the body; that's where "one-winged" edges are located.


Oh, another funny thing : "Alt-F4" don't work because "F4" is used :D

_________________
"Haa, the good old time" - KPixel.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 23, 2004 11:34 am 
Offline
Plasma Spammer
User avatar

Joined: Fri Jun 27, 2003 10:24 pm
Posts: 2508
Location: Munich, Germany
The shadowing issue is there because my viewer doesn´t know that the thing should be "twosided". If it´s just onesided, then the light doesn´t get blocked from the other side, and that´s correct behaviour for that case. I´m almost sure Doom3 behaves like that too.

Yep, the Alt-F4 thing... I reworked that whole windows messaging stuff and I´m even adding a GUI. Alt-F4 works in my current version.

And I apologize to every user who was annoyed that you can´t close the viewer with the 'x' button (Win98 issue only?). Stupid Win32 API (or stupid der_ton, you decide). Until the next version update use Escape instead.

_________________
Image Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 23, 2004 8:49 pm 
Offline
is connecting to Doom3world.org

Joined: Thu Apr 22, 2004 11:51 pm
Posts: 7
der_ton wrote:
I compute them every frame from the changing vertex coordinates. Computing them for the bind-pose at startup and then re-deriving them from the bone animations is more cpu work, I think. But it should work, too.


Do you really think it is more CPU work? Most of the work is at startup as you have said.

I think computing those vectors and averaging (smoothing) them every frame requieres much more CPU power than just transforming them with the bones' rotation matrices (no translation, as done with the vertex position).

But what bothers me is: what is the right way to add together the rotations of the multiple weights that affect the vertex. Maybe something like this?

Code:
// Per vertex
vertex.tangent = vector3::zero;
vector3 vec;
for( int i = 0; i < vertex.numweights; i++ )
{
  weight = A weight affecting this vertex
  bone = The bone this weight is attached to
  vec = bone.rotationMatrix.transform(vertex.originalTangent);
  vertex.tangent += vec * weight.bias;
}
vertex.tangent.normalize();


Doing the same to normals and binormals (I dont compute the binormal in the vertex shader).

Not yet at home to implement and test it :(


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 23, 2004 9:45 pm 
Offline
Plasma Spammer
User avatar

Joined: Fri Jun 27, 2003 10:24 pm
Posts: 2508
Location: Munich, Germany
Mig wrote:
But what bothers me is: what is the right way to add together the rotations of the multiple weights that affect the vertex.


I´m not sure about that either. If you tested your idea, let me know about the result. One issue I´m spotting:
Code:
vec = bone.rotationMatrix.transform(vertex.originalTangent);

That transformation matrix used here would not simply be the animated matrix of the bone, but it would be the matrix that rotates the bindmat into the animated matrix. There´s some work involved in calculating that.

My somewhat naive approach would have been:
generate the 3 tangentspace vectors for each vertex for the bindpose at startup, and then do a similar thing as with the weighted bonespace vertex coordinates:
For each bone that affects that vertex, inverse-transform the normal, binormal and tangent by the bone-matrix (rotation-part only ofcourse). Store these "bone-space tangent space vectors".
At runtime, calculate the weighted sum of these bone-space tangentspace vectors by the animated bone matrices.
I´m almost sure that is more expensive than calculating the tangentspace from scratch. But it depends on what we are comparing it to.[/code]

_________________
Image Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5


Top
 Profile  
 
 Post subject:
PostPosted: Sun Apr 25, 2004 6:22 am 
Offline
is connecting to Doom3world.org

Joined: Thu Apr 22, 2004 11:51 pm
Posts: 7
It's working!

This is what i'm doing:

- Computing tangent space vectors from bind position at startup.
- For each bone, saving the inverse of the bind rotation matrix ("invBindMatrix"), at startup too.
- When preparing the animated bones, in adittion to the matrix used to rotate vertices ("vertMatrix"), i compute another matrix that I can use to rotate tangent space vectors ("tangMatrix = invBindMatrix * vertMatrix"). As der_ton noted, we must undo the original rotations of the space where the tangent vectors were created in the first place, before doing the usual rotations of the animations.
- Vertex position is rotated with vertMatrix, and tangent space vectors with tangMatrix, averaging between the several weights affecting the vertex.
- Done :D

I found hard to determine what is faster:

Pre-computed tangent space vectors:
- One more matrix multiplication (invBindMatrix * vertMatrix) per bone.
- Three more vector rotations (normal, binormal, tangent) per vertex(alright, for each weight affecting the vertex :P).

Tangent space vectors computed per frame, with pre-computed smooth groups:
- A good amount of vector math to get the tangent and the binormal, per vertex. Then adding the vector to the associated smooth group. (*)
- Normalizing vectors for each smooth group, then copying to each vertex.

I'm using an old and slow Duron processor (it does work!!... with the help of a Radeon 9600 pro :P), and that's why I care much about it.

(*) der_ton, could you tell me how much do you do here?
I must confess that I'm (temporally) using the nvidia mesh mender library for the task after getting frustated with my first attempts of doing it by myself. (looser)


------------------

Anyway, my animations are not perfect.

An unofficial MD5 specifications file says "if you are half way between the start and end time, you are also half way through the keys", so I'm getting the key index with this:

float keyTime = ((currentTime - channel.startTime) / (channel.endTime - channel.startTime)) * (float)channel.numKeys;
int keyIndex = (int)keyTime;

I have disabled any attempt of interpolation between keys until I get the animations going on fine.

I think my problem has to do with the channel frame rate and range values, because I haven't found a use for them yet :P


Top
 Profile  
 
 Post subject:
PostPosted: Sun Apr 25, 2004 12:06 pm 
Offline
Plasma Spammer
User avatar

Joined: Fri Jun 27, 2003 10:24 pm
Posts: 2508
Location: Munich, Germany
That unofficial md5 specification is wrong. I will post some info on that keyframe range thing soon, it´s going to be in that "details about md5anim" thread.

The per vertex tangentspace calculation is a bit complicated in my viewer. I had a problem with that before, and then around christmas I decided to redesign the whole thing and that also resulted in a 200-300% speed gain.

To make it short, at loadtime I determine vertices that need to be cloned (because of discontinuity of tangentspace at some vertices). Then a table is made that stores for each triangle, which vertices receive the triangle´s normal and tangent/binormal (these are two different things, because cloned vertices receive the normal, but not necessarily the tangentspace vectors. If you want to put it that way, you end up with different kinds of smoothing groups for normals or for tangentspace).
At runtime, first the normals and tangentspace vectors for the triangles are calculated. Then they are distributed to the vertices according to the precalculated tables.
Then the normals are normalized, and binormals and tangents are normalized and made perpendicular to the normal.

_________________
Image Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 29, 2004 3:37 pm 
Offline
Plasma Spammer
User avatar

Joined: Fri Jun 27, 2003 10:24 pm
Posts: 2508
Location: Munich, Germany
Mig wrote:
An unofficial MD5 specifications file says "if you are half way between the start and end time, you are also half way through the keys", so I'm getting the key index with this:

float keyTime = ((currentTime - channel.startTime) / (channel.endTime - channel.startTime)) * (float)channel.numKeys;
int keyIndex = (int)keyTime;


I wrote something about that a while ago:
http://www.doom3world.org/phpbb2/viewto ... light=keys

Does that help?

_________________
Image Staff
Modelviewer | 3DSMax<->MD5 | Blender<->MD5


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 131 posts ]  Go to page Previous  1, 2, 3, 4, 5, 6, 7  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 0 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group