Kamera für 2.5D-Spiele

12

Ich hoffe, jemand kann mir das erklären, als wäre ich 5, weil ich stundenlang damit zu kämpfen habe und einfach nicht verstehen kann, was ich falsch mache.

Ich habe eine CameraKlasse für mein 2.5D-Spiel geschrieben. Die Absicht ist es, Welt- und Bildräume wie folgt zu unterstützen:

Bildbeschreibung hier eingeben

Die Kamera ist das schwarze Ding auf der rechten Seite. Die + Z-Achse ist in diesem Bild nach oben gerichtet, wobei -Z nach unten zeigt. Wie Sie sehen können, haben sowohl der Weltraum als auch der Bildschirmbereich (0, 0) oben links.

Ich fing an, einige Unit-Tests zu schreiben, um zu beweisen, dass meine Kamera erwartungsgemäß funktionierte, und dort begannen die Dinge ... seltsam zu werden. Meine Tests zeichnen Koordinaten in Welt-, Ansichts- und Bildschirmbereichen. Irgendwann werde ich den Bildvergleich verwenden, um zu bestätigen, dass sie korrekt sind, aber im Moment zeigt mein Test nur das Ergebnis an.

Die Renderlogik dient Camera.ViewMatrixzum Transformieren des Weltraums zum Anzeigen des Raums und Camera.WorldPointToScreenzum Transformieren des Weltraums zum Anzeigen des Raums.

Hier ist ein Beispieltest:

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

Und Folgendes wird angezeigt, wenn ich diesen Test durchführe:

Bildbeschreibung hier eingeben

Der Weltraum sieht in Ordnung aus, obwohl ich vermute, dass die Z-Achse in den Bildschirm und nicht in Richtung des Betrachters zeigt.

Sichtraum hat mich total verblüfft. Ich hatte erwartet, dass die Kamera über (0, 0) sitzt und in die Mitte der Szene schaut. Stattdessen scheint die z-Achse falsch herum zu sein, und die Kamera befindet sich in der entgegengesetzten Ecke zu dem, was ich erwarte!

Ich vermute, dass der Platz auf dem Bildschirm eine andere Sache sein wird, aber kann mir jemand erklären, was ich in meiner CameraKlasse falsch mache ?


AKTUALISIEREN

Ich habe einige Fortschritte gemacht, um die Dinge so aussehen zu lassen, wie ich es erwartet habe, aber nur durch Intuition: kein wirkliches Verständnis dessen, was ich tue. Jede Erleuchtung wäre sehr dankbar.

Ich stellte fest, dass mein Ansichtsbereich im Vergleich zu meinen Erwartungen sowohl vertikal als auch horizontal gespiegelt war, und änderte meine Ansichtsmatrix entsprechend, um sie zu skalieren:

this.viewMatrix = Matrix.CreateLookAt(this.location, this.target, this.up) *
    Matrix.CreateScale(this.zoom, this.zoom, 1) *
    Matrix.CreateScale(-1, -1, 1);

Ich könnte die beiden CreateScaleAnrufe kombinieren , habe sie jedoch aus Gründen der Klarheit getrennt gelassen. Auch hier habe ich keine Ahnung, warum dies notwendig ist, aber es hat meinen Sichtbereich festgelegt:

Bildbeschreibung hier eingeben

Jetzt muss mein Bildschirmbereich jedoch vertikal gespiegelt werden, und ich habe meine Projektionsmatrix entsprechend geändert:

this.projectionMatrix = Matrix.CreatePerspectiveFieldOfView(0.7853982f, viewport.AspectRatio, 1, 2)
    * Matrix.CreateScale(1, -1, 1);

Und das führt zu dem, was ich von meinem ersten Versuch erwartet hatte:

Bildbeschreibung hier eingeben

Ich habe auch gerade versucht Camera, Sprites über a zu rendern SpriteBatch, um sicherzustellen, dass auch dort alles funktioniert.

Die Frage bleibt jedoch: Warum muss ich all diese Achsendrehungen durchführen, um die Raumkoordinaten so zu erhalten, wie ich es erwartet habe?


UPDATE 2

