Hax брет какой а... это вобще из другой оперы...
>Hax брет какой а... это вобще из другой оперы...
Очевидно, вы еще не сталкивались с этой проблемой.
tangent space vectors в вершинах (normal, tangent, binormal) должны быть такими, какие использовались при рассчете normals maps. Если посчитать normal maps, а потом использовать с tagent space vectors, рассчитанными другим алгоритмом - появляются отчетливо видимые стыки и искривления.
Собственно непонятно, как использовать normal maps, посчитанные в 3ds max/maya/zbrush, если неизвестно, какие там tanget space vectors.
Hax
Ещё раз... медленно... это вобще из другой оперы...
Разговор шел о экспорте нормалей. Это геометрический уровень. Пиксельные методы тут непричем. Совершенно.
Если очень хочется поговорить про tangent, binormal и normals maps, то tangent и binormal расчитываются для каждой вершини от нормали и текстурных координат карты нормалей. Тоесть нормаль является частью исходных данных но никак не результатом расчетов. Нормаль просто есть, такая какую создал художник. Точно так же как есть позиция вершины в пространстве и есть текстурные координаты. Ведь что такое тангент спейс по сути? Это матрицы 3х3 для каждой вершины, используя которые можно преобразовать вектор из пространства нормаль карты в пространство объекта, и мне кажется вполне логичным что вектор нормаль карты направленный вверх должен после преобразования перейти в вектор равный нормали в данной точке. Мне честно говоря всё равно как 3ds MAX и другое редакторы расчитывают нормаль карты, потому что я использую свой плагин. А Вам, стоит изменится чему то в алгоритмах той же ATI, придется подгонять свой экспортер под эти изменения. А они ведь могут быть недокументированы. Естественно что алгоритм расчета тангент спейса в генераторе нормаль карт и экспортере должен быть идентичен, и добится идентичности какраз проще всего используя свои утилиты, которые работают с УЖЕ экспортированной геометрией, или используют алгоритмы идентичные экспортеру.
Мы взяли ATI Normal mapper, т.к. :
1) художники сказали, что он их полностью устраивает;
2) он поставляется в исходниках;
3) писать свой normal mapper особого желания не было.
> или используют алгоритмы идентичные экспортеру.
Как раз это мы и делаем. Для ATI Normal mapper есть свой экспортер в формат NMF. Так вот, этот экспортер нормали пересчитывает. Поэтому наш экспортер в наш формат нормали тоже пересчитывает. Алгоритм взят из исходников NMFExport.
> А Вам, стоит изменится чему то в алгоритмах той же ATI, придется подгонять свой экспортер под эти изменения.
Точно с такой же вероятностью может измениться класс RNormal :)
Интерфейсы меняются реже(в идеале только расширяются) чем реализации...
MO
А ты проверял что на эти r-normals действует модификатор EditNormals? Я чё-то как нормали не кручу, одни и те же цифры выдаёт... Действуют только smooth группы. (скопировал твой код практически 1:1)
// вот такую клёвую функцию утащил с http://www.gamedev.ru/community/toolcorner/forum/?id=1149&page=1 Point3* MeshGetVertexNormal(Mesh* mesh, int faceNo, DWORD faceVertV0) { Face* f = &mesh->faces[faceNo]; DWORD smGroup = f->smGroup; int numNormals; Point3 *vertexNormal = new Point3; *vertexNormal = mesh->getNormal(f->v[faceVertV0]); RVertex* rv = &mesh->getRVert(f->v[faceVertV0]); //RVertex *rv = mesh->getRVert(Faces[f].v[i]); if(rv->ern) { for(unsigned int n = 0; n<(rv->rFlags&NORCT_MASK); n++) if(rv->ern[n].getSmGroup() == f->smGroup) { *vertexNormal=rv->ern[n].getNormal(); break; }; }; return vertexNormal; };
имхо для Edit Normals надо юзать функции интерфейса этого модификатора (enfn_get_normal, enfn_get_num_normals etc)
а я где-то выдрал вот этот код, и теперь похоже из за него поимел проблемы на голову
Point3 v; Point3 n; int smGroup = MaxMesh->faces[i].getSmGroup(); for( int j=0; j<3; j++) { int vi = MaxMesh->faces[i].v[j]; v = MaxMesh->verts[vi]; if( smGroup != 0) { n = MaxMesh->getRVertPtr( vi)->rn.getNormal( ); } else { n = MaxMesh->getNormal( vi)/* * ntm*/; } n = n.Normalize( );
народ!!!!! где искать правильный экспорт нормалей??????
я тут 3 дня мучаюсь.....
а тут смотрю в свой экспортер, обычный параллелепипед, его нормали, а там такой бред:
Normals =
0 -1 0
0 -1 0
0 -1 0
0 -1 0
0 1 0
0 1 0
0 1 0
0 1 0
Исправление к коду MO
DWORD smGroup = f->smGroup;
Point3 *vertexNormal = new Point3;
*vertexNormal = mesh->getFaceNormal(faceNo);
if(smGroup==0)
return vertexNormal;
RVertex* rv = mesh->getRVertPtr(f->v[faceVertV0]);
Тогда правильно экспортируются нормали для плоских поверхностей.
Festern
там еще большой вопрос что вызывать, когда нет группы сглаживания
MaxMesh->getFaceNormal(fi);
или
MaxMesh->getNormal(vi);
в обоих случаях есть артефакты.
я так до сих пор и не понял и нигде нету инфы
getFaceNormal() было бы логично потому что если нормаль ни с чем не сглаживается, она равномерно-одинакова
по всему треугольнику. Так же считают в Valve Software судя по исходникам их экспортера.
Это не избавляет от всех косяков, но уменьшает их количество=)
А артифакты есть и во вьювпорте максы, особенно при использовании DxShaderMaterial
В 2009й максе появился совсем непонятный глюк с нормалями в окне просмотра. К счастью нечастый.
Смотришь - всё сшито, smooth-группа одна, поверхность ровная, но угол одного из треугольников черный.
(в смысле освещается неправильно, нормаль не в ту сторону).
Кстати, как добраться до модификатора EditNormals (довольно полезный для лоуполи и чамферинга)?
Вот конечный вариант функции получения нормали.
Думаю, её стоит добавить в статью.
#include "MeshNormalSpec.h" Point3 MeshGetVertexNormal(Mesh* mesh, int faceNo, DWORD faceVertV0) { //EditNormals modifier MeshNormalSpec* specifiedNormals = mesh->GetSpecifiedNormals( ); if( specifiedNormals) { int normalID = specifiedNormals->GetNormalIndex( faceNo, faceVertV0); if( specifiedNormals->GetNormalExplicit( normalID ) ) return specifiedNormals->GetNormal( faceNo, faceVertV0); } Face* f = &mesh->faces[faceNo]; if( f->smGroup==0) return mesh->getFaceNormal( faceNo); RVertex* rv = mesh->getRVertPtr( f->v[faceVertV0]); if( rv->ern) { for( unsigned int n = 0; n<( rv->rFlags&NORCT_MASK); n++) { if( rv->ern[n].getSmGroup( ) & f->smGroup) { return rv->ern[n].getNormal( ); }; } } return rv->rn.getNormal( ); };
Проблемы были в
if(rv->ern[n].getSmGroup() & f->smGroup)
и
if(f->smGroup==0)
return mesh->getFaceNormal(faceNo);
Festern
а у меня при MaxMesh->getFaceNormal(fi) освещение становится размазанным как бы. т.е. как бы контраст пропадает и я думаю это не просто так.
потому как далеко не всегда нормаль вершин полика = нормаль полика, причем групп сглаживания там нет.
т.е. три нормали полика не всегда параллельны и это нормальная ситуация я уверен.
а с одной нормалью на 3 вершины начинают сильно выделятся отдельные полики и не происходит интерполяции освещения.
так что как хотите а я экпорчу нормаль вершины :)
Festern
к тому же у тя там ошибко похоже
MeshNormalSpec* specifiedNormals = MaxMesh->GetSpecifiedNormals(); if( specifiedNormals) { int normalID = specifiedNormals->GetNormalIndex( fi, crni); // может вернуть -1 if( normalID!=-1 && specifiedNormals->GetNormalExplicit( normalID)) { return specifiedNormals->GetNormal( fi, crni); } }
Festern
кста , а faceVertV0 это индекс угла? т.е. 0,1 или 2?
Да.
Как хочешь, экспорть=) Просто для справки как считаются нормали: сначала считаются нормали для полигонов,
т.е. как раз параллельные для каждой вершины полигона, потом сглаживаются нормали в каждой вершине для
всех полигонов, которым принадлежит эта вершина если их smgroup равны. smgroup причем у одного полигона
может быть и не одна, поэтому они записываются битами в 32х битном слове и сравниваются логическим и (&)
Тоесть если у полигона нет smgroup то он ни с кем не сглаживается и его нормали остаются параллельными.
У тебя в максе точно всё сшито и сглажено? Цель экспортера - добиться точной копии с картинкой во вьювпорте.
Про ошибку спасибо, -1 ещё ни разу не возвращал, но мало ли...
Вот код из smd-экспортера кстати...
DWORD smGroupFace = pface->getSmGroup(); // Get the 3 RVertex's for this face // NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug RVertex *prvertex0 = pmesh->getRVertPtr( iVertex0); RVertex *prvertex1 = pmesh->getRVertPtr( iVertex1); RVertex *prvertex2 = pmesh->getRVertPtr( iVertex2); // Find appropriate normals for each RVertex // A vertex can be part of multiple faces, so the "smoothing group" // is used to locate the normal for this face's use of the vertex. Point3 pt3Vertex0Normal; Point3 pt3Vertex1Normal; Point3 pt3Vertex2Normal; if ( smGroupFace) { pt3Vertex0Normal = Pt3GetRVertexNormal( prvertex0, smGroupFace); pt3Vertex1Normal = Pt3GetRVertexNormal( prvertex1, smGroupFace); pt3Vertex2Normal = Pt3GetRVertexNormal( prvertex2, smGroupFace); } else { pt3Vertex0Normal = pmesh->getFaceNormal( iFace ); pt3Vertex1Normal = pmesh->getFaceNormal( iFace ); pt3Vertex2Normal = pmesh->getFaceNormal( iFace ); } ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus orig normal 0" ); ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus orig normal 1" ); ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus orig normal 2" );
функция Pt3GetRVertexNormal
Point3 DumpModelTEP::Pt3GetRVertexNormal(RVertex *prvertex, DWORD smGroupFace) { // Lookup the appropriate vertex normal, based on smoothing group. int cNormals = prvertex->rFlags & NORCT_MASK; ASSERT_MBOX( ( cNormals == 1 && prvertex->ern == NULL) || ( cNormals > 1 && prvertex->ern != NULL), "BOGUS RVERTEX"); if ( cNormals == 1) return prvertex->rn.getNormal( ); else { for ( int irn = 0; irn < cNormals; irn++) if ( prvertex->ern[irn].getSmGroup( ) & smGroupFace) break; if ( irn >= cNormals) { irn = 0; // ASSERT_MBOX(irn < cNormals, "unknown smoothing group\n"); } return prvertex->ern[irn].getNormal( ); } }
Тема в архиве.