PDA

Voir la version complète : Ombre ZFail


ShadowTzu
03/11/2005, 10h44
coucou,
Alors j'ai un bug de zbuffer, je pense, en passant ma gestion des ombres de zpass en zfail.

le bon rendu, zpass ou zfail avec le zbuffer en Greater mais il y a le bug quand la camera est dans l'ombre:
http://shadowtzu.free.fr/ao/ombre/screen2.jpg

zfail buggué, pas de bug quand la camera est dans l'ombre, mais toujours ce probleme de zbuffer:
http://shadowtzu.free.fr/ao/ombre/screen1.jpg

coté code:

Device.RenderState.StencilEnable = True
Device.RenderState.AlphaTestEnable = False
Device.RenderState.ZBufferWriteEnable = False
Device.RenderState.ShadeMode = ShadeMode.Flat
Device.RenderState.StencilFunction = Compare.Always
Device.RenderState.StencilFail = StencilOperation.Keep
Device.RenderState.StencilPass = StencilOperation.Keep

Device.RenderState.ReferenceStencil = 0
Device.RenderState.StencilMask = -1
Device.RenderState.StencilWriteMask = -1

Device.RenderState.AlphaBlendEnable = True
Device.RenderState.SourceBlend = Blend.Zero
Device.RenderState.DestinationBlend = Blend.One

''''
ConstructionOmbre
'''''

Device.RenderState.CullMode = Cull.Clockwise
Device.RenderState.StencilZBufferFail = StencilOperation.Increment
Device.DrawUserPrimitives(PrimitiveType.TriangleList, Tab_Ombre(Index).Nbr_VertDiv3, Tab_Ombre(Index).vertices)

Device.RenderState.CullMode = Cull.CounterClockwise
Device.RenderState.StencilZBufferFail = StencilOperation.Decrement
Device.DrawUserPrimitives(PrimitiveType.TriangleList, Tab_Ombre(Index).Nbr_VertDiv3, Tab_Ombre(Index).vertices)

'[...] RenderState remis par défaut


Rendu de l'ombre:

Device.RenderState.ZBufferEnable = False
Device.RenderState.StencilEnable = True
Device.RenderState.FogEnable = False

Device.RenderState.AlphaBlendEnable = True
Device.RenderState.SourceBlend = Blend.SourceAlpha
Device.RenderState.DestinationBlend = Blend.InvSourceAlpha

Device.RenderState.CullMode = Cull.CounterClockwise
Device.RenderState.ReferenceStencil = 0
Device.RenderState.StencilFunction = Compare.Less
Device.RenderState.StencilZBufferFail = StencilOperation.Keep
Device.DrawUserPrimitives(PrimitiveType.TriangleStrip, 2, v)


Voila si quelqu'un a une idée de ce qui ne va pas?

Ruffi
03/11/2005, 16h15
J'ai une idée, mais que theorique : j'ai eu un cours parlant des shadow volume il y a 2 semaine mais je n'ai pas eu le temps de le mettre en place.

Mais il parlais de ton probleme, quand la camera est dans une ombre, car elle n'update pas le stencil buffer.

Mais, John Carmack (notre Dieu à tous, loué soit ton nom) a trouvé une solution. Je te file la partie de mon cours en parlant, sans pouvoir t'aider plus tant que je ne l'ai pas implementé moi meme :


The Depth-Fail Shadow Volume Algorithm

John Carmack introduced a reverse of the depth-pass algorithm. An 'first-cut' sequence is:

1) Render back faces. Increment stencil value for both depth-pass and depth-fail.
2) Render front faces of shadow volumes. Decrement stencil value for both depth-pass and depth-fail.
3) Render back faces of shadow volumes. Decrement stencil value for depth-pass. Do nothing for depth-fail.
4) Render front faces of shadow volumes. Increment stencil value for depth-pass. Do nothing for depth-fail.

The first two steps ensure positive values are left in the stencil buffer when the eye is inside a view volume. Otherwise the increment and decrement cancel each other out.


Final Depth-Fail Algorithm

1) Render back faces of shadow volumes. Increment stencil values for depth-fail. Do nothing for depth-pass. Disable updating of frame buffer and Z-buffer.
2) Render front faces of shadow volumes. Decrement stencil value for depth-fail. Do nothing for depth-pass. Disable updating of frame buffer and Z-buffer.


Shadow Volume Capping

If a shadow volume intersects the near plane of the view frustum, new polygons must be created to cap the shadow volume.

Capping polygons can be created by projecting the casters back faces onto the near plane and use these to close the shadow volume.
Ordering of vertices must be reversed to ensure outward facing normals.

Likewise front capping polygons can be extruded onto the back plane of the frustum with vertex order reversal to produce back capping polygons.


J'espere t'avoir été utile :D

ShadowTzu
03/11/2005, 16h21
oui c'est cette technique que j'utilise, appelé Carmack's reverse, ou Zfail
Mon problème peu venir du Shadow Volume Capping, mais je ne sais pas du tout comment le résoudre

Edit: Problème résolut :D
Cela venait bien de la construction de l'ombre, il fallait rajouter des "cap" (couvercle) aux 2 coté du volume d'ombre.

