View Full Version : Tangent Space Per Geom Vertex

04 April 2012, 02:54 PM
Hi, I want to calculate the tangent space pe geom vertex.
As I see gutil has a function ComputeTangentAndBinormal as stated here

But, it computes the tangent space per TVFace vertex.
I need to convert it to Geom Vertex.

As I understand I can liniar interpolate the bitangent and tanget and the normalize of the neighbour TVFace vert.

If somebody has done this, can he share some ideas about implemeting this optimal.

Also how to convert the TVFace vertex index to Geom vertex index?

I'm working with MNMesh.


04 April 2012, 03:32 PM
Also how to convert the TVFace vertex index to Geom vertex index?

you do it on a face by face basis, as shown in the second function you
linked to....

for( int f=0; f<numFaces; f++ )
Face& geomFace = geomFaces[f];
geomTri[0] = geomVerts[ geomFace.v[0] ];
geomTri[1] = geomVerts[ geomFace.v[1] ];
geomTri[2] = geomVerts[ geomFace.v[2] ];

TVFace& mapFace = mapFaces[f];
mapTri[0] = mapVerts[ mapFace.t[0] ];
mapTri[1] = mapVerts[ mapFace.t[1] ];
mapTri[2] = mapVerts[ mapFace.t[2] ];

ComputeTangentAndBinormal( mapTri, geomTri, basisVec );

btw is this for an exporter or something internal ?

04 April 2012, 03:39 PM
This is for something internal for some deformation in tangent space

one more problem is that calculation uses 3 verts, 3 tverts, ... but I'm doing it for poly, so its normal to assume that if face deg > 3 the first 3 vertex will make the correct tangent space?

04 April 2012, 04:40 PM
i don't think you can make that assumption at all, what about this case ?

you could use MNMesh::OutToTri(Mesh &tmesh) to create a temp mesh and process that to generate the Tangents and binormals. Or process the Polys based on their triangulation. Personally I would go for outtotri method and use the prewritten code directly though you may want to ask theArea OP about the FindIndex function call.

04 April 2012, 09:56 PM
Ah, ok, thanks.
Btw, do you know a method to convert from tvert index to geom vert? I see that in NormalMap sample its using some "magic" FindIndex that I'm not sure if its what I need

04 April 2012, 07:01 AM
i think you'll get most your answers from maxsdk/samples/materials/NormalBump/ in the sdk

from VNormal.cpp

// VNormalChannel
// Holds a normal vector and related data at each vertex & smoothing group;
// that is, one piece of data per vertex per smoothing group at the vertex.

after a quick browse of the code, you should just be able to drop VNormalMgr (VNormal.h & VNormal.cpp) straight into your plugin as is. Call it via the interface mechanism as per the normal render version.

might need to add an additional VNormalMgr::InitTangentBasis to initialize from your MNMesh but should be pretty straightforward ;).

04 April 2012, 10:51 AM
I though about it, but I don't understand the
GetTangentBasis( int faceIndex, int vertIndex, int faceVertIndex ) method...

its needs a faceIndex, vertIndex and faceVertIndex.
So I need somehow to convert the vertIndex to all this data?
or for each neighbour face, I need to find the vertex index on the face of my vertex, get the tangent, add it to the sum, then normalize it?

If its like this then there is still this question: "Is there a method to get all TVerts indexes from a VertexIndex?"

04 April 2012, 11:02 AM
you just need to convert VNormal.h & VNormal.cpp to work from your MNMesh as opposed to a ShadeContext/RenderInstance method it current uses in the NormalShader example. 99% of the code is written for you....

04 April 2012, 11:15 AM
I understood that VNormal has the calculations for tangents,
But I don't see any method here to get the tangets per vertex... I only see a method to get the tanget given a Geom VertexIndex, Face Index, TVertex Index.

I didn't found any method to get the TVerts indices from Vertex Indices...

