J’essaie de configurer un rendu d’objects en deux étapes dans un moteur 3D sur lequel je travaille, écrit en C ++ avec DirectX9 pour faciliter la transparence (et d’autres éléments). Je pensais que tout fonctionnait bien jusqu’à ce que je remarque une certaine dodgynicité sur le bord des objects rendus avant les objects utilisant cette méthode en deux étapes.
La méthode en deux étapes est simple:
Dessinez le modèle sur une texture de la même taille hors écran (“côté”) à l’aide du même zbuffer (aucun MSAA n’est utilisé nulle part)
Dessinez une texture hors-écran (“latérale”) par-dessus la cible de rendu principale avec un mélange approprié, sans test alpha ni écriture.
Dans l’image ci-dessous, la vue de gauche présente le rendu en deux étapes de l’object gris (un lampadaire), le corps situé devant celui-ci étant directement rendu à la texture cible. La vue de droite est avec le rendu en deux étapes désactivé, donc les deux sont rendus directement sur la surface cible.
Lors d’une inspection minutieuse, tout se passe comme si la texture latérale était compensée par exactement 1 pixel “bas” et 1 pixel “droit” lorsqu’elle était restituée sur la surface cible (mais restituée correctement sur place). Ceci peut être vu dans une superposition de la texture hors écran (que je D3DXSaveTextureToFile
mon programme d’écrire dans un fichier bitmap via D3DXSaveTextureToFile
) sur une capture d’écran ci-dessous.
Une dernière image pour que vous puissiez voir d’où vient le bord de la texture latérale (c’est parce que le rendu de la texture latérale utilise le test z). La gauche est courte à l’écran, la droite est la texture latérale (comme indiqué ci-dessus).
Tout cela me porte à croire que ma “superposition” n’est pas très efficace. Le code qui rend la texture latérale sur la cible de rendu principale est présenté ci-dessous (notez que la même fenêtre est utilisée pour tous les rendus de scènes (écran activé et désactivé)). L’object “effect” est une instance d’un wrapper fin sur LPD3DXEFFECT
, avec le champ “effect” (désolé pour le nommage LPD3DXEFFECT
) étant un LPD3DXEFFECT
lui-même.
void drawSideOver(LPDIRECT3DDEVICE9 dxDevice, drawData* ddat) { // "ddat" drawdata contains lots of render state information, but all we need here is the handles for the targetSurface and sideSurface D3DXMATRIX idMat; D3DXMasortingxIdentity(&idMat); // create identity masortingx dxDevice->SetRenderTarget(0, ddat->targetSurface); // switch to targetSurface dxDevice->SetRenderState(D3DRS_ZENABLE, false); // disable z test and z write dxDevice->SetRenderState(D3DRS_ZWRITEENABLE, false); vertexOver overVerts[4]; // create square overVerts[0] = vertexOver(-1, -1, 0, 0, 1); overVerts[1] = vertexOver(-1, 1, 0, 0, 0); overVerts[2] = vertexOver(1, -1, 0, 1, 1); overVerts[3] = vertexOver(1, 1, 0, 1, 0); effect.setTexture(ddat->sideTex); // use side texture as shader texture ("tex") effect.effect->SetTechnique("over"); // change to "over" technique effect.setViewProj(&idMat); // set viewProj to identity masortingx so 1/-1 map directly effect.effect->CommitChanges(); setAlpha(dxDevice); // this sets up the alpha blending which works fine UINT numPasses, pass; effect.effect->Begin(&numPasses, 0); effect.effect->BeginPass(0); dxDevice->SetVertexDeclaration(vertexDecOver); dxDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, overVerts, sizeof(vertexOver)); effect.effect->EndPass(); effect.effect->End(); dxDevice->SetRenderState(D3DRS_ZENABLE, true); // revert these so we don't mess everything up drawn after this dxDevice->SetRenderState(D3DRS_ZWRITEENABLE, true); }
La définition du côté C ++ de la structure et du constructeur VertexOver (côté HLSL montré quelque part ci-dessous):
struct vertexOver { public: float x; float y; float z; float w; float tu; float tv; vertexOver() { } vertexOver(float xN, float yN, float zN, float tuN, float tvN) { x = xN; y = yN; z = zN; w = 1.0; tu = tuN; tv = tvN; } };
L’inefficacité à recréer et à transmettre les sumts au GPU, chaque dessin mis à part, ce que je veux vraiment savoir, c’est pourquoi cette méthode ne fonctionne pas tout à fait , et s’il existe de meilleures méthodes pour superposer de telles textures avec un mélange alpha ne montrera pas ce problème
Je pensais que l’échantillonnage de texture importait peut-être un peu dans cette affaire, mais le choix d’options ne semblait pas beaucoup aider (par exemple, utiliser un filtre LINEAR le rend flou comme on pourrait s’y attendre, ce qui implique que le décalage n’est pas aussi clair. -couper comme une différence de 1 pixel). Code shader:
struct VS_Input_Over { float4 pos : POSITION0; float2 txc : TEXCOORD0; }; struct VS_Output_Over { float4 pos : POSITION0; float2 txc : TEXCOORD0; float4 altPos : TEXCOORD1; }; struct PS_Output { float4 col : COLOR0; }; Texture tex; sampler texSampler = sampler_state { texture = ;magfilter = NONE; minfilter = NONE; mipfilter = NONE; AddressU = mirror; AddressV = mirror;}; // side/over shaders (these make up the "over" technique (pixel shader version 2.0) VS_Output_Over VShade_Over(VS_Input_Over inp) { VS_Output_Over outp = (VS_Output_Over)0; outp.pos = mul(inp.pos, viewProj); outp.altPos = outp.pos; outp.txc = inp.txc; return outp; } PS_Output PShade_Over(VS_Output_Over inp) { PS_Output outp = (PS_Output)0; outp.col = tex2D(texSampler, inp.txc); return outp; }
J’ai cherché environ un “Blended Blit” ou quelque chose mais je ne trouve rien, et d’autres recherches connexes ont seulement fait apparaître des forums qui impliquent que le rendu d’un quad avec une projection orthographique est la meilleure façon de procéder.
Désolé si j’ai donné trop de détails sur ce problème, mais c’est à la fois intéressant et énervant. Tous vos commentaires seraient grandement appréciés.
Il me semble que votre problème est le mappage de texels en pixels. Vous devez décaler un quadrilatère aligné avec un demi-pixel pour faire correspondre les texels directement aux pixels d’écran. Ce numéro est expliqué ici: Mappage direct de Texels en pixels (MSDN)
Pour quiconque heurtant un mur similaire, mon problème spécifique a été résolu en ajustant les valeurs U et V des vertices envoyées au GPU pour les sortingangles de texture superposés, comme suit:
for (int i = 0; i < 4; i++) { overVerts[i].tu += 0.5 / (float)ddat->targetVp->Width; // ddat->targetVp is the viewport in use, and the viewport is the same size as the texture overVerts[i].tv += 0.5 / (float)ddat->targetVp->Height; }
Voir Mappage direct Texels to Pixels , fourni par la réponse de Gnietschow, pour une explication de la raison pour laquelle cela a du sens.