Espero que alguien pueda explicarme esto como si tuviera 5 años, porque he estado luchando con esto durante horas y simplemente no puedo entender lo que estoy haciendo mal.
He escrito una Camera
clase para mi juego 2.5D. La intención es apoyar espacios mundiales y de pantalla como este:
La cámara es la cosa negra a la derecha. El eje + Z está hacia arriba en esa imagen, con -Z hacia abajo. Como puede ver, tanto el espacio mundial como el espacio de la pantalla tienen (0, 0) en su esquina superior izquierda.
Comencé a escribir algunas pruebas unitarias para demostrar que mi cámara funcionaba como se esperaba, y ahí es donde las cosas comenzaron a ponerse ... extrañas. Mis pruebas trazan coordenadas en espacios de mundo, vista y pantalla. Eventualmente usaré la comparación de imágenes para afirmar que son correctas, pero por ahora mi prueba solo muestra el resultado.
La lógica de renderizado utiliza Camera.ViewMatrix
para transformar el espacio mundial para ver el espacio, y Camera.WorldPointToScreen
para transformar el espacio mundial en espacio de pantalla.
Aquí hay un ejemplo de prueba:
[Fact]
public void foo()
{
var camera = new Camera(new Viewport(0, 0, 250, 100));
DrawingVisual worldRender;
DrawingVisual viewRender;
DrawingVisual screenRender;
this.Render(camera, out worldRender, out viewRender, out screenRender, new Vector3(30, 0, 0), new Vector3(30, 40, 0));
this.ShowRenders(camera, worldRender, viewRender, screenRender);
}
Y esto es lo que aparece cuando ejecuto esta prueba:
El espacio mundial se ve bien, aunque sospecho que el eje z está entrando en la pantalla en lugar de hacia el espectador.
Ver espacio me tiene completamente desconcertado. Esperaba que la cámara estuviera sentada arriba (0, 0) y mirando hacia el centro de la escena. En cambio, el eje z parece estar al revés, y la cámara se coloca en la esquina opuesta a lo que espero.
Sospecho que el espacio en la pantalla será otra cosa, pero ¿alguien puede explicar lo que estoy haciendo mal en mi Camera
clase?
ACTUALIZAR
Hice algunos progresos en términos de hacer que las cosas se vean visualmente como esperaba, pero solo a través de la intuición: no una comprensión real de lo que estoy haciendo. Cualquier iluminación sería muy apreciada.
Me di cuenta de que mi espacio de vista se volteó vertical y horizontalmente en comparación con lo que esperaba, así que cambié mi matriz de vista para escalar en consecuencia:
this.viewMatrix = Matrix.CreateLookAt(this.location, this.target, this.up) *
Matrix.CreateScale(this.zoom, this.zoom, 1) *
Matrix.CreateScale(-1, -1, 1);
Podría combinar las dos CreateScale
llamadas, pero las he dejado separadas para mayor claridad. Nuevamente, no tengo idea de por qué esto es necesario, pero solucionó mi espacio de vista:
Pero ahora mi espacio de pantalla debe invertirse verticalmente, por lo que modifiqué mi matriz de proyección en consecuencia:
this.projectionMatrix = Matrix.CreatePerspectiveFieldOfView(0.7853982f, viewport.AspectRatio, 1, 2)
* Matrix.CreateScale(1, -1, 1);
Y esto da como resultado lo que esperaba de mi primer intento:
También he intentado usar Camera
renderizar sprites a través de a SpriteBatch
para asegurarme de que todo funciona allí, y lo hace.
Pero la pregunta sigue siendo: ¿por qué necesito hacer todo este giro de ejes para obtener las coordenadas espaciales de la manera que espero?
ACTUALIZACIÓN 2
Desde entonces, he mejorado mi lógica de renderizado en mi conjunto de pruebas para que admita geometrías y para que las líneas se aclaren cuanto más lejos estén de la cámara. Quería hacer esto para evitar ilusiones ópticas y demostrarme a mí mismo que estoy viendo lo que creo que soy.
Aquí hay un ejemplo:
En este caso, tengo 3 geometrías: un cubo, una esfera y una polilínea en la cara superior del cubo. Observe cómo el oscurecimiento y el aligeramiento de las líneas identifica correctamente aquellas partes de las geometrías más cercanas a la cámara.
Si elimino la escala negativa que tuve que poner, veo:
Así que puedes ver que todavía estoy en el mismo bote, todavía necesito esos cambios verticales y horizontales en mis matrices para que las cosas aparezcan correctamente.
En aras de darles a las personas una reproducción, aquí está el código completo necesario para generar lo anterior. Si desea ejecutar a través del arnés de prueba, simplemente instale el paquete xunit:
Camera.cs :
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Diagnostics;
public sealed class Camera
{
private readonly Viewport viewport;
private readonly Matrix projectionMatrix;
private Matrix? viewMatrix;
private Vector3 location;
private Vector3 target;
private Vector3 up;
private float zoom;
public Camera(Viewport viewport)
{
this.viewport = viewport;
// for an explanation of the negative scaling, see: http://gamedev.stackexchange.com/questions/63409/
this.projectionMatrix = Matrix.CreatePerspectiveFieldOfView(0.7853982f, viewport.AspectRatio, 1, 2)
* Matrix.CreateScale(1, -1, 1);
// defaults
this.location = new Vector3(this.viewport.Width / 2, this.viewport.Height, 100);
this.target = new Vector3(this.viewport.Width / 2, this.viewport.Height / 2, 0);
this.up = new Vector3(0, 0, 1);
this.zoom = 1;
}
public Viewport Viewport
{
get { return this.viewport; }
}
public Vector3 Location
{
get { return this.location; }
set
{
this.location = value;
this.viewMatrix = null;
}
}
public Vector3 Target
{
get { return this.target; }
set
{
this.target = value;
this.viewMatrix = null;
}
}
public Vector3 Up
{
get { return this.up; }
set
{
this.up = value;
this.viewMatrix = null;
}
}
public float Zoom
{
get { return this.zoom; }
set
{
this.zoom = value;
this.viewMatrix = null;
}
}
public Matrix ProjectionMatrix
{
get { return this.projectionMatrix; }
}
public Matrix ViewMatrix
{
get
{
if (this.viewMatrix == null)
{
// for an explanation of the negative scaling, see: http://gamedev.stackexchange.com/questions/63409/
this.viewMatrix = Matrix.CreateLookAt(this.location, this.target, this.up) *
Matrix.CreateScale(this.zoom) *
Matrix.CreateScale(-1, -1, 1);
}
return this.viewMatrix.Value;
}
}
public Vector2 WorldPointToScreen(Vector3 point)
{
var result = viewport.Project(point, this.ProjectionMatrix, this.ViewMatrix, Matrix.Identity);
return new Vector2(result.X, result.Y);
}
public void WorldPointsToScreen(Vector3[] points, Vector2[] destination)
{
Debug.Assert(points != null);
Debug.Assert(destination != null);
Debug.Assert(points.Length == destination.Length);
for (var i = 0; i < points.Length; ++i)
{
destination[i] = this.WorldPointToScreen(points[i]);
}
}
}
CameraFixture.cs :
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Xunit;
using XNA = Microsoft.Xna.Framework;
public sealed class CameraFixture
{
[Fact]
public void foo()
{
var camera = new Camera(new Viewport(0, 0, 250, 100));
DrawingVisual worldRender;
DrawingVisual viewRender;
DrawingVisual screenRender;
this.Render(
camera,
out worldRender,
out viewRender,
out screenRender,
new Sphere(30, 15) { WorldMatrix = XNA.Matrix.CreateTranslation(155, 50, 0) },
new Cube(30) { WorldMatrix = XNA.Matrix.CreateTranslation(75, 60, 15) },
new PolyLine(new XNA.Vector3(0, 0, 0), new XNA.Vector3(10, 10, 0), new XNA.Vector3(20, 0, 0), new XNA.Vector3(0, 0, 0)) { WorldMatrix = XNA.Matrix.CreateTranslation(65, 55, 30) });
this.ShowRenders(worldRender, viewRender, screenRender);
}
#region Supporting Fields
private static readonly Pen xAxisPen = new Pen(Brushes.Red, 2);
private static readonly Pen yAxisPen = new Pen(Brushes.Green, 2);
private static readonly Pen zAxisPen = new Pen(Brushes.Blue, 2);
private static readonly Pen viewportPen = new Pen(Brushes.Gray, 1);
private static readonly Pen nonScreenSpacePen = new Pen(Brushes.Black, 0.5);
private static readonly Color geometryBaseColor = Colors.Black;
#endregion
#region Supporting Methods
private void Render(Camera camera, out DrawingVisual worldRender, out DrawingVisual viewRender, out DrawingVisual screenRender, params Geometry[] geometries)
{
var worldDrawingVisual = new DrawingVisual();
var viewDrawingVisual = new DrawingVisual();
var screenDrawingVisual = new DrawingVisual();
const int axisLength = 15;
using (var worldDrawingContext = worldDrawingVisual.RenderOpen())
using (var viewDrawingContext = viewDrawingVisual.RenderOpen())
using (var screenDrawingContext = screenDrawingVisual.RenderOpen())
{
// draw lines around the camera's viewport
var viewportBounds = camera.Viewport.Bounds;
var viewportLines = new Tuple<int, int, int, int>[]
{
Tuple.Create(viewportBounds.Left, viewportBounds.Bottom, viewportBounds.Left, viewportBounds.Top),
Tuple.Create(viewportBounds.Left, viewportBounds.Top, viewportBounds.Right, viewportBounds.Top),
Tuple.Create(viewportBounds.Right, viewportBounds.Top, viewportBounds.Right, viewportBounds.Bottom),
Tuple.Create(viewportBounds.Right, viewportBounds.Bottom, viewportBounds.Left, viewportBounds.Bottom)
};
foreach (var viewportLine in viewportLines)
{
var viewStart = XNA.Vector3.Transform(new XNA.Vector3(viewportLine.Item1, viewportLine.Item2, 0), camera.ViewMatrix);
var viewEnd = XNA.Vector3.Transform(new XNA.Vector3(viewportLine.Item3, viewportLine.Item4, 0), camera.ViewMatrix);
var screenStart = camera.WorldPointToScreen(new XNA.Vector3(viewportLine.Item1, viewportLine.Item2, 0));
var screenEnd = camera.WorldPointToScreen(new XNA.Vector3(viewportLine.Item3, viewportLine.Item4, 0));
worldDrawingContext.DrawLine(viewportPen, new Point(viewportLine.Item1, viewportLine.Item2), new Point(viewportLine.Item3, viewportLine.Item4));
viewDrawingContext.DrawLine(viewportPen, new Point(viewStart.X, viewStart.Y), new Point(viewEnd.X, viewEnd.Y));
screenDrawingContext.DrawLine(viewportPen, new Point(screenStart.X, screenStart.Y), new Point(screenEnd.X, screenEnd.Y));
}
// draw axes
var axisLines = new Tuple<int, int, int, int, int, int, Pen>[]
{
Tuple.Create(0, 0, 0, axisLength, 0, 0, xAxisPen),
Tuple.Create(0, 0, 0, 0, axisLength, 0, yAxisPen),
Tuple.Create(0, 0, 0, 0, 0, axisLength, zAxisPen)
};
foreach (var axisLine in axisLines)
{
var viewStart = XNA.Vector3.Transform(new XNA.Vector3(axisLine.Item1, axisLine.Item2, axisLine.Item3), camera.ViewMatrix);
var viewEnd = XNA.Vector3.Transform(new XNA.Vector3(axisLine.Item4, axisLine.Item5, axisLine.Item6), camera.ViewMatrix);
var screenStart = camera.WorldPointToScreen(new XNA.Vector3(axisLine.Item1, axisLine.Item2, axisLine.Item3));
var screenEnd = camera.WorldPointToScreen(new XNA.Vector3(axisLine.Item4, axisLine.Item5, axisLine.Item6));
worldDrawingContext.DrawLine(axisLine.Item7, new Point(axisLine.Item1, axisLine.Item2), new Point(axisLine.Item4, axisLine.Item5));
viewDrawingContext.DrawLine(axisLine.Item7, new Point(viewStart.X, viewStart.Y), new Point(viewEnd.X, viewEnd.Y));
screenDrawingContext.DrawLine(axisLine.Item7, new Point(screenStart.X, screenStart.Y), new Point(screenEnd.X, screenEnd.Y));
}
// for all points in all geometries to be rendered, find the closest and furthest away from the camera so we can lighten lines that are further away
var distancesToAllGeometrySections = from geometry in geometries
let geometryViewMatrix = geometry.WorldMatrix * camera.ViewMatrix
from section in geometry.Sections
from point in new XNA.Vector3[] { section.Item1, section.Item2 }
let viewPoint = XNA.Vector3.Transform(point, geometryViewMatrix)
select viewPoint.Length();
var furthestDistance = distancesToAllGeometrySections.Max();
var closestDistance = distancesToAllGeometrySections.Min();
var deltaDistance = Math.Max(0.000001f, furthestDistance - closestDistance);
// draw each geometry
for (var i = 0; i < geometries.Length; ++i)
{
var geometry = geometries[i];
// there's probably a more correct name for this, but basically this gets the geometry relative to the camera so we can check how far away each point is from the camera
var geometryViewMatrix = geometry.WorldMatrix * camera.ViewMatrix;
// we order roughly by those sections furthest from the camera to those closest, so that the closer ones "overwrite" the ones further away
var orderedSections = from section in geometry.Sections
let startPointRelativeToCamera = XNA.Vector3.Transform(section.Item1, geometryViewMatrix)
let endPointRelativeToCamera = XNA.Vector3.Transform(section.Item2, geometryViewMatrix)
let startPointDistance = startPointRelativeToCamera.Length()
let endPointDistance = endPointRelativeToCamera.Length()
orderby (startPointDistance + endPointDistance) descending
select new { Section = section, DistanceToStart = startPointDistance, DistanceToEnd = endPointDistance };
foreach (var orderedSection in orderedSections)
{
var start = XNA.Vector3.Transform(orderedSection.Section.Item1, geometry.WorldMatrix);
var end = XNA.Vector3.Transform(orderedSection.Section.Item2, geometry.WorldMatrix);
var viewStart = XNA.Vector3.Transform(start, camera.ViewMatrix);
var viewEnd = XNA.Vector3.Transform(end, camera.ViewMatrix);
worldDrawingContext.DrawLine(nonScreenSpacePen, new Point(start.X, start.Y), new Point(end.X, end.Y));
viewDrawingContext.DrawLine(nonScreenSpacePen, new Point(viewStart.X, viewStart.Y), new Point(viewEnd.X, viewEnd.Y));
// screen rendering is more complicated purely because I wanted geometry to fade the further away it is from the camera
// otherwise, it's very hard to tell whether the rendering is actually correct or not
var startDistanceRatio = (orderedSection.DistanceToStart - closestDistance) / deltaDistance;
var endDistanceRatio = (orderedSection.DistanceToEnd - closestDistance) / deltaDistance;
// lerp towards white based on distance from camera, but only to a maximum of 90%
var startColor = Lerp(geometryBaseColor, Colors.White, startDistanceRatio * 0.9f);
var endColor = Lerp(geometryBaseColor, Colors.White, endDistanceRatio * 0.9f);
var screenStart = camera.WorldPointToScreen(start);
var screenEnd = camera.WorldPointToScreen(end);
var brush = new LinearGradientBrush
{
StartPoint = new Point(screenStart.X, screenStart.Y),
EndPoint = new Point(screenEnd.X, screenEnd.Y),
MappingMode = BrushMappingMode.Absolute
};
brush.GradientStops.Add(new GradientStop(startColor, 0));
brush.GradientStops.Add(new GradientStop(endColor, 1));
var pen = new Pen(brush, 1);
brush.Freeze();
pen.Freeze();
screenDrawingContext.DrawLine(pen, new Point(screenStart.X, screenStart.Y), new Point(screenEnd.X, screenEnd.Y));
}
}
}
worldRender = worldDrawingVisual;
viewRender = viewDrawingVisual;
screenRender = screenDrawingVisual;
}
private static float Lerp(float start, float end, float amount)
{
var difference = end - start;
var adjusted = difference * amount;
return start + adjusted;
}
private static Color Lerp(Color color, Color to, float amount)
{
var sr = color.R;
var sg = color.G;
var sb = color.B;
var er = to.R;
var eg = to.G;
var eb = to.B;
var r = (byte)Lerp(sr, er, amount);
var g = (byte)Lerp(sg, eg, amount);
var b = (byte)Lerp(sb, eb, amount);
return Color.FromArgb(255, r, g, b);
}
private void ShowRenders(DrawingVisual worldRender, DrawingVisual viewRender, DrawingVisual screenRender)
{
var itemsControl = new ItemsControl();
itemsControl.Items.Add(new HeaderedContentControl { Header = "World", Content = new DrawingVisualHost(worldRender)});
itemsControl.Items.Add(new HeaderedContentControl { Header = "View", Content = new DrawingVisualHost(viewRender) });
itemsControl.Items.Add(new HeaderedContentControl { Header = "Screen", Content = new DrawingVisualHost(screenRender) });
var window = new Window
{
Title = "Renders",
Content = itemsControl,
ShowInTaskbar = true,
SizeToContent = SizeToContent.WidthAndHeight
};
window.ShowDialog();
}
#endregion
#region Supporting Types
// stupidly simple 3D geometry class, consisting of a series of sections that will be connected by lines
private abstract class Geometry
{
public abstract IEnumerable<Tuple<XNA.Vector3, XNA.Vector3>> Sections
{
get;
}
public XNA.Matrix WorldMatrix
{
get;
set;
}
}
private sealed class Line : Geometry
{
private readonly XNA.Vector3 magnitude;
public Line(XNA.Vector3 magnitude)
{
this.magnitude = magnitude;
}
public override IEnumerable<Tuple<XNA.Vector3, XNA.Vector3>> Sections
{
get
{
yield return Tuple.Create(XNA.Vector3.Zero, this.magnitude);
}
}
}
private sealed class PolyLine : Geometry
{
private readonly XNA.Vector3[] points;
public PolyLine(params XNA.Vector3[] points)
{
this.points = points;
}
public override IEnumerable<Tuple<XNA.Vector3, XNA.Vector3>> Sections
{
get
{
if (this.points.Length < 2)
{
yield break;
}
var end = this.points[0];
for (var i = 1; i < this.points.Length; ++i)
{
var start = end;
end = this.points[i];
yield return Tuple.Create(start, end);
}
}
}
}
private sealed class Cube : Geometry
{
private readonly float size;
public Cube(float size)
{
this.size = size;
}
public override IEnumerable<Tuple<XNA.Vector3, XNA.Vector3>> Sections
{
get
{
var halfSize = this.size / 2;
var frontBottomLeft = new XNA.Vector3(-halfSize, halfSize, -halfSize);
var frontBottomRight = new XNA.Vector3(halfSize, halfSize, -halfSize);
var frontTopLeft = new XNA.Vector3(-halfSize, halfSize, halfSize);
var frontTopRight = new XNA.Vector3(halfSize, halfSize, halfSize);
var backBottomLeft = new XNA.Vector3(-halfSize, -halfSize, -halfSize);
var backBottomRight = new XNA.Vector3(halfSize, -halfSize, -halfSize);
var backTopLeft = new XNA.Vector3(-halfSize, -halfSize, halfSize);
var backTopRight = new XNA.Vector3(halfSize, -halfSize, halfSize);
// front face
yield return Tuple.Create(frontBottomLeft, frontBottomRight);
yield return Tuple.Create(frontBottomLeft, frontTopLeft);
yield return Tuple.Create(frontTopLeft, frontTopRight);
yield return Tuple.Create(frontTopRight, frontBottomRight);
// left face
yield return Tuple.Create(frontTopLeft, backTopLeft);
yield return Tuple.Create(backTopLeft, backBottomLeft);
yield return Tuple.Create(backBottomLeft, frontBottomLeft);
// right face
yield return Tuple.Create(frontTopRight, backTopRight);
yield return Tuple.Create(backTopRight, backBottomRight);
yield return Tuple.Create(backBottomRight, frontBottomRight);
// back face
yield return Tuple.Create(backBottomLeft, backBottomRight);
yield return Tuple.Create(backTopLeft, backTopRight);
}
}
}
private sealed class Sphere : Geometry
{
private readonly float radius;
private readonly int subsections;
public Sphere(float radius, int subsections)
{
this.radius = radius;
this.subsections = subsections;
}
public override IEnumerable<Tuple<XNA.Vector3, XNA.Vector3>> Sections
{
get
{
var latitudeLines = this.subsections;
var longitudeLines = this.subsections;
// see http://stackoverflow.com/a/4082020/5380
var results = from latitudeLine in Enumerable.Range(0, latitudeLines)
from longitudeLine in Enumerable.Range(0, longitudeLines)
let latitudeRatio = latitudeLine / (float)latitudeLines
let longitudeRatio = longitudeLine / (float)longitudeLines
let nextLatitudeRatio = (latitudeLine + 1) / (float)latitudeLines
let nextLongitudeRatio = (longitudeLine + 1) / (float)longitudeLines
let z1 = Math.Cos(Math.PI * latitudeRatio)
let z2 = Math.Cos(Math.PI * nextLatitudeRatio)
let x1 = Math.Sin(Math.PI * latitudeRatio) * Math.Cos(Math.PI * 2 * longitudeRatio)
let y1 = Math.Sin(Math.PI * latitudeRatio) * Math.Sin(Math.PI * 2 * longitudeRatio)
let x2 = Math.Sin(Math.PI * nextLatitudeRatio) * Math.Cos(Math.PI * 2 * longitudeRatio)
let y2 = Math.Sin(Math.PI * nextLatitudeRatio) * Math.Sin(Math.PI * 2 * longitudeRatio)
let x3 = Math.Sin(Math.PI * latitudeRatio) * Math.Cos(Math.PI * 2 * nextLongitudeRatio)
let y3 = Math.Sin(Math.PI * latitudeRatio) * Math.Sin(Math.PI * 2 * nextLongitudeRatio)
let start = new XNA.Vector3((float)x1 * radius, (float)y1 * radius, (float)z1 * radius)
let firstEnd = new XNA.Vector3((float)x2 * radius, (float)y2 * radius, (float)z2 * radius)
let secondEnd = new XNA.Vector3((float)x3 * radius, (float)y3 * radius, (float)z1 * radius)
select new { First = Tuple.Create(start, firstEnd), Second = Tuple.Create(start, secondEnd) };
foreach (var result in results)
{
yield return result.First;
yield return result.Second;
}
}
}
}
#endregion
}
fuente
Viewport.Project
requiere una matriz mundial. Por lo tanto, agregué una matriz mundial a mi API. Puede ser que termine eliminándolo si es necesario.Respuestas:
Sus diagramas se pueden interpretar de una de dos maneras. Es una ilusión óptica llamada cubo Necker. Aquí está el artículo de wikipedia. Debido a esto, cuando crees que estás mirando la parte superior, sospecho que en realidad puedes estar viendo la parte inferior.
Si puede, en su código original, niegue el valor z de la posición de su cámara.
fuente
this.viewport.Height
, mirandothis.viewport.Height/2
, lo que significa que apunta en la dirección -y. Intenta configurar la ubicación de tu cámara en(this.viewport.Width / 2, 0, 100)
.Dado que esto es 2.5D, dos cosas que encuentro extrañas aquí son:
Math.PiOver4().
fuente
MathHelper.PiOver4
para limpiar un poco el código. La profundidad de la vista no hace ninguna diferencia con el problema declarado, y no puedo entender por qué lo haría ...Aplicar una transformación a ciegas como una escala negativa no es una buena idea para entender el problema.
En la captura de pantalla original y la actualización 1, si observa el cuadro RGB, coincidirá con un sistema de coordenadas diestro como debería porque al negar dos ejes de la matriz de vista se mantiene el signo determinante sin cambios.
En la captura de la actualización 2, invierte solo un eje de la matriz de proyección, al hacer esto, pasa de un sistema diestro a uno zurdo. Use su pulgar, índice y dedo medio como X, Y y Z.
Debido a que XNA está utilizando coordenadas diestras con (+ X derecha, + Y arriba, -Z adelante), significa que realmente hay un problema en lo que está mostrando.
Decide que su coordenada Z es hacia arriba (como se ve en la parte del espacio mundial de la captura). Significa que necesitas una transformación para moverte de nuestro espacio mundial (+ X derecha, + Z arriba y + Y adelante) al XNA.
Si miras tu mano, revelará una
PI/2
rotación alrededor del eje X. Debe insertarlo antes de la proyección.Sin él, debido a los dos sistemas diferentes, su avión no es un piso sino una pared.
fuente
this.ProjectionMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) * Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, viewport.AspectRatio, 1, 2);
ythis.ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, viewport.AspectRatio, 1, 2) * Matrix.CreateRotationX(MathHelper.PiOver2);
tampoco funcionó.