Seitdem habe ich meine Renderlogik in meiner Testsuite verbessert, sodass Geometrien unterstützt werden und die Linien heller werden, je weiter sie von der Kamera entfernt sind. Ich wollte dies tun, um optische Täuschungen zu vermeiden und mir weiter zu beweisen, dass ich auf das schaue, was ich denke, was ich bin.

Hier ist ein Beispiel:

Bildbeschreibung hier eingeben

In diesem Fall habe ich drei Geometrien: einen Würfel, eine Kugel und eine Polylinie auf der Oberseite des Würfels. Beachten Sie, wie durch das Abdunkeln und Aufhellen der Linien die Teile der Geometrien korrekt identifiziert werden, die sich näher an der Kamera befinden.

Wenn ich die negative Skalierung entferne, die ich einfügen musste, sehe ich:

Bildbeschreibung hier eingeben

Du siehst also, dass ich immer noch im selben Boot bin - ich brauche immer noch diese vertikalen und horizontalen Kippbewegungen in meinen Matrizen, um die Dinge richtig erscheinen zu lassen.

Um den Leuten einen Repro zum Spielen zu geben, finden Sie hier den vollständigen Code, der zum Generieren der obigen Informationen erforderlich ist. Wenn Sie über das Testkabel laufen möchten, installieren Sie einfach das xunit-Paket:

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
}
mir--
quelle
3
Kennen Sie das Konzept der Händigkeit von Koordinatensystemen? Weitere Informationen finden Sie unter dem Link.
MooseBoys
Ich habe Ihren Beitrag überprüft und um ehrlich zu sein, kann ich nicht verstehen, was Sie zu fragen versuchen (vielleicht bin ich es), aber zum Beispiel "Die Absicht ist es, Welt- und Bildräume wie diesen <image> zu unterstützen"? und wenn ich mir die Unit-Tests anschaue, sehen sie für mich so aus, als sollten sie die Etiketten in umgekehrter Reihenfolge haben? Noch eine Anmerkung, warum eine Kameraklasse eine Weltmatrix hat. Speichern Sie nicht bereits die Position und Drehung relativ zur Welt, damit Sie die Ansichtsmatrix erstellen können?
concept3d
und ich denke, dieser Beitrag kann Ihnen helfen, die Kameramatrix besser zu verstehen 3dgep.com/?p=1700
concept3d
@MooseBoys: Ich kenne mich mit Händigkeit aus, aber XNA gibt vor, Rechtshänder zu sein. Ich verstehe, dass Z aus dem Bildschirm in Richtung des Betrachters kommen sollte. Da ich (0,0,1) als Aufwärtsrichtung meiner Kamera verwendet habe, verstehe ich nicht, dass das Ergebnis umgedreht werden muss.
Ich
@ concept3d: könnte ich auch sein;) Meine Hauptfrage ist am Ende fett gedruckt, aber mir ist klar, dass du das nicht so gemeint hast. Ich weiß nicht, dass ich Ihren Standpunkt zum Umdrehen der Bezeichnungen in den UTs verstehe - von oben nach unten sind die Darstellungen Welt, Ansicht und Bildschirm. Wenn ich das falsch verstanden habe, bin ich schrecklich verwirrt. Was das Einfügen einer Weltmatrix in die Kamera betrifft, stimme ich zu: Ich verstehe noch nicht wirklich, warum ich das brauche, abgesehen von der Tatsache, Viewport.Projectdass eine Weltmatrix erforderlich ist. Deshalb habe ich meiner API eine Weltmatrix hinzugefügt. Es kann sein, dass ich es bei Bedarf entferne.
Ich

Antworten:

1

Ihre Diagramme können auf zwei Arten interpretiert werden. Es ist eine optische Täuschung, die Neckerwürfel genannt wird. Hier ist der Wikipedia-Artikel. Aus diesem Grund vermute ich, dass Sie tatsächlich den Boden sehen, wenn Sie denken, dass Sie nach oben schauen.

Wenn Sie können, negieren Sie in Ihrem ursprünglichen Code den Z-Wert Ihrer Kameraposition.

