Desenhando uma linha de um pixel de largura no espaço 3D

10

Gostaria de desenhar uma linha no espaço 3D que tenha sempre exatamente um pixel de largura na tela, independentemente da distância da câmera. (E o mesmo para pontos únicos também).

Alguma dica de como eu poderia fazer isso?

danbystrom
fonte

Respostas:

24

Rendering Lines - Método 1 (Primitivas)

Para linhas simples no espaço 3D, você pode desenhá-las usando a LineListou LineStripprimitivo. Aqui está a quantidade mínima de código que você deve adicionar a um projeto XNA vazio para que ele desenhe uma linha de (0,0,0) a (0,0, -50). A linha deve parecer ter aproximadamente a mesma largura, não importa onde a câmera esteja localizada.

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

Basicamente, criei um simples BasicEffectpara manter minhas transformações de vista e projeção e passei dois vértices (posição e cor de armazenamento) para o GraphicsDevice.DrawUserPrimitivesmétodo a ser renderizado como a LineList.

É claro que existem muitas maneiras de otimizá-lo, a maioria delas envolve a criação de um VertexBufferpara armazenar todos os vértices e agrupar o maior número possível de linhas em uma única chamada do Draw, mas isso é irrelevante para a questão.


Pontos de renderização - Método 1 (SpriteBatch)

Quanto aos pontos de desenho, isso costumava ser fácil usando sprites de pontos, mas eles foram removidos do XNA 4.0 . Existem algumas alternativas embora. A maneira mais fácil é criar um Texture2Dobjeto branco 1x1 e renderizá-lo usando SpriteBatcho local correto da tela, que você pode encontrar facilmente usando o método Viewport.Project .

Você pode criar o Texture2Dobjeto necessário assim:

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

E renderize-o no local (x, y, z) assim:

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

Renderizando linhas - método 2 (SpriteBatch)

Como alternativa, você também pode desenhar linhas SpriteBatchusando a técnica descrita aqui . Nesse caso, você simplesmente precisa encontrar a coordenada do espaço da tela para as duas extremidades da linha 3D (mais uma vez usando Viewport.Project) e depois desenhar uma linha regular entre elas.


Pontos de renderização - método 2 (linha pequena com primitivas)

Nos comentários, o eBusiness levantou a seguinte pergunta:

Que tal uma linha com o mesmo ponto inicial e final, isso não produziria um ponto? Ou seria simplesmente invisível?

Eu tentei e renderizar LineListusando os mesmos pontos de início e fim resultou em nada sendo desenhado. Eu encontrei uma maneira de contornar isso, então eu vou descrevê-lo aqui para ser completo.

O truque não é usar os mesmos pontos de início e de término, mas sim desenhar uma linha tão pequena que só aparece como um pixel quando desenhada. Portanto, para escolher o ponto final correto, projetei o ponto do espaço mundial no espaço da tela, movi-o para a direita um pixel no espaço da tela e, finalmente, o projetei de volta ao espaço do mundo. Esse é o ponto final da sua linha para fazer com que pareça um ponto. Algo assim:

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

Seguido renderizando-o como uma linha primitiva normal.


Demonstração

Aqui está o que eu consegui desenhar uma linha branca no espaço 3D usando uma lista de linhas primitiva e pontos vermelhos nas duas extremidades da linha usando uma textura 1x1 e SpriteBatch. O código usado é praticamente o que eu escrevi acima. Também ampliei o zoom para confirmar que eles têm exatamente um pixel de largura:

insira a descrição da imagem aqui

David Gouveia
fonte
Que tal uma linha com o mesmo ponto inicial e final, isso não produziria um ponto? Ou seria simplesmente invisível?
Aaaaaaaaaaaa
@eBusiness Boa pergunta! Eu apenas tentei e, de fato, não renderiza nada.
David Gouveia
@eBusiness Acabei de encontrar uma maneira, mas acho que é um pouco bobo. Vou adicioná-lo para completar de qualquer maneira.
David Gouveia
2

Isso está marcado como XNA, então estou assumindo que é disso que você está perguntando?

Nesse caso, este artigo deve ser útil para linhas:

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

Obviamente, você pode usar suas próprias matrizes de visão / projeção em vez das deles. Basicamente, PrimitiveType.LineListou LineStripé como desenhar linhas no nível da GPU.

Quanto aos pontos, não é mais possível usar PrimitiveType.PointList para desenhar mais pontos no XNA 4.0. Em vez disso, você teria que fazer triângulos muito pequenos. Este exemplo fornece uma boa linha de base:

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

Para versões anteriores do XNA, se você estiver usando um deles, poderá ler a parte Point da versão XNA 3.0 do artigo postado acima:

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

Observe que o link possui:

graphics.GraphicsDevice.RenderState.PointSize = 10;

Obviamente mude isso para 1.


fonte