If you still say that VNormal has it all, can you give me a simple exemple how to acces the tangent from VNormal classes (doesn't mater which class, I will convert it for my needs if it will work) using only a vertex index?

I see that in this loop:

for( int v=0; v<numVerts; v++ ) {
if( (v&0xFF)==0 )
vertBase[v>>8] = itemBase = itemCount;
WORD offset = (WORD)(itemCount-itemBase);
vertOffset[v] = offset;
itemCount += grouping.GetGroupCount(v);

ItemCount can be greater than vertex count...

ItemCount is used as TangentArray count

this->numItems = itemCount;
tangentBasisSet.SetCount( numItems );

04 April 2012, 11:30 AM
void VNormalMgr::InitTangentBasis( ShadeContext& sc, int mapChannel ) {
int nodeID = sc.NodeID();
RenderInstance* inst = sc.globContext->GetRenderInstance(nodeID);
if( (inst==NULL) || (inst->mesh==NULL) ) return;


InitChannel_Internal( *(inst->mesh), nodeID, mapChannel );
VNormalChannel* channel = GetChannel_Internal( nodeID, mapChannel );
if( !channel->ValidTangentBasis() ) // initialized while waiting on the semaphore?
channel->InitTangentBasis( *(inst->mesh), inst->objToCam, mapChannel );


void VNormalMgr::GetTangentBasis( ShadeContext& sc, int mapChannel, TangentBasis& tangentBasis ) {
DbgAssert( sc.globContext!=NULL );
int nodeID = sc.NodeID();
RenderInstance* inst = sc.globContext->GetRenderInstance(nodeID);
if( (inst==NULL) ||
(inst->mesh==NULL) ||
NULL == inst->mesh->faces ||
inst->mesh->getNumFaces() <= sc.FaceNumber() )


VNormalChannel* channel = GetChannel_Internal( nodeID, mapChannel );
while( (channel==NULL) || (!channel->ValidTangentBasis()) ) {
lock.ExitRead(); //release lock while initializing
InitTangentBasis( sc, mapChannel );
lock.EnterRead(); //regain lock after initializing
channel = GetChannel_Internal( nodeID, mapChannel );

int faceIndex = sc.FaceNumber();
Face& f = inst->mesh->faces[faceIndex];
DWORD smGroup = f.smGroup;
if( smGroup==0 )
tangentBasis = channel->GetTangentBasis( faceIndex, 0, 0 );
else {
DWORD *v = f.v;
TangentBasis& b0 = channel->GetTangentBasis( faceIndex, v[0], 0 ); //returned in camera space
TangentBasis& b1 = channel->GetTangentBasis( faceIndex, v[1], 1 ); //returned in camera space
TangentBasis& b2 = channel->GetTangentBasis( faceIndex, v[2], 2 ); //returned in camera space

Point3 bary = sc.BarycentricCoords();
tangentBasis.uBasis = ((bary.x*b0.uBasis) + (bary.y*b1.uBasis) + (bary.z*b2.uBasis)).Normalize();
tangentBasis.vBasis = ((bary.x*b0.vBasis) + (bary.y*b1.vBasis) + (bary.z*b2.vBasis)).Normalize();


these are the functions you change, the first one initialize the VNormalMgr from a mesh, the second one returns the tangents of a face depending where on the face you want them (if you don't know BarycentricCoords there plenty online) In your case you don't need that the values will be b0,b1,b2.

04 April 2012, 12:05 PM
Ok, just to make things clear.
I understand the VNormal class (almost all except the strange thing inside the FindIndex method).
I know that its doing a great job getting the Tangents per face.

What I need is having this data... to get the tanget per vertex...

something like this:

for (int i = 0; i < mesh.numv; i++) {
Tanget t = GetTangent(i);

What I need is logic behind the GetTangent(i);

As I understand you suggest the idea to implement something like this?:

GetTangent(i) {
returnTangent = 0;
nFaces = getNeighbourFacesOfVert(i);
for (f = 0; f < nFaces.count; f++)
for (j = 0; j < 3; j++)
if (nFaces[f].VertexIndex[j] == i)
returnTangent += channel->GetTangentBasis( f, TvFaceVert[f].TVert[j], j );

return Normalize(returnTangent)

something like this?

04 April 2012, 12:27 PM
I'm not suggesting anything like that. I'm suggesting...

copying VNormal.h & VNormal.cpp into your plugin project

add something like
VNormalMgr* vnmgr;
to your plugin class

in the plugin constructor add
vnmgr = GetVNormalMgr();
void VNormalMgr::InitTangentBasis( ShadeContext& sc, int mapChannel )
to something like
void VNormalMgr::InitTangentBasis( MNMesh& mnmsh, int mapChannel )
with the necessary changes to the code (see MNMesh::OutToTri(Mesh & tmesh) ( . And Call this function whenever you need to generate the Tangents.
vnmgr->InitTangentBasis( myMNMesh, 1);
void VNormalMgr::GetTangentBasis( ShadeContext& sc, int mapChannel, TangentBasis& tangentBasis )

to something like
void VNormalMgr::GetTangentBasis( int face, int mapChannel, TangentBasis& v1, TangentBasis& v2, TangentBasis& v3)
again with the required edits in code...
then you just get the tangents with something like...
for(int f=0;f < mesh->numfaces;f++)
// do stuff with tangents

i was forgetting.. and add
to the plugin destructor

04 April 2012, 01:17 PM
I feel a little unconfortable because I'm wasting your time.
We are repeating the same on the last posts.
I understood what you mean from your first post.
I will try to explain one more time what I need.

Here is the image

So the data that I need to process is Geom Vert.

VNormal.cpp calculates data on TFaces and TVerts

If I will use Classes from VNormal I need somehow to convert my GeomVert indeces to TVert indeces as they difer.

From your last post we see that we still getting data per face not per GeomVert
for(int f=0;f < mesh->numfaces;f++) {
vnmgr->GetTangentBasis(f,1,v1tangent,v2tangent,v3tangent) ; // do stuff with tangents

if the order of v1tanget, v2tangent, v3tangent coresponds to the GeomFaces vertex indices
then getting the tangent per GeomVert will look like I wrote in the post above

GetTangent(i) {
returnTangent = 0;
nFaces = getNeighbourFacesOfVert(i);
for (f = 0; f < nFaces.count; f++)
for (j = 0; j < 3; j++)
if (nFaces[f].VertexIndex[j] == i)
returnTangent += channel->GetTangentBasis( f, TvFaceVert[f].TVert[j], j );

return Normalize(returnTangent)

replacing it with your last code will look like

GetTangent(i) {
returnTangent = 0;
nFaces = getNeighbourFacesOfVert(i);

for(int f=0;f < mesh->numfaces;f++) {

vnmgr->GetTangentBasis(f,1,vtangent[0],vtangent[1],vtangent[2]) ;
// do stuff with tangents
for (j = 0; j < 3; j++)
if (nFaces[f].VertexIndex[j] == i)
returnTangent += vtangent[i]
return Normalize(returnTangent)

So this function will work as it should? or it could be something else.

Basically I'm looking for a method that will work like
where i is the GeomVertex index

All my problem consist of getting the tangents per Geom Vertex

VNormal uses the tangents per TFace and computes tangents per TVerts... in Normal Map case its ok, cause the rendering uses per face information and interpolation.
In my case I need only the geom vertecies

Also one more problem with VNormal is that its interpolating the tangent space based on smoothing groups. But this is not such a big problem because I can modify it to not do so.

04 April 2012, 01:34 PM
you just do it on a per face basis....

tvface.v1 = geoface.v1 = tangentface.v1
tvface.v2 = geoface.v2 = tangentface.v2
tvface.v3 = geoface.v3 = tangentface.v3

04 April 2012, 01:39 PM
This is the answer I was for but aren't TVert indeces of TFace different than Geom Indeces?

tvface.v1 != geoface.v1
tvface.v1 = tangentface.v1

geoface.vertNum < TVertNum

for example in the picture above there are 12 TVerts and only 5 GeomVerts

04 April 2012, 01:48 PM
TVface1 vert1 is the same vert as Geometryface1 vert1
TVface1 vert2 is the same vert as Geometryface1 vert2
TVface1 vert3 is the same vert as Geometryface1 vert3

04 April 2012, 04:12 PM
sorry for the previous comment, I though there was geom.verts not geomFace

So that still means that I need to get the neighbour faces of my absolute vert, to find the coresponding vert on the geom face, so I can get the TVert index on the TVFace to get Tangents to interpolate for each neigbour face

CGTalk Moderation
04 April 2012, 04:12 PM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.