Nous développons un RPG de haut en bas en utilisant XNA. Récemment, nous avons rencontré un problème lors de l'écriture du code d'affichage de nos cartes. Lorsque l'on dessine la carte en vue descendante avec une matrice de transformation normale, tout semble aller bien. Lorsque l'on utilise une matrice de transformation non plate, par exemple en comprimant le haut ou le bas pour imiter la profondeur, des lignes noires (lignes lorsque le haut ou le bas, colonne lorsque la gauche ou la droite est comprimée) qui se déplacent lorsque la caméra change de position, apparaissent. Le mouvement et le placement semblent être aléatoires. (Image fournie plus bas.)
Informations générales
Les cartes sont constituées de tuiles. La texture originale comporte des tuiles de 32x32 pixels. Nous dessinons les tuiles en créant 2 triangles et en affichant une partie de la texture originale sur ces triangles. Un shader fait cela pour nous. Il y a trois couches de triangles. D'abord, nous dessinons toutes les tuiles opaques et tous les pixels opaques de toutes les tuiles semi-opaques et partiellement transparentes, puis toutes les tuiles et pixels semi-opaques et partiellement transparents. Cela fonctionne bien (mais lorsque nous zoomons par un facteur à virgule flottante, il arrive que des lignes de couleur se trouvent entre les rangées et/ou les colonnes de tuiles).
États de rendu
Nous utilisons le même rasterizerState pour toutes les tuiles et nous passons de l'un à l'autre lorsque nous dessinons des tuiles solides ou semi-transparentes.
_rasterizerState = new RasterizerState();
_rasterizerState.CullMode = CullMode.CullCounterClockwiseFace;
_solidDepthState = new DepthStencilState();
_solidDepthState.DepthBufferEnable = true;
_solidDepthState.DepthBufferWriteEnable = true;
_alphaDepthState = new DepthStencilState();
_alphaDepthState.DepthBufferEnable = true;
_alphaDepthState.DepthBufferWriteEnable = false;
Dans l'ombre, nous définissons le SpriteBlendMode comme suit :
La première couche solide 1 utilise
AlphaBlendEnable = False;
SrcBlend = One;
DestBlend = Zero;
Tous les autres calques solides et transparents (dessinés plus tard) utilisent la fonction
AlphaBlendEnable = True;
SrcBlend = SrcAlpha;
DestBlend = InvSrcAlpha;
D'autres shaders l'utilisent également. Le site SpriteBatch
pour le SpriteFonts
utilisé, utilise le paramètre par défaut.
Texture générée
Certaines tuiles sont générées à la volée et enregistrées dans un fichier. Le fichier est chargé lorsque la carte est chargée. Cela se fait à l'aide d'un RenderTarget
créé comme suit :
RenderTarget2D rt = new RenderTarget2D(sb.GraphicsDevice, 768, 1792, false,
SurfaceFormat.Color, DepthFormat.None);
sb.GraphicsDevice.SetRenderTarget(rt);
Une fois généré, le fichier est sauvegardé et chargé (afin de ne pas le perdre lors de la réinitialisation de l'appareil, car il ne sera plus sur un support de type RenderTarget
). J'ai essayé d'utiliser le mipmapping, mais il s'agit d'un spritesheet. Il n'y a aucune information sur l'emplacement des tuiles, donc le mipmapping est inutile et n'a pas résolu le problème.
Sommets
Nous passons en revue toutes les positions. Pas encore de points flottants ici, mais la position est un Vector3 (Float3).
for (UInt16 x = 0; x < _width; x++)
{
for (UInt16 y = 0; y < _heigth; y++)
{
[...]
position.z = priority; // this is a byte 0-5
Pour positionner les tuiles, le code suivant est utilisé :
tilePosition.X = position.X;
tilePosition.Y = position.Y + position.Z;
tilePosition.Z = position.Z;
Comme vous le savez, les flottants sont de 32 bits, avec 24 bits pour la précision. La valeur maximale de z est de 8 bits (5 = 00000101). Les valeurs maximales pour X et Y sont de 16 bits resp. 24 bits. J'ai supposé que rien ne pouvait se passer en termes de points flottants.
this.Position = tilePosition;
Lorsque les sommets sont définis, il le fait de la manière suivante (afin qu'ils partagent tous la même position de tuile)
Vector3[] offsets = new Vector3[] { Vector3.Zero, Vector3.Right,
Vector3.Right + (this.IsVertical ? Vector3.Forward : Vector3.Up),
(this.IsVertical ? Vector3.Forward : Vector3.Up) };
Vector2[] texOffset = new Vector2[] { Vector2.Zero, Vector2.UnitX,
Vector2.One, Vector2.UnitY };
for (int i = 0; i < 4; i++)
{
SetVertex(out arr[start + i]);
arr[start + i].vertexPosition = Position + offsets[i];
if (this.Tiles[0] != null)
arr[start + i].texturePos1 += texOffset[i] * this.Tiles[0].TextureWidth;
if (this.Tiles[1] != null)
arr[start + i].texturePos2 += texOffset[i] * this.Tiles[1].TextureWidth;
if (this.Tiles[2] != null)
arr[start + i].texturePos3 += texOffset[i] * this.Tiles[2].TextureWidth;
}
Shader
Le shader peut dessiner des tuiles animées et des tuiles statiques. Les deux utilisent l'état d'échantillonnage suivant :
sampler2D staticTilesSampler = sampler_state {
texture = <staticTiles> ; magfilter = POINT; minfilter = POINT;
mipfilter = POINT; AddressU = clamp; AddressV = clamp;};
Le shader ne définit pas d'états d'échantillonnage différents, ce que nous ne faisons pas non plus dans notre code.
A chaque passage, nous coupons à la valeur alpha (pour ne pas avoir de pixels noirs) en utilisant la ligne suivante
clip(color.a - alpha)
Alpha est égal à 1 pour la couche solide 1, et presque 0 pour toute autre couche. Cela signifie que s'il y a une fraction d'alpha, elle sera dessinée, sauf sur la couche inférieure (car nous ne saurions pas quoi en faire).
Appareil photo
Nous utilisons une caméra pour simuler une observation de haut en bas des tuiles, les faisant apparaître plates, en utilisant la valeur z pour les superposer par des données de superposition externes (les 3 couches ne sont pas toujours dans le bon ordre). Cela fonctionne également très bien. La caméra met à jour la matrice de transformation. Si vous vous demandez pourquoi elle a une structure bizarre comme ceci.AddChange - le code est en double tampon (cela fonctionne aussi). La matrice de transformation est formée comme suit :
// First get the position we will be looking at. Zoom is normally 32
Single x = (Single)Math.Round((newPosition.X + newShakeOffset.X) *
this.Zoom) / this.Zoom;
Single y = (Single)Math.Round((newPosition.Y + newShakeOffset.Y) *
this.Zoom) / this.Zoom;
// Translation
Matrix translation = Matrix.CreateTranslation(-x, -y, 0);
// Projection
Matrix obliqueProjection = new Matrix(1, 0, 0, 0,
0, 1, 1, 0,
0, -1, 0, 0,
0, 0, 0, 1);
Matrix taper = Matrix.Identity;
// Base it of center screen
Matrix orthographic = Matrix.CreateOrthographicOffCenter(
-_resolution.X / this.Zoom / 2,
_resolution.X / this.Zoom / 2,
_resolution.Y / this.Zoom / 2,
-_resolution.Y / this.Zoom / 2,
-10000, 10000);
// Shake rotation. This works fine
Matrix shakeRotation = Matrix.CreateRotationZ(
newShakeOffset.Z > 0.01 ? newShakeOffset.Z / 20 : 0);
// Projection is used in Draw/Render
this.AddChange(() => {
this.Projection = translation * obliqueProjection *
orthographic * taper * shakeRotation; });
Raisonnement et flux
Il existe 3 couches de données sur les tuiles. Chaque tuile est définie par IsSemiTransparent
. Lorsqu'une tuile est IsSemiTransparent
il doit être dessiné après quelque chose qui n'est pas IsSemiTransparent
. Les données de la tuile sont empilées lorsqu'elles sont chargées sur une SplattedTile
instance. Ainsi, même si la couche 1 des données de la tuile est vide, la couche 1 de la SplattedTile
aura des données de tuiles dans la première couche, (étant donné qu'au moins une couche a des données de tuiles). La raison en est que le Z-buffer
ne sait pas avec quoi se mélanger s'ils sont dessinés dans l'ordre, car il se peut qu'il n'y ait pas de pixels solides derrière lui.
Les couches n'ont PAS de valeur z, les données individuelles des tuiles en ont une. Lorsqu'il s'agit d'une tuile de sol, elle a une valeur z. Priority = 0
. Ainsi, les tuiles avec le même Priority
nous sommes classés par couche (ordre de dessin) et par opacité (semi-transparent, après opaque). Les tuiles ayant une priorité différente seront dessinées en fonction de leur priorité.
Le premier calque solide n'a pas de pixels de destination, donc je l'ai réglé sur DestinationBlend.Zero
. Il n'a pas non plus besoin AlphaBlending
puisqu'il n'y a rien à mélanger. Les autres couches (5, 2 solides, 3 transparentes) peuvent être dessinées lorsqu'il y a déjà des données de couleur et qu'il faut les mélanger en conséquence.
Avant d'itérer à travers les 6 passages, la fonction projection matrix
est réglé. Lorsque vous n'utilisez pas de cône, cela fonctionne. En utilisant un cône, ça ne fonctionne pas.
Le problème
Nous voulons imiter un peu plus de profondeur en appliquant le taper, en utilisant la matrice some. Nous avons essayé plusieurs valeurs mais ceci est un exemple :
new Matrix(1, 0, 0, 0,
0, 1, 0, 0.1f,
0, 0, 1, 0,
0, 0, 0, 1);
L'écran (tout ce qui a une hauteur de 0, tout ce qui est plat) sera comprimé. Plus le y est bas (plus haut sur l'écran), plus il est écrasé. Cela fonctionne, mais maintenant des lignes noires aléatoires apparaissent presque partout. Cela semble exclure quelques tuiles, mais je ne vois pas quelle est la corrélation. Nous pensons que cela pourrait avoir quelque chose à voir avec l'interpolation ou les mipmaps.
Et voici une image pour vous montrer ce dont je parle : .
Les tuiles non affectées semblent être des tuiles statiques qui ne sont PAS sur la couche inférieure. Cependant, les tuiles transparentes qui se superposent à celles-ci présentent d'autres artefacts graphiques. Il leur manque des lignes (les rangées sont donc simplement supprimées). J'ai marqué ce texte parce que je pense qu'il est un indice de ce qui se passe. Le site vertical apparaissent si je mets le mip mag and minfilter
a Linear
.
Voici une image agrandie (zoom dans le jeu), montrant l'artefact sur les tuiles de la couche 2 ou 3.
Nous avons déjà essayé
-
mipfilter
enPoint
oLinear
- Réglage de
GenerateMipMaps
sur les textures originales - Réglage de
GenerateMipMaps
sur les textures générées (constructeur true flag deRenderTarget
) - L'activation du mipmapping (qui n'a donné que plus d'artefacts lors du zoom arrière, parce que j'ai été mipmapping d'un spritesheet .
- Ne pas dessiner les couches 2 et 3 (ce qui fait que TOUTES les tuiles ont des lignes noires)
DepthBufferEnable = false
- Régler toutes les couches solides sur
SrcBlend = One;
DestBlend = Zero;
- Régler toutes les couches solides sur
ScrBlend = SrcAlpha;
DestBlend = InvSrcAlpha;
- Ne pas dessiner la couche transparente (les lignes sont toujours là).
- Suppression de
clip(opacity)
dans leshader
. Cela ne supprime que certaines lignes. Nous poursuivons nos recherches dans ce domaine. - Je recherche le même problème sur msdn, stackoverflow et en utilisant google (sans succès).
Quelqu'un reconnaît-il ce problème ? Pour finir, nous appelons le SpriteBatch
APRÈS avoir dessiné les tuiles, et utiliser un autre Shader
pour les avatars (ne posent aucun problème, car ils ont une hauteur > 0). Cela annule-t-il notre sampler state
? Ou... ?