Rysowanie linii o szerokości jednego piksela w przestrzeni 3D

10

Chciałbym narysować linię w przestrzeni 3D, która zawsze ma dokładnie jeden piksel na ekranie, bez względu na to, jak daleko jest od aparatu. (To samo dotyczy pojedynczych punktów).

Wszelkie wskazówki, jak to zrobić?

danbystrom
źródło

Odpowiedzi:

24

Linie renderowania - metoda 1 (prymitywy)

W przypadku prostych linii w przestrzeni 3D możesz je narysować za pomocą LineListlub LineStripprymitywu. Oto minimalna ilość kodu, który musisz dodać do pustego projektu XNA, aby narysować linię od (0,0,0) do (0,0, -50). Linia powinna wydawać się mieć mniej więcej tę samą szerokość bez względu na to, gdzie znajduje się kamera.

// 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);

Zasadniczo stworzyłem prosty BasicEffectdo przechowywania transformacji widoku i projekcji i przekazałem dwa wierzchołki (położenie przechowywania i kolor) GraphicsDevice.DrawUserPrimitivesmetodzie, która ma być renderowana jako LineList.

Oczywiście istnieje wiele sposobów na jego optymalizację, z których większość polega na utworzeniu a VertexBufferdo przechowywania wszystkich wierzchołków i grupowaniu jak największej liczby linii w jedno wywołanie Draw, ale nie ma to znaczenia dla pytania.


Punkty renderowania - Metoda 1 (SpriteBatch)

Jeśli chodzi o rysowanie punktów, było to łatwe przy użyciu sprajtów punktowych, ale zostały one usunięte z XNA 4.0 . Istnieje jednak kilka alternatyw. Najłatwiejszym sposobem jest utworzenie białego Texture2Dobiektu 1x1 i renderowanie go przy użyciu SpriteBatchwłaściwej lokalizacji ekranu, którą można łatwo znaleźć za pomocą metody Viewport.Project .

Możesz utworzyć wymagany Texture2Dobiekt w następujący sposób:

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

I renderuj go w miejscu (x, y, z) w następujący sposób:

// 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();

Linie renderowania - metoda 2 (SpriteBatch)

Alternatywnie możesz również rysować linie za SpriteBatchpomocą opisanej tutaj techniki . W takim przypadku musisz po prostu znaleźć współrzędną przestrzenną ekranu dla obu końców linii 3D (jeszcze raz używając Viewport.Project), a następnie narysować regularną linię między nimi.


Punkty renderowania - metoda 2 (mała linia z elementami pierwotnymi)

W komentarzach e-biznes podniósł następujące pytanie:

A co z linią o tym samym punkcie początkowym i końcowym, czy to nie dałoby punktu? A może po prostu byłby niewidoczny?

Spróbowałem i renderowanie LineListprzy użyciu tych samych punktów początkowych i końcowych spowodowało, że nic nie zostało narysowane. Znalazłem sposób na obejście tego, więc opiszę to tutaj dla kompletności.

Sztuczka nie polega na użyciu tych samych punktów początkowych i końcowych, ale na narysowaniu linii tak małej, że po narysowaniu pojawia się ona tylko jako jeden piksel . Aby więc wybrać właściwy punkt końcowy, najpierw rzutowałem światowy punkt kosmiczny na przestrzeń ekranową, przesunąłem go o jeden piksel w przestrzeń ekranową , a na koniec rzutowałem go z powrotem na przestrzeń światową. To jest punkt końcowy twojej linii, aby wyglądała jak kropka. Coś takiego:

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);
}

Następnie renderowane jako normalna linia pierwotna.


Demonstracja

Oto, co narysowałem białą linię w przestrzeni 3D za pomocą prymitywnej listy linii oraz czerwone kropki na obu końcach linii za pomocą tekstury 1x1 i SpriteBatch. Użyty kod jest prawie tym, co napisałem powyżej. Przybliżiłem również, abyś mógł potwierdzić, że mają dokładnie jeden piksel:

wprowadź opis zdjęcia tutaj

David Gouveia
źródło
A co z linią o tym samym punkcie początkowym i końcowym, czy to nie dałoby punktu? A może po prostu byłby niewidoczny?
aaaaaaaaaaaa
@eBusiness Dobre pytanie! Po prostu spróbowałem i naprawdę nic nie renderuje.
David Gouveia
@eBusiness Właśnie znalazłem sposób, ale myślę, że to trochę głupie. I tak dodam to dla kompletności.
David Gouveia,
2

Jest to oznaczone jako XNA, więc zakładam, że o to pytasz?

Jeśli tak, ten artykuł powinien być pomocny w przypadku wierszy:

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

Oczywiście możesz użyć własnych matryc widoku / projekcji zamiast ich. Zasadniczo, PrimitiveType.LineListczyli LineStripjak narysować linie na poziomie GPU.

Jeśli chodzi o punkty, nie można już używać PrimitiveType.PointList do rysowania punktów w XNA 4.0. Zamiast tego musielibyście robić bardzo małe trójkąty. Ta próbka stanowi dobry punkt odniesienia:

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

W przypadku poprzednich wersji XNA, jeśli używasz jednej z nich, możesz przejść dalej i przeczytać część Point wersji XNA 3.0 artykułu opublikowanego powyżej:

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

Uwaga: link zawiera:

graphics.GraphicsDevice.RenderState.PointSize = 10;

Oczywiście zmień to na 1.


źródło