Tracer une ligne d'un pixel de large dans l'espace 3D

10

J'aimerais dessiner une ligne dans un espace 3D qui fait toujours exactement un pixel de largeur à l'écran, quelle que soit la distance de la caméra. (Et la même chose pour les points simples aussi).

Des conseils sur la façon dont je pourrais faire cela?

danbystrom
la source

Réponses:

24

Lignes de rendu - Méthode 1 (primitives)

Pour les lignes simples dans l'espace 3D, vous pouvez les dessiner à l'aide d'un LineListou LineStripprimitif. Voici la quantité minimale de code que vous devez ajouter à un projet XNA vide pour qu'il trace une ligne de (0,0,0) à (0,0, -50). La ligne devrait sembler avoir à peu près la même largeur, peu importe où se trouve la caméra.

// Inside your Game class
private BasicEffect basicEffect;
private Vector3 startPoint = new Vector3(0, 0, 0);
private Vector3 endPoint = new Vector3(0, 0, -50);

// Inside your Game.LoadContent method
basicEffect = new BasicEffect(GraphicsDevice);
basicEffect.View = Matrix.CreateLookAt(new Vector3(50, 50, 50), new Vector3(0, 0, 0), Vector3.Up);
basicEffect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45f), GraphicsDevice.Viewport.AspectRatio, 1f, 1000f);

// Inside your Game.Draw method
basicEffect.CurrentTechnique.Passes[0].Apply();
var vertices = new[] { new VertexPositionColor(startPoint, Color.White),  new VertexPositionColor(endPoint, Color.White) };
GraphicsDevice.DrawUserPrimitives(PrimitiveType.LineList, vertices, 0, 1);

J'ai essentiellement créé un simple BasicEffectpour conserver mes transformations de vue et de projection, et passé deux sommets (stockage de la position et de la couleur) à la GraphicsDevice.DrawUserPrimitivesméthode à rendre en tant que LineList.

Bien sûr, il existe de nombreuses façons de l'optimiser, dont la plupart impliquent la création d'un VertexBufferpour stocker tous les sommets et le regroupement d'autant de lignes que possible dans un seul appel Draw, mais cela n'a rien à voir avec la question.


Points de rendu - Méthode 1 (SpriteBatch)

Quant aux points de dessin, cela était facile à utiliser avec les sprites ponctuels mais ils ont été supprimés de XNA 4.0 . Il existe cependant quelques alternatives. Le moyen le plus simple consiste à créer un Texture2Dobjet blanc 1x1 et à le rendre SpriteBatchà l'emplacement de l'écran correct, que vous pouvez facilement trouver à l'aide de la méthode Viewport.Project .

Vous pouvez créer l' Texture2Dobjet requis comme ceci:

Texture2D pixel = new Texture2D(GraphicsDevice, 1, 1);
pixel.SetData(new [] { Color.White });

Et rendez-le à l'emplacement (x, y, z) comme ceci:

// Find screen equivalent of 3D location in world
Vector3 worldLocation = new Vector3(0, 0, 50);
Vector3 screenLocation = GraphicsDevice.Viewport.Project(worldLocation, projectionMatrix, viewMatrix, Matrix.Identity);

// Draw our pixel texture there
spriteBatch.Begin();
spriteBatch.Draw(pixel, new Vector2(screenLocation.X, screenLocation.Y), Color.White);
spriteBatch.End();

Lignes de rendu - Méthode 2 (SpriteBatch)

Alternativement, vous pouvez également tracer des lignes en utilisant un en SpriteBatchutilisant la technique décrite ici . Dans ce cas, il vous suffit de trouver les coordonnées d'espace d'écran pour les deux extrémités de la ligne 3D (en utilisant à nouveau Viewport.Project), puis de tracer une ligne régulière entre elles.


Points de rendu - Méthode 2 (petite ligne avec des primitives)

Dans les commentaires, eBusiness a soulevé la question suivante:

Qu'en est-il d'une ligne avec le même point de départ et de fin, cela ne produirait-il pas un point? Ou serait-il simplement invisible?

Je l'ai essayé et le rendu en LineListutilisant les mêmes points de début et de fin n'a entraîné aucun dessin. J'ai trouvé un moyen de contourner cela, alors je vais le décrire ici pour être complet.

L'astuce n'est pas d'utiliser les mêmes points de début et de fin, mais de tracer une ligne si petite qu'elle n'apparaît que comme un pixel lorsqu'elle est dessinée. Donc, afin de choisir le bon point de fin, j'ai d'abord projeté le point de l'espace mondial dans l'espace d'écran, je l'ai déplacé d'un pixel vers la droite dans l'espace d'écran , et finalement je l'ai projeté de nouveau dans l'espace mondial. C'est le point final de votre ligne afin de la faire ressembler à un point. Quelque chose comme ça:

Vector3 GetEndPointForDot(Vector3 start)
{
    // Convert start point to screen space
    Vector3 screenPoint = GraphicsDevice.Viewport.Project(start, projection, view, Matrix.Identity);

    // Screen space is defined in pixels so adding (1,0,0) moves it right one pixel
    screenPoint += Vector3.Right;

    // Finally unproject it back into world space
    return GraphicsDevice.Viewport.Unproject(screenPoint, projection, view, Matrix.Identity);
}

Suivi en le rendant comme une primitive de ligne normale.


Manifestation

Voici ce que j'ai obtenu en dessinant une ligne blanche dans l'espace 3D en utilisant une primitive de liste de lignes et des points rouges aux deux extrémités de la ligne en utilisant une texture 1x1 et SpriteBatch. Le code utilisé est à peu près ce que j'ai écrit ci-dessus. J'ai également zoomé pour que vous puissiez confirmer qu'ils mesurent exactement un pixel de large:

entrez la description de l'image ici

David Gouveia
la source
Qu'en est-il d'une ligne avec le même point de départ et de fin, cela ne produirait-il pas un point? Ou serait-il simplement invisible?
aaaaaaaaaaaa
@eBusiness Bonne question! Je viens de l'essayer, et en effet, cela ne rend rien.
David Gouveia
@eBusiness Je viens de trouver un moyen, mais je pense que c'est un peu idiot. Je vais quand même l'ajouter pour être complet.
David Gouveia
2

Ceci est marqué comme XNA, donc je suppose que c'est ce que vous demandez?

Si c'est le cas, cet article devrait être utile pour les lignes:

http://msdn.microsoft.com/en-us/library/bb196414(v=xnagamestudio.40).aspx

Bien sûr, vous pouvez utiliser vos propres matrices de vue / projection au lieu des leurs. Fondamentalement, PrimitiveType.LineListou LineStripcomment dessiner des lignes au niveau du GPU.

Quant aux points, vous ne pouvez plus utiliser PrimitiveType.PointList pour dessiner des points dans XNA 4.0. Au lieu de cela, vous devrez faire de très petits triangles. Cet exemple fournit une bonne base de référence:

http://create.msdn.com/education/catalog/sample/primitives_3d

Pour les versions précédentes de XNA, si vous utilisez l'une d'entre elles, vous pouvez continuer et lire la partie Point de la version XNA 3.0 de l'article publié ci-dessus:

http://msdn.microsoft.com/en-us/library/bb196414(v=xnagamestudio.30).aspx

Notez que le lien a:

graphics.GraphicsDevice.RenderState.PointSize = 10;

Évidemment, changez cela en 1.


la source