Loulou
03/11/2005, 18h37
A noter que si tu projètes à l'infini, tu peux te passer du back-cap.

Il existe plein plein d'optimisations pour les shadows volumes, si ça t'interesse je peux développer un peu.

kratolp
04/11/2005, 12h40
eh eh, oui le ZFail ne marche que sur des volumes fermes.
Une autre technique pour les shadow volumes c'est le ZP+
http://artis.inrialpes.fr/Publications/2005/HHLH05/

Mickael
04/11/2005, 14h17
Tu peux developper loulou ? :)

ShadowTzu
04/11/2005, 15h32
ne construire l'ombre que quand la source de lumière ou l'objet bouge, ou tout les X frames, par exemple. :)

Loulou
04/11/2005, 15h45
Ca fait déjà pas mal de temps, mais pêle-mêle :

- On peut n'utiliser le Z-Fail (qui je le rappelle est plus gourmand) que lorsque la caméra se trouve dans un shadow volume ; pour tout le reste on peut switcher en Z-Pass. Le test est lui plutôt simple, une intersection ray / box sur chaque objet devrait faire l'affaire.

- En Z-Fail, il vaut mieux utiliser une matrice de projection infinie. Ca permet de ne pas avoir de problème avec le far plane, de se passer du back cap, et d'extruder des triangles plutôt que des quads.

- Il est assez facile de faire un culling des shadow volumes. Par exemple pour une lumière omnidirectionnelle, on peut limiter l'extrusion à la portée de la source lumineuse. Pour une lumière directionnelle, on peut également virer pas mal de calculs inutiles avec de simples tests ray / box. Pour une lumière omni encore, on peut facilement savoir si le shadow volume d'un objet donné sera à l'écran ou non.

- Ne pas oublier les vertex shaders pour l'extrusion. Plus coûteux en mémoire mais mais nettement plus avantageux, surtout avec les capacités du hardware actuel.

- Les external triangles, qui permettent de gérer les lumières omni et directionnelles de la même manière. Un external triangle est en fait un triangle dont au moins l'un des sommets a une composante w négative : ça permet de modéliser "l'inverse" de ce triangle, à savoir un quad. C'est un peu compliqué à piger au début, mais c'est vraiment l'optimisation que je préfère.

- Une technique vue sur gamedev.net il me semble : le silouhete tracking, pour accélerer la détection de la silouhete. Le principe est simple : au lieu de faire une détection complète à chaque frame, on effectue une détection incrémentale basée sur la silouhete précédente et le déplacement de la lumière ou de l'objet.

- On peut utiliser les TRIANGLE_FAN lorsqu'il n'y a pas de back cap.

- On peut gratter un peu en utilisant le two-sided stencil buffer sur les cartes qui le supportent.

- Technique qui permet d'économiser pas mal aussi : utiliser des versions low poly des modèles pour fabriquer le shadow volume. Attention cependant, si on ne respecte pas certaines conditions ça peut mener à des résultats incorrects.

J'ai pas encore eu l'occasion d'étudier le Z-Pass+, mais ça avait l'air pas mal aussi.


Je pense synthétiser tout ça dans un bon gros tutoriel, quand je serai arrivé aux shadow volumes dans ma série de tutos sur la création d'un moteur 3D.

Ruffi
12/11/2005, 16h30
Je commence à reflechir à comment je vais gerer les shadow volumes pour mon moteur :

J'ai fait une version simplifié pour tester le zpass et le zfail en créant le shadow volume à partir d'un simple triangle.
Pour le coup, j'ai compris le probleme du capping qu'avait ShadowTzu.

Maintenant je reflechit à comment creer les shadow volume de plus gros models.
J'ai lu dans "ShaderX2: Introductions & Tutorials with DirectX 9"qu'il y a 2 façon de gerer ça :
- avec le CPU : on calcule la silouette de l'objet par raport a la lumiere, et on l'extrude.
- avec le GPU : on créé un créé un nouveau mesh basé sur l'original, et on lui ajoute des polygones à chaque bord. Le shader extrudera ces polygone afin de creer le shadow volume. C'est la methode employé dans les sample du SDK de DirectX ( http://msdn.microsoft.com/library/default.asp?url=/library/en-us/directx9_c/directx/graphics/TutorialsAndSamples/Samples/ShadowVolume.asp )

Je pense que la version GPU est plus indiqué, surtout si le mesh est skinné par shaders. D'ailleur, ce dernier cas risque d'etre assez trivial à implementer. :)

Du coup, je me demande quel solution tu a utilisé pour Tzu3D.

ShadowTzu
13/11/2005, 17h50
celle du sdk directx :)
(sans shader)

Ruffi
02/12/2005, 01h07
Je me met (enfin) à coder mes ShadowVolume, par shader.

Sur tout les tutoriaux, que ce soit en zpass ou zfaill, par shader ou CPU, ils ne parlent jamais de comment rendre l'ombre un fois le stencil remplit.

Avant, j'affichait un quad noir avec de la transparence. Mais il y avait un probleme : le caping de devant fait que l'objet à l'origine de l'ombre est entierement ombré.

