Aparat do gry 2.5D


12

Mam nadzieję, że ktoś może mi to wyjaśnić, jak mam 5 lat, ponieważ walczyłem z tym przez wiele godzin i po prostu nie mogę zrozumieć, co robię źle.

Napisałem Cameraklasę do mojej gry 2.5D. Chodzi o to, aby wspierać świat i przestrzenie ekranowe w następujący sposób:

wprowadź opis zdjęcia tutaj

Aparat jest czarną rzeczą po prawej stronie. Oś + Z znajduje się na tym obrazie z -Z skierowanym w dół. Jak widać, zarówno przestrzeń świata, jak i przestrzeń ekranu mają (0, 0) w lewym górnym rogu.

Zacząłem pisać testy jednostkowe, aby udowodnić, że mój aparat działa zgodnie z oczekiwaniami, i tam zaczęło się robić ... dziwnie. Moje testy wykreślają współrzędne w przestrzeniach świata, widoku i ekranu. W końcu użyję porównania obrazów, aby potwierdzić, że są poprawne, ale na razie mój test wyświetla wynik.

Logika renderowania wykorzystuje Camera.ViewMatrixdo przekształcania przestrzeni świata w celu wyświetlenia przestrzeni oraz Camera.WorldPointToScreentransformacji przestrzeni świata w przestrzeń ekranu.

Oto przykładowy test:

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

A oto, co pojawia się po uruchomieniu tego testu:

wprowadź opis zdjęcia tutaj

Przestrzeń na świecie wygląda OK, choć podejrzewam, że oś Z wchodzi w ekran zamiast w stronę widza.

Widok przestrzeni całkowicie mnie zaskoczył. Spodziewałem się, że kamera będzie siedzieć powyżej (0, 0) i patrzy w stronę centrum sceny. Zamiast tego oś Z wydaje się być niewłaściwa, a kamera jest ustawiona w przeciwległym rogu do tego, czego oczekuję!

Podejrzewam, że miejsce na ekranie będzie zupełnie inną rzeczą, ale czy ktoś może wyjaśnić, co robię źle w mojej Cameraklasie?


AKTUALIZACJA

Poczyniłem pewne postępy, jeśli chodzi o to, aby wyglądać wizualnie tak, jak się spodziewam, ale tylko dzięki intuicji: nie faktyczne rozumienie tego, co robię. Każde oświecenie byłoby bardzo mile widziane.

Zdałem sobie sprawę, że moja przestrzeń widzenia została odwrócona zarówno pionowo, jak i poziomo w porównaniu z tym, czego się spodziewałem, więc zmieniłem swoją matrycę widoku, aby odpowiednio skalować:

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

Mógłbym połączyć oba CreateScalewezwania, ale zostawiłem je osobno dla jasności. Ponownie nie mam pojęcia, dlaczego jest to konieczne, ale naprawiło to moją przestrzeń widzenia:

wprowadź opis zdjęcia tutaj

Ale teraz moje miejsce na ekranie musi być odwrócone w pionie, więc odpowiednio zmodyfikowałem matrycę projekcji:

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

A to skutkuje tym, czego oczekiwałem od mojej pierwszej próby:

wprowadź opis zdjęcia tutaj

Próbowałem też użyć Camerarenderowania duszków za pomocą a, SpriteBatchaby upewnić się, że wszystko też tam działa i działa.

Pozostaje jednak pytanie: dlaczego muszę robić to całe przerzucanie osi, aby uzyskać współrzędne przestrzeni zgodnie z oczekiwaniami?


AKTUALIZACJA 2

Od tego czasu ulepszyłem logikę renderowania w moim pakiecie testowym, aby obsługiwał geometrię i aby linie były jaśniejsze, im dalej od kamery. Chciałem to zrobić, aby uniknąć złudzeń optycznych i dodatkowo udowodnić sobie, że patrzę na to, co myślę.

Oto przykład:

wprowadź opis zdjęcia tutaj

W tym przypadku mam 3 geometrie: sześcian, kulę i polilinię na górnej powierzchni sześcianu. Zwróć uwagę, w jaki sposób przyciemnienie i rozjaśnienie linii poprawnie identyfikuje te części geometrii, które znajdują się bliżej kamery.

Jeśli usunę negatywne skalowanie, które musiałem wprowadzić, zobaczę:

wprowadź opis zdjęcia tutaj

Możesz więc zobaczyć, że wciąż jestem na tej samej łodzi - wciąż potrzebuję tych pionowych i poziomych przerzutów w moich matrycach, aby wszystko wyglądało poprawnie.

Aby dać ludziom możliwość gry, oto pełny kod potrzebny do wygenerowania powyższego. Jeśli chcesz uruchomić za pomocą wiązki testowej, po prostu zainstaluj pakiet 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
}

3
Czy znasz pojęcie przydatności układów współrzędnych? Sprawdź link, aby uzyskać więcej informacji.
MooseBoys

Sprawdzałem twój post i szczerze mówiąc, nie rozumiem o co próbujesz zapytać (może to ja), ale na przykład „Intencją jest wspieranie świata i przestrzeni ekranowych takich jak ten <image>” ?? i patrząc na testy jednostkowe, wyglądają dla mnie, jakby mieli mieć etykiety w odwrotnej kolejności? kolejna uwaga, dlaczego klasa kamery ma matrycę świata, czy nie zapisujesz już pozycji i obrotu względem świata, aby móc zbudować matrycę widoku?
concept3d

i myślę, że ten post pomoże ci lepiej zrozumieć matrycę aparatu 3dgep.com/?p=1700
concept3d

@MooseBoys: Znam się na ręczności, ale XNA wydaje się być praworęczny, co rozumiem, że Z powinien wychodzić z ekranu w stronę widza. Ponieważ używam (0,0,1) jako kierunku do góry mojego aparatu, nie rozumiem potrzeby wykonywania żadnego odwracania wyniku.
ja--

@ concept3d: też mogę być ja;) Moje główne pytanie jest pogrubione na końcu, ale zdaję sobie sprawę, że nie o to ci chodziło. Nie wiem, czy rozumiem twój punkt widzenia na odwracanie etykiet w UT - od góry do dołu, rendery to świat, widok, potem ekran. Jeśli się mylę, to jestem strasznie zdezorientowany. Jeśli chodzi o włączenie matrycy świata do aparatu, zgadzam się: jeszcze nie rozumiem, dlaczego jest mi potrzebny, poza tym, że Viewport.Projectwymaga matrycy świata. Dlatego dodałem macierz światową do mojego API. Może być tak, że w końcu to usuwam, jeśli to konieczne.
ja--

Odpowiedzi:


1

Twoje diagramy można interpretować na dwa sposoby. Jest to złudzenie optyczne zwane sześcianem Neckera. Oto artykuł na Wikipedii. Z tego powodu, gdy myślisz, że patrzysz na górę, podejrzewam, że rzeczywiście widzisz dół.

Jeśli możesz, w swoim oryginalnym kodzie, zaneguj wartość Z pozycji kamery.


Dzięki, ale szczerze mówiąc nie sądzę, że o to chodzi. Wypróbowałem twoją sugestię i widzę, czego się spodziewałem: moja scena z dołu i niepoprawnie odwrócona na osiach xiy. Zobacz także aktualizację 2 w moim pytaniu.
ja--

Kamera jest ustawiona na this.viewport.Height, patrząc na this.viewport.Height/2, co oznacza, że ​​kamera jest skierowana w kierunku -y. Spróbuj ustawić lokalizację kamery na (this.viewport.Width / 2, 0, 100).
shadow4159 16.10.13

Spróbuję wkrótce, ale jak na pierwsze zdjęcie w moim pytaniu, chcę, aby było skierowane w -y kierunku.
ja--

Tak, to nie zadziałało. Umieszcza początek w lewym dolnym rogu, podczas gdy chcę (0,0,0) w lewym górnym rogu. Czy udało Ci się wykonać repro przy użyciu kodu, który opublikowałem?
ja--

1

Biorąc pod uwagę, że jest to 2.5D, dwie rzeczy, które wydaje mi się tutaj dziwne, to:

this.projectionMatrix = Matrix.CreatePerspectiveFieldOfView(0.7853982f, viewport.AspectRatio, 1, 2)
* Matrix.CreateScale(1, -1, 1);
  1. Spróbuj zmienić FOV na Math.PiOver4().
  2. Z wartości Near / Far twoja Far jest ustawiona na 2. Spróbuj ustawić tę wartość na większą (zacznij od 1000).

0.785 to to samo, co Pi / 4, ale zmieniłem go, aby MathHelper.PiOver4trochę wyczyścić kod. Głębokość rzutni nie ma znaczenia dla podanego problemu i nie rozumiem, dlaczego ...
ja--

Czy to 2.5D jak w 2D, który wygląda 3D (rysunki izometryczne na płaskiej powierzchni) czy 2.5D jak w 3D, który wizualnie zachowuje się jak 2D?
ChocoMan,

ten drugi. Cała matematyka jest w 3D, ale renderuję za pomocą duszków 2D, a nie modeli 3D. Przepraszamy za zamieszanie ...
ja--

0

Zastosowanie transformacji ślepej jak skala ujemna nie jest dobrym pomysłem na zrozumienie problemu.

Na oryginalnym zrzucie ekranu i aktualizacji 1, jeśli spojrzysz na ramkę RGB, będzie ona pasować do układu współrzędnych po prawej stronie, tak jak powinna, ponieważ negacja dwóch osi matrycy widoku pozostawia znak determinujący bez zmian.

Podczas przechwytywania aktualizacji 2 odwracasz tylko jedną oś matrycy projekcyjnej, robiąc to, przechodzisz z systemu praworęcznego na leworęczny. Użyj kciuka, palca wskazującego i środkowego palca jako X, Y i Z.

Ponieważ XNA używa współrzędnych prawej ręki z (+ X w prawo, + Y w górę, -Z do przodu), oznacza to, że naprawdę jest problem z tym, co wyświetlasz.

Ty decydujesz, że twoja współrzędna Z jest górą (jak widać w części przechwytywania w przestrzeni światowej). Oznacza to, że potrzebujesz transformacji, aby przejść z naszej przestrzeni świata (+ X w prawo, + Z w górę i + Y do przodu) do XNA.

Jeśli spojrzysz na swoją rękę, odkryje ona PI/2obrót wokół osi X. Powinieneś wstawić go przed projekcją.

Bez niego, z powodu dwóch różnych systemów, twój samolot nie jest podłogą, ale ścianą.


Dzięki, ale co rozumiesz przez „przed projekcją”? Próbowałem this.ProjectionMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) * Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, viewport.AspectRatio, 1, 2);i this.ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, viewport.AspectRatio, 1, 2) * Matrix.CreateRotationX(MathHelper.PiOver2);ani działało.
ja--

Mimo że nie otrzymałem odpowiedzi na to pytanie, przyznałem ci nagrodę, ponieważ Twoja odpowiedź była najgłębsza i próbowałem wyjaśnić rzeczywisty problem.
ja--
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.