shade4159
quelle
Danke, aber ich glaube ehrlich gesagt nicht, dass es das ist. Ich habe Ihren Vorschlag ausprobiert und sehe, was ich erwartet habe: meine Szene von unten und falsch umgedreht auf der x- und y-Achse. Siehe auch Update 2 in meiner Frage.
Ich
Ihre Kamera befindet sich in this.viewport.Height, betrachtet this.viewport.Height/2, was bedeutet, dass Ihre Kamera in die -y-Richtung zeigt. Stellen Sie den Kamerastandort auf ein (this.viewport.Width / 2, 0, 100).
shade4159
Ich werde es bald versuchen, aber gemäß dem ersten Bild in meiner Frage möchte ich, dass es in eine Richtung zeigt.
Ich
Ja, es hat nicht funktioniert. Es setzt den Ursprung links unten, wohingegen das, was ich will, (0,0,0) links oben ist. Hast du es geschafft, mit dem von mir geposteten Code erneut zu antworten?
Ich
1

Da dies 2.5D ist, finde ich zwei Dinge seltsam:

this.projectionMatrix = Matrix.CreatePerspectiveFieldOfView(0.7853982f, viewport.AspectRatio, 1, 2)
* Matrix.CreateScale(1, -1, 1);
  1. Ändern Sie Ihr FOV zu Math.PiOver4().
  2. Von Ihren Nah / Fern-Werten ist Ihr Fern auf 2 eingestellt. Versuchen Sie, diesen Wert größer einzustellen (beginnen Sie mit 1000).
ChocoMan
quelle
0.785 ist dasselbe wie Pi / 4, aber ich habe es geändert, MathHelper.PiOver4um den Code ein wenig aufzuräumen. Die Tiefe des Ansichtsfensters spielt für das angegebene Problem keine Rolle, und ich kann nicht verstehen, warum ...
ich--
Ist dies 2,5D wie in 2D, das wie 3D aussieht (isometrische Zeichnungen auf einer ebenen Fläche), oder 2,5D wie in 3D, das sich visuell wie 2D verhält?
ChocoMan
Letzteres. Die ganze Mathematik ist 3D, aber ich rendere mit 2D-Sprites anstatt mit 3D-Modellen. Entschuldigung für jede Verwirrung ...
ich--
0

Das Anwenden einer Blindtransformation wie einer negativen Skala ist keine gute Idee, um das Problem zu verstehen.

Bei der ursprünglichen Bildschirmaufnahme und bei Update 1 entspricht der RGB-Rahmen genau einem rechtshändigen Koordinatensystem, da bei der Negation von zwei Achsen der Ansichtsmatrix das Vorzeichen unverändert bleibt.

Bei der Erfassung von Update 2 kehren Sie nur eine Achse der Projektionsmatrix um. Auf diese Weise wechseln Sie von einem rechtshändigen zu einem linkshändigen System. Verwenden Sie Daumen, Zeigefinger und Mittelfinger als X, Y und Z.

Da XNA rechtshändige Koordinaten mit (+ X rechts, + Y hoch, -Z vorwärts) verwendet, bedeutet dies, dass es wirklich ein Problem in dem gibt, was Sie anzeigen.

Sie entscheiden, dass Ihre Z-Koordinate die obere Koordinate ist (im Weltraum gesehen). Es bedeutet, dass Sie eine Transformation benötigen, um von unserem Weltraum (+ X nach rechts, + Z nach oben und + Y nach vorne) zum XNA zu gelangen.

Wenn Sie auf Ihre Hand schauen, wird eine PI/2Drehung um die X-Achse sichtbar. Sie sollten es vor der Projektion einfügen.

Ohne sie ist Ihr Flugzeug aufgrund der beiden unterschiedlichen Systeme kein Boden, sondern eine Wand.

galop1n
quelle
Danke, aber was meinst du mit "vor der Projektion"? Ich habe versucht , this.ProjectionMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) * Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, viewport.AspectRatio, 1, 2);und , this.ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, viewport.AspectRatio, 1, 2) * Matrix.CreateRotationX(MathHelper.PiOver2);und weder gearbeitet.
Ich
Auch wenn ich die Antwort darauf nicht erhalten habe, habe ich Ihnen das Kopfgeld zuerkannt, weil Ihre Antwort am tiefsten ging und versucht hat, das eigentliche Problem zu erklären.
Ich