En regardant comment faisait le sdk de DirectX, j'ai vu qu'il faisait 2 rendu de la scene :
le premier juste avec la lumiere ambiante
( rendu des shadow volume dans le stencil)
la deusieme avec les lumieres, mais sans l'ambiante, en utilisant le stencil.

Bon voila quoi... rendre 2 fois la scene... :00000023:
Bon, je peut chercher à optimiser en n'affichant pas les objet entierement dans l'ombre durant le deusieme rendu, mais vu leur faible nombre ça serai pas grand chose.
Ou alors rendre la scene une seule fois mais en 2 passes dans le shader, en les affichant dans 2 endroits differents (backbuffer et dans une texture par exemple) qu'on fusionera selon le stencil une fois qu'il aura été calculé. (idée que j'ai eu en tapant ce post :D , mais qui a surement deja été tésté)

Comment gerrez vous ça ?

Loulou
02/12/2005, 11h57
Il y a deux manières de rendre les ombres :

1- La manière très facile mais très incorrecte ; sera vite limitée si tu veux gérer plus d'une lumière, ou des lumières de différentes couleurs. C'est l'histoire du quad que tu rends sur l'écran pour foncer les zones d'ombre (il y a une feinte toute bête pour résoudre ton problème, mais je ne m'en rappelle plus).

2- La manière très peu performante, mais physiquement correcte : le rendu multi-passes (utilisé dans Doom 3 par exemple). En gros au lieu de foncer les zones d'ombre, tu vas partir d'une scène noire et illuminer les zones éclairées. Très peu performant car une passe par lumière. Par contre le résultat est très chouette.

janta
02/12/2005, 14h43
2- La manière très peu performante, mais physiquement correcte : le rendu multi-passes (utilisé dans Doom 3 par exemple). En gros au lieu de foncer les zones d'ombre, tu vas partir d'une scène noire et illuminer les zones éclairées. Très peu performant car une passe par lumière. Par contre le résultat est très chouette.
Et puis comme ca apres tu n'as pas besoin d'allumer les radiateurs chez toi en hiver :) ;) :D

Ruffi
02/12/2005, 17h36
La technique du SDK de directX est proche de la deusieme technique, mise a part qu'elle n'est pas multi-passe, mais plutot "mutli-rendu".

J'ai lu, je ne sais plus trop où, que faire plusieures passes dans un shader etait peu gourmant car les cartes graphiques etaient optimisé pour.
Une pass ambiante suivi d'une passe par lumiere pour chaque objet serait donc mieux que de reaficher tout la scene a chaque fois.

Par contre, il faudrai un buffer pour la version sombre, et un autre par lumiere.
Il ne me semble pas que DirectX permette de creer plusieurs backbuffer

grob1212
02/12/2005, 17h44
Et puis comme ca apres tu n'as pas besoin d'allumer les radiateurs chez toi en hiver :) ;) :D


Et si tu veux carrément la cheminée en direct, il te reste touujours le photon mapping sur GPU ! :D

Loulou
02/12/2005, 19h52
Par contre, il faudrai un buffer pour la version sombre, et un autre par lumiere.
Il ne me semble pas que DirectX permette de creer plusieurs backbuffer
Si bien sûr. Plus précisément, il te faut une texture de rendu.

Je ne me souviens plus du lien, mais à l'époque j'avais trouvé un mini-article très simple décrivant en détail chaque passe de l'algorithme.

En gros, il faut rendre la scène texturée dans ton color buffer, puis additionner les contributions de chaque lumière dans une texture de rendu separée, que tu vas finalement moduler (multiplier) avec ton color buffer pour avoir le résultat final.

yoyonel
03/12/2005, 00h11
Juste un ptit mot sur le sample de DirectX.
Je vous conseille de regarder leur option (tres intéressante d'ailleurs) d'évaluation du FillRates des volumes d'ombres (du moins leurs enveloppes).

Avec "leur méthode", ils ont tres rapidement un nombre tres élévé de recouvrement (leur échelle va jusqu'à plus de 71 recouvrements !).
Cela vient directement du fait qu'ils extrudent l'ensemble des triangles (ou aretes de triangles) qui ne percoivent pas la lumiere. Ca simplifie certe les calculs (absence de calcul de silhouettes de contours) mais ca augmente rapidement le nombre de polygone et également (et ca c'est plus critique) le fillrate de ses volumes d'ombres.
Je pense qu'il faut prendre en compte assez rapidement ce facteur de FillRate, car ... c'est vraiment tres violent :-D.

Par contre je suis encore en train de lire le tuto. et le source du sample, ca se trouve je suis a coté de la plaque (pour l'extrusion de l'ensemble des triangles unlight) mais je suis quasi sure de ce que je dis (en concordant avec l'affichage du FillRate de leur application).

Juste pour terminer :-), leur méthode est loin d'etre bete, c'est une solution intéressante dans le cas d'une intégration complète sur GPU.
De plus, en ce moment je m'interesse aux modeles qui utilisent du vertex skinning pour l'animation, et gérer le shadow volume sur ces trucs ... c'est vraiment tres tendu (sans s'amuser a refaire le skinning sur CPU).
D'ailleurs j'ai toujours pas trouvé de solutions :-| ... Si quelqu'1 a une idée je suis preneur :p).
L'avantage de tout faire sur GPU, c'est justement d'utiliser toute les fonctionnalités hardware (en l'occurrence le skinning) ... mais bon les couts en terme de polygones et surtout en fillrate font vraiment trop mal pour que ca soit ma solution définitive :-|

Ruffi
26/12/2005, 23h49
Loulou, aurais-tu mis la main sur la feinte toute bête dont tu parlais plus tot ? :D

Sinon, je voudrai afficher un quad à l'ecran (pour les rendu dans les texture par exemple), en imaginant que les coordonées de l'ecran aillent de 0 à 1 en X et 0 à 1 en Y en rendu ortho.
Du coup, je pensait passer par un simple shader, mais je ne sais pas comment calculer calculer la matrice de projection.:00000017:

Ruffi
28/12/2005, 14h47
C'est bon, j'ai trouvé comment calculer une matrice de projection.
Pour ceux que ça pourait interesser :
http://graphics.ucsd.edu/courses/cse167_f05/CSE167_04.ppt

Ruffi
04/01/2006, 20h19
Voila, je me suis enfin mis au shadows volumes.
Mais ça marche pas terrible pour le moment :(
Si le rendu en affichant le shadow volume semble correct :

http://img250.imageshack.us/img250/8705/showvolumes2bb.th.jpg
http://img250.imageshack.us/img250/8705/showvolumes2bb.jpg

Des que je veux afficher le quad noir, il presente de serieux problemes d'affichage :
http://img257.imageshack.us/img257/2237/bug11vt.th.jpg
http://img257.imageshack.us/img257/2237/bug11vt.jpg
http://img435.imageshack.us/img435/3085/bug26hm.th.jpg
http://img435.imageshack.us/img435/3085/bug26hm.jpg


J'ai suivi l'exemple du SDK : mon code est quasiment similaire concernant la creation du shadow volume ainsi que son shader :

bool cStaticShadowVolume::create( cD3DGraphics* graphics,
std::string filename )
{
m_graphics = graphics;

LPD3DXBUFFER materialsBuffer = NULL;
DWORD numMaterial = 0;
LPD3DXMESH meshTMP = NULL;
LPD3DXMESH mesh = NULL;
HRESULT result;


//--------------------------------
// First, we create the mesh, by loading it and modifying it to the good vertex declaration
// We don't use the original mesh (the shadow caster) because maybe we will use a low polygoned mesh

// Loading the mesh
result = D3DXLoadMeshFromX( filename.data(), D3DXMESH_SYSTEMMEM, m_graphics->getDevice(),
NULL, &materialsBuffer, NULL, &numMaterial, &meshTMP ) ;
if( FAILED( result ) )
{
Log->sendFatalError( "cStaticShadowVolume::create() -> Cannot open mesh : " + filename );
return false;
}

// Converting the mesh
result = meshTMP->CloneMesh( D3DXMESH_32BIT, s_declaration, m_graphics->getDevice(), &mesh );
if( FAILED( result ) )
{
Log->sendFatalError( "cStaticShadowVolume::create() -> Cannot clone mesh : " + filename );
SafeRelease( meshTMP );
return false;
}
SafeRelease( meshTMP );



//--------------------------------
// Now, we need some information about the mesh, more exactly the adjency betweens faces

DWORD *adjency = new DWORD[ 3*mesh->GetNumFaces() ];
DWORD *pointRep = new DWORD[ mesh->GetNumVertices() ];

// Generate adjacency information
result = mesh->GenerateAdjacency( ADJACENCY_EPSILON, adjency );
if( FAILED( result ) )
{
SafeDeleteArray( adjency ); SafeDeleteArray( pointRep );
SafeRelease( mesh );
Log->sendFatalError( "cStaticShadowVolume::create() -> Cannot generate adjency : " + filename );
return false;
}

// Convert the adjency
result = mesh->ConvertAdjacencyToPointReps( adjency, pointRep );
if( FAILED( result ) )
{
SafeDeleteArray( adjency ); SafeDeleteArray( pointRep );
SafeRelease( mesh );
Log->sendFatalError( "cStaticShadowVolume::create() -> Cannot convert adjency to point reps : " + filename );
return false;
}
delete[] adjency;



//--------------------------------
// Create data needed to begin the shadow mesh creation

// Get the Vertex buffer and the index buffer
sShadowVertex* VBData = NULL;
DWORD* IBData = NULL;

mesh->LockVertexBuffer( 0, (LPVOID*)&VBData );
mesh->LockIndexBuffer( 0, (LPVOID*)&IBData );

if( !VBData || !IBData )
{
SafeDeleteArray( pointRep );
SafeRelease( mesh );
Log->sendFatalError( "cStaticShadowVolume::create() -> Cannot lock buffers : " + filename );
return false;
}

// Create the edge mapping array
DWORD numEdges = mesh->GetNumFaces() * 3;
cEdgeMapping *mapping = new cEdgeMapping[ numEdges ];
int numMaps = 0; // Number of entries that exist in mapping

// Create a new mesh
ID3DXMesh *newMesh;
result = D3DXCreateMesh( mesh->GetNumFaces()+numEdges*2, mesh->GetNumFaces()*3,
D3DXMESH_32BIT, s_declaration, m_graphics->getDevice(), &newMesh );
if( FAILED( result ) )
{
SafeDeleteArray( pointRep ); SafeDeleteArray( mapping );
mesh->UnlockVertexBuffer(); mesh->UnlockIndexBuffer();
SafeRelease( mesh );
Log->sendFatalError( "cStaticShadowVolume::create() -> Cannot create new mesh" );
return false;
}

// Get the Vertex buffer and the index buffer of this new mesh
sShadowVertex* newVBData = NULL;
DWORD* newIBData = NULL;
sShadowVertex* nextVertex = NULL; // will store the location in the VB to write the next vertex to
int nextIndex = 0; // will store the index in the IB where the next vertex index will be stored

newMesh->LockVertexBuffer( 0, (LPVOID*)&newVBData );
newMesh->LockIndexBuffer( 0, (LPVOID*)&newIBData );

if( !newVBData || !newIBData )
{
SafeDeleteArray( pointRep ); SafeDeleteArray( mapping );
mesh->UnlockVertexBuffer(); mesh->UnlockIndexBuffer();
SafeRelease( mesh ); SafeRelease( newMesh );
Log->sendFatalError( "cStaticShadowVolume::create() -> Cannot lock buffers : " + filename );
return false;
}

// set these buffer to zero
ZeroMemory( newVBData, newMesh->GetNumVertices()*newMesh->GetNumBytesPerVertex() );
ZeroMemory( newIBData, sizeof(DWORD)*newMesh->GetNumFaces()*3 );
nextVertex = newVBData;



//--------------------------------
// Create the shadow volume ( without patching non-shared egdes)

// iterate through the faces
for( uint face=0; face<mesh->GetNumFaces(); ++face )
{

//--------------------------------
// Set the 3 vertices in the news VB and IB

// Copy the vertex data for all 3 vertices
CopyMemory( nextVertex, VBData+IBData[face*3] , sizeof(sShadowVertex) );
CopyMemory( nextVertex+1, VBData+IBData[face*3+1], sizeof(sShadowVertex) );
CopyMemory( nextVertex+2, VBData+IBData[face*3+2], sizeof(sShadowVertex) );

// Write out the face
newIBData[ nextIndex++ ] = face*3;
newIBData[ nextIndex++ ] = face*3+1;
newIBData[ nextIndex++ ] = face*3+2;

// Compute the face normal and assign it to the normals of the vertices.
D3DXVECTOR3 v1, v2; // v1 and v2 are the edge vectors of the face
D3DXVECTOR3 vNormal;
v1 = *(D3DXVECTOR3*)(nextVertex + 1) - *(D3DXVECTOR3*)nextVertex;
v2 = *(D3DXVECTOR3*)(nextVertex + 2) - *(D3DXVECTOR3*)(nextVertex + 1);
D3DXVec3Cross( &vNormal, &v1, &v2 );
D3DXVec3Normalize( &vNormal, &vNormal );

nextVertex->normal = vNormal;
(nextVertex+1)->normal = vNormal;
(nextVertex+2)->normal = vNormal;

nextVertex += 3;

//--------------------------------
// Add the face's edges to the edge mapping table


//**** Edge 1 ****//

int index;
int vertIndex[3] = { pointRep[ IBData[ face*3 ] ],
pointRep[ IBData[ face*3+1 ] ],
pointRep[ IBData[ face*3+2 ] ] };
index = findEdgeInMappingTable( vertIndex[0], vertIndex[1], mapping, numEdges );

if( index == -1 )
{
SafeDeleteArray( pointRep ); SafeDeleteArray( mapping );
mesh->UnlockVertexBuffer(); mesh->UnlockIndexBuffer(); newMesh->UnlockVertexBuffer(); newMesh->UnlockIndexBuffer();
SafeRelease( mesh ); SafeRelease( newMesh );
Log->sendFatalError( "cStaticShadowVolume::create() -> finding edge in mapping table failed : " + filename );
return false;
}

if( mapping[ index ].m_oldEdge[0] == -1 && mapping[ index ].m_oldEdge[1] == -1 )
{
// No entry for this edge yet. Initialize one.
mapping[ index ].m_oldEdge[0] = vertIndex[0];
mapping[ index ].m_oldEdge[1] = vertIndex[1];
mapping[ index ].m_newEdge[0][0] = face*3;
mapping[ index ].m_newEdge[0][1] = face*3+1;

++numMaps;
}
else
{
mapping[ index ].m_newEdge[1][0] = face*3; // For clarity
mapping[ index ].m_newEdge[1][1] = face*3+1;

// First triangle
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[0][1];
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[0][0];
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[1][0];

// Second triangle
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[1][1];
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[1][0];
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[0][0];

// mapping[ index ] is no longer needed.
// Copy the last map entry over and decrement the map count.
mapping[ index ] = mapping[ numMaps-1 ];
FillMemory( &mapping[ numMaps-1 ], sizeof( mapping[ numMaps-1 ] ), 0xFF );
--numMaps;
}



//**** Edge 2 ****//

index = findEdgeInMappingTable( vertIndex[1], vertIndex[2], mapping, numEdges );

if( index == -1 )
{
SafeDeleteArray( pointRep ); SafeDeleteArray( mapping );
mesh->UnlockVertexBuffer(); mesh->UnlockIndexBuffer(); newMesh->UnlockVertexBuffer(); newMesh->UnlockIndexBuffer();
SafeRelease( mesh ); SafeRelease( newMesh );
Log->sendFatalError( "cStaticShadowVolume::create() -> finding edge in mapping table failed : " + filename );
return false;
}

if( mapping[ index ].m_oldEdge[0] == -1 && mapping[ index ].m_oldEdge[1] == -1 )
{
// No entry for this edge yet. Initialize one.
mapping[ index ].m_oldEdge[0] = vertIndex[1];
mapping[ index ].m_oldEdge[1] = vertIndex[2];
mapping[ index ].m_newEdge[0][0] = face*3+1;
mapping[ index ].m_newEdge[0][1] = face*3+2;

++numMaps;
}
else
{
mapping[ index ].m_newEdge[1][0] = face*3+1; // For clarity
mapping[ index ].m_newEdge[1][1] = face*3+2;

// First triangle
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[0][1];
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[0][0];
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[1][0];

// Second triangle
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[1][1];
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[1][0];
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[0][0];

// mapping[ index ] is no longer needed.
// Copy the last map entry over and decrement the map count.
mapping[ index ] = mapping[ numMaps-1 ];
FillMemory( &mapping[ numMaps-1 ], sizeof( mapping[ numMaps-1 ] ), 0xFF );
--numMaps;
}



//**** Edge 3 ****//

index = findEdgeInMappingTable( vertIndex[2], vertIndex[0], mapping, numEdges );

if( index == -1 )
{
SafeDeleteArray( pointRep ); SafeDeleteArray( mapping );
mesh->UnlockVertexBuffer(); mesh->UnlockIndexBuffer(); newMesh->UnlockVertexBuffer(); newMesh->UnlockIndexBuffer();
SafeRelease( mesh ); SafeRelease( newMesh );
Log->sendFatalError( "cStaticShadowVolume::create() -> finding edge in mapping table failed : " + filename );
return false;
}

if( mapping[ index ].m_oldEdge[0] == -1 && mapping[ index ].m_oldEdge[1] == -1 )
{
// No entry for this edge yet. Initialize one.
mapping[ index ].m_oldEdge[0] = vertIndex[2];
mapping[ index ].m_oldEdge[1] = vertIndex[0];
mapping[ index ].m_newEdge[0][0] = face*3+2;
mapping[ index ].m_newEdge[0][1] = face*3;

++numMaps;
}
else
{
mapping[ index ].m_newEdge[1][0] = face*3+2; // For clarity
mapping[ index ].m_newEdge[1][1] = face*3;

// First triangle
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[0][1];
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[0][0];
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[1][0];

// Second triangle
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[1][1];
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[1][0];
newIBData[ nextIndex++ ] = mapping[ index ].m_newEdge[0][0];

// mapping[ index ] is no longer needed.
// Copy the last map entry over and decrement the map count.
mapping[ index ] = mapping[ numMaps-1 ];
FillMemory( &mapping[ numMaps-1 ], sizeof( mapping[ numMaps-1 ] ), 0xFF );
--numMaps;
}
}


//--------------------------------
// Now, the entries in the edge mapping table represent non-shared edges.
// So the shadow mesh has some holes that we need to patch.


//--------------------------------
// First, we create a new mesh large enought for all news vertex and index datas

sShadowVertex* patchVBData = NULL;
DWORD* patchIBData = NULL;
ID3DXMesh* patchMesh = NULL;

result = D3DXCreateMesh( nextIndex/3 + numMaps*7, // the maximum number of faces
( mesh->GetNumFaces()+numMaps )*3, // the maximum number of indices
D3DXMESH_32BIT, s_declaration, m_graphics->getDevice(), &patchMesh );

if( FAILED( result ) )
{
SafeDeleteArray( pointRep ); SafeDeleteArray( mapping );
mesh->UnlockVertexBuffer(); mesh->UnlockIndexBuffer(); newMesh->UnlockVertexBuffer(); newMesh->UnlockIndexBuffer();
SafeRelease( mesh ); SafeRelease( newMesh );
Log->sendFatalError( "cStaticShadowVolume::create() -> cannot create new mesh " );
return false;
}


//--------------------------------
// Get the Vertex buffer and the index buffer of this patch mesh

patchMesh->LockVertexBuffer( 0, (LPVOID*)&patchVBData );
patchMesh->LockIndexBuffer( 0, (LPVOID*)&patchIBData );

if( !patchVBData || !patchIBData )
{
SafeDeleteArray( pointRep ); SafeDeleteArray( mapping );
mesh->UnlockVertexBuffer(); mesh->UnlockIndexBuffer(); newMesh->UnlockVertexBuffer(); newMesh->UnlockIndexBuffer();
SafeRelease( mesh ); SafeRelease( newMesh ); SafeRelease( patchMesh );
Log->sendFatalError( "cStaticShadowVolume::create() -> Cannot lock buffers : " + filename );
return false;
}


//--------------------------------
// Set vertex buffer and index buffer initials values

ZeroMemory( patchVBData, sizeof(sShadowVertex) * ( mesh->GetNumFaces() + numMaps ) * 3 );
ZeroMemory( patchIBData, sizeof(DWORD) * ( nextIndex + 3 * numMaps * 7 ) );

// Copy the data from one mesh to the other
CopyMemory( patchVBData, newVBData, sizeof(sShadowVertex)*mesh->GetNumFaces()*3 );
CopyMemory( patchIBData, newIBData, sizeof(DWORD)*nextIndex );

// release the new mesh, and set it as the patch mesh
newMesh->UnlockVertexBuffer();
newMesh->UnlockIndexBuffer();
newVBData = patchVBData;
newIBData = patchIBData;
SafeRelease( newMesh );
newMesh = patchMesh;
patchMesh = NULL;



//--------------------------------
// Now, we will patch the mesh by iterating through the mapping table
// - for each shared edge, we will generate a quad
// - for each non-shared edge, we patch the opening with new faces

int indiceNextVertex = mesh->GetNumFaces() * 3 ;

for( int i = 0; i < numMaps; ++i )
{

// If the 2nd new edge indexes is -1, this edge is a non-shared one.
if( mapping[i].m_oldEdge[0] != -1 &&
mapping[i].m_oldEdge[1] != -1 )
{
// We patch the opening by creating new faces.

if( mapping[i].m_newEdge[1][0] == -1 || // must have only one new edge
mapping[i].m_newEdge[1][1] == -1 )
{
// Find another non-shared edge that shares a vertex with the current edge.
for( int i2 = i+1; i2<numMaps; ++i2 )
{
if( mapping[i2].m_oldEdge[0] != -1 && // must have a valid old edge
mapping[i2].m_oldEdge[1] != -1 &&
( mapping[i2].m_newEdge[1][0] == -1 || // must have only one new edge
mapping[i2].m_newEdge[1][1] == -1 ) )
{
int vertShared = 0;
if( mapping[i2].m_oldEdge[0] == mapping[i].m_oldEdge[1] )
vertShared++;
if( mapping[i2].m_oldEdge[1] == mapping[i].m_oldEdge[0] )
vertShared++;

if( vertShared == 2 )
{
// These are the last two edges of this particular opening.
// Mark this edge as shared so that a degenerate quad can be created for it.

mapping[i2].m_newEdge[1][0] = mapping[i].m_newEdge[0][0];
mapping[i2].m_newEdge[1][1] = mapping[i].m_newEdge[0][1];
break;
}
else if( vertShared == 1 )
{
// "before" and "after" tell us which edge comes before the other.
int before, after;
if( mapping[i2].m_oldEdge[0] == mapping[i].m_oldEdge[1] )
{
before = i;
after = i2;
}
else
{
before = i2;
after = i;
}

// Found such an edge. Now create a face along with two degenerate quads from these two edges.
newVBData[ indiceNextVertex ] = newVBData[ mapping[ after ].m_newEdge[0][1] ];
newVBData[ indiceNextVertex+1 ] = newVBData[ mapping[ before ].m_newEdge[0][1] ];
newVBData[ indiceNextVertex+2 ] = newVBData[ mapping[ before ].m_newEdge[0][0] ];

// Recompute the normal
D3DXVECTOR3 v1 = newVBData[ indiceNextVertex+1 ].position - newVBData[ indiceNextVertex ].position;
D3DXVECTOR3 v2 = newVBData[ indiceNextVertex+2 ].position - newVBData[ indiceNextVertex+1 ].position;
D3DXVec3Normalize( &v1, &v1 );
D3DXVec3Normalize( &v2, &v2 );
D3DXVec3Cross( &newVBData[ indiceNextVertex ].normal, &v1, &v2 );
newVBData[ indiceNextVertex+1 ].normal = newVBData[ indiceNextVertex+2 ].normal = newVBData[ indiceNextVertex ].normal;

newIBData[ nextIndex ] = indiceNextVertex;
newIBData[ nextIndex+1 ] = indiceNextVertex+1;
newIBData[ nextIndex+2 ] = indiceNextVertex+2;

// 1st quad
newIBData[ nextIndex+3 ] = mapping[ before ].m_newEdge[0][1];
newIBData[ nextIndex+4 ] = mapping[ before ].m_newEdge[0][0];
newIBData[ nextIndex+5 ] = indiceNextVertex + 1;

newIBData[ nextIndex+6 ] = indiceNextVertex + 2;
newIBData[ nextIndex+7 ] = indiceNextVertex + 1;
newIBData[ nextIndex+8 ] = mapping[ before ].m_newEdge[0][0];

// 2nd quad
newIBData[ nextIndex+9 ] = mapping[ after ].m_newEdge[0][1];
newIBData[ nextIndex+10 ] = mapping[ after ].m_newEdge[0][0];
newIBData[ nextIndex+11 ] = indiceNextVertex;

newIBData[ nextIndex+12 ] = indiceNextVertex+1;
newIBData[ nextIndex+13 ] = indiceNextVertex;
newIBData[ nextIndex+14 ] = mapping[ after ].m_newEdge[0][0];

// Modify mapping entry i2 to reflect the third edge of the newly added face.

if( mapping[i2].m_oldEdge[0] == mapping[i].m_oldEdge[1] )
{
mapping[i2].m_oldEdge[0] = mapping[i].m_oldEdge[0];
}
else
{
mapping[i2].m_oldEdge[1] = mapping[i].m_oldEdge[1];
}
mapping[i2].m_newEdge[0][0] = indiceNextVertex+2;
mapping[i2].m_newEdge[0][1] = indiceNextVertex;

// Update next vertex/index positions

indiceNextVertex += 3;
nextIndex += 15;
break;
} // else if( vertShared == 1 )
} // if( mapping[i2].m_oldEdge[0] != -1 ...
} // for( int i2 = i+1; i2<numMaps; ++i2 )
} // if( mapping[i].m_newEdge[1][0] == -1 || ...
else
{
// This is a shared edge. Create the degenerate quad.

// First triangle
newIBData[ nextIndex++ ] = mapping[i].m_newEdge[0][1];
newIBData[ nextIndex++ ] = mapping[i].m_newEdge[0][0];
newIBData[ nextIndex++ ] = mapping[i].m_newEdge[1][0];

// Second triangle
newIBData[ nextIndex++ ] = mapping[i].m_newEdge[1][1];
newIBData[ nextIndex++ ] = mapping[i].m_newEdge[1][0];
newIBData[ nextIndex++ ] = mapping[i].m_newEdge[0][0];
}
} // if( mapping[i].m_oldEdge[0] != -1 && ...
} // for( int i = 0; i < numMaps; ++i )


//--------------------------------
// The output mesh may have an index buffer bigger than what is actually needed, so we create yet
// another mesh with the exact IB size that we need and output it.
// This mesh also uses 16-bit index if 32-bit is not necessary.

bool need32Bit = ( mesh->GetNumFaces() + numMaps )*3 > 65535;
result = D3DXCreateMesh( nextIndex/3, ( mesh->GetNumFaces()+numMaps) * 3,
D3DXMESH_WRITEONLY | ( need32Bit ? D3DXMESH_32BIT : 0 ),
s_declaration, m_graphics->getDevice(), &m_shadowMesh );
if( FAILED( result ) )
{
SafeDeleteArray( pointRep ); SafeDeleteArray( mapping );
mesh->UnlockVertexBuffer(); mesh->UnlockIndexBuffer(); newMesh->UnlockVertexBuffer(); newMesh->UnlockIndexBuffer();
SafeRelease( mesh ); SafeRelease( newMesh );
Log->sendFatalError( "cStaticShadowVolume::create() -> Failed to create final shadow mesh : " + filename );
return false;
}

//--------------------------------
// Lock the VB and IB of the final mesh

sShadowVertex* finalVBData = NULL;
WORD* finalIBData = NULL;
m_shadowMesh->LockVertexBuffer( 0, (LPVOID*)&finalVBData );
m_shadowMesh->LockIndexBuffer( 0, (LPVOID*)&finalIBData );

if( !finalVBData || !finalIBData )
{
SafeDeleteArray( pointRep ); SafeDeleteArray( mapping );
mesh->UnlockVertexBuffer(); mesh->UnlockIndexBuffer(); newMesh->UnlockVertexBuffer(); newMesh->UnlockIndexBuffer();
SafeRelease( mesh ); SafeRelease( newMesh ); SafeRelease( m_shadowMesh );
Log->sendFatalError( "cStaticShadowVolume::create() -> Failed to lock final shadow mesh : " + filename );
return false;
}

//--------------------------------
// Fill the final mesh

CopyMemory( finalVBData, newVBData, sizeof(sShadowVertex) * ( mesh->GetNumFaces()+numMaps ) * 3 );
if( need32Bit )
CopyMemory( finalIBData, newIBData, sizeof(DWORD) * nextIndex );
else
{
for( int i = 0; i < nextIndex; ++i )
finalIBData[i] = (WORD)newIBData[i];
}



//--------------------------------
// clean up

newMesh->UnlockVertexBuffer();
newMesh->UnlockIndexBuffer();
m_shadowMesh->UnlockVertexBuffer();
m_shadowMesh->UnlockIndexBuffer();
mesh->UnlockVertexBuffer();
mesh->UnlockIndexBuffer();
SafeRelease( newMesh );
SafeRelease( mesh );
SafeDeleteArray( mapping );


return true;

} // function create()

Loulou
04/01/2006, 20h31
Je n'ai pas regardé ton code, mais d'après les captures d'écran il y a de fortes chances pour que le problème vienne du calcul des arêtes et / ou des triangles à extruder. En l'occurence ils ne seraient pas tous dans le bon sens (parfois suffit d'inverser l'ordre de deux sommets pour que ça fonctionne).

Ou alors le modèle est mal défini à la base, mais étant donné qu'il s'agit d'un modèle du SDK je pense qu'on peut exclure cette hypothèse.

Ruffi
04/01/2006, 21h04
Effectivement, c'etait bien ça.
Maintenant ça marche nickel, reste plus qu'a optimiser le shader (enlever les if que ma carte graphique semble ne pas trop aimer )

(je vais coriger le code au cas où quelqu'un en aurait besoin)