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?
Rendering Lines - Método 1 (Primitivas)
Para linhas simples no espaço 3D, você pode desenhá-las usando a LineList
ou LineStrip
primitivo. 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 BasicEffect
para manter minhas transformações de vista e projeção e passei dois vértices (posição e cor de armazenamento) para o GraphicsDevice.DrawUserPrimitives
método a ser renderizado como a LineList
.
É claro que existem muitas maneiras de otimizá-lo, a maioria delas envolve a criação de um VertexBuffer
para 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 Texture2D
objeto branco 1x1 e renderizá-lo usando SpriteBatch
o local correto da tela, que você pode encontrar facilmente usando o método Viewport.Project .
Você pode criar o Texture2D
objeto 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 SpriteBatch
usando 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 LineList
usando 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:
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.LineList
ouLineStrip
é 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:
Obviamente mude isso para
1
.fonte