Kamera untuk Game 2.5D

12

Saya berharap seseorang dapat menjelaskan hal ini kepada saya seperti saya 5, karena saya telah berjuang dengan ini selama berjam-jam dan tidak bisa mengerti apa yang saya lakukan salah.

Saya telah menulis sebuah Camerakelas untuk game 2.5D saya. Tujuannya adalah untuk mendukung ruang dunia dan layar seperti ini:

masukkan deskripsi gambar di sini

Kamera adalah benda hitam di sebelah kanan. Sumbu + Z berada di atas dalam gambar itu, dengan -Z mengarah ke bawah. Seperti yang Anda lihat, ruang dunia dan ruang layar memiliki (0, 0) di kiri atas.

Saya mulai menulis beberapa unit test untuk membuktikan bahwa kamera saya berfungsi seperti yang diharapkan, dan di situlah segalanya mulai menjadi ... aneh. Tes saya merencanakan koordinat dalam ruang dunia, tampilan, dan layar. Akhirnya saya akan menggunakan perbandingan gambar untuk menyatakan bahwa mereka benar, tetapi untuk saat ini pengujian saya hanya menampilkan hasilnya.

Logika render digunakan Camera.ViewMatrixuntuk mengubah ruang dunia untuk melihat ruang, dan Camera.WorldPointToScreenuntuk mengubah ruang dunia menjadi ruang layar.

Berikut ini adalah contoh tes:

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

Dan inilah yang muncul ketika saya menjalankan tes ini:

masukkan deskripsi gambar di sini

Ruang dunia terlihat OK, meskipun saya curiga sumbu z masuk ke layar alih-alih ke arah penampil.

Ruang tampilan membuat saya benar-benar bingung. Saya mengharapkan kamera berada di atas (0, 0) dan melihat ke tengah adegan. Alih-alih, sumbu z tampaknya jalan yang salah, dan kamera diposisikan di sudut yang berlawanan dengan yang saya harapkan!

Saya menduga ruang layar akan menjadi hal yang sama sekali berbeda, tetapi adakah yang bisa menjelaskan apa yang saya lakukan salah di Camerakelas saya ?


MEMPERBARUI

Saya membuat beberapa kemajuan dalam hal membuat sesuatu terlihat secara visual seperti yang saya harapkan, tetapi hanya melalui intuisi: bukan pemahaman yang sebenarnya tentang apa yang saya lakukan. Pencerahan apa pun akan sangat dihargai.

Saya menyadari bahwa ruang tampilan saya dibalik baik secara vertikal maupun horizontal dibandingkan dengan yang saya harapkan, jadi saya mengubah matriks tampilan saya untuk menyesuaikan skala:

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

Saya bisa menggabungkan dua CreateScalepanggilan, tetapi membuatnya terpisah untuk kejelasan. Sekali lagi, saya tidak tahu mengapa ini perlu, tetapi itu memperbaiki ruang tampilan saya:

masukkan deskripsi gambar di sini

Tapi sekarang ruang layar saya perlu dibalik secara vertikal, jadi saya memodifikasi matriks proyeksi saya sesuai:

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

Dan ini menghasilkan apa yang saya harapkan dari upaya pertama saya:

masukkan deskripsi gambar di sini

Saya juga baru saja mencoba menggunakan Camerauntuk membuat sprite melalui a SpriteBatchuntuk memastikan semuanya bekerja di sana juga, dan itu berhasil.

Tetapi pertanyaannya tetap: mengapa saya harus melakukan semua pembalikan sumbu ini untuk mendapatkan koordinat ruang seperti yang saya harapkan?


PEMBARUAN 2

Sejak itu saya meningkatkan logika rendering di suite pengujian saya sehingga mendukung geometri dan garis-garis menjadi lebih terang semakin jauh dari kamera. Saya ingin melakukan ini untuk menghindari ilusi optik dan untuk lebih membuktikan kepada diri sendiri bahwa saya melihat apa yang saya pikirkan.

Berikut ini sebuah contoh:

masukkan deskripsi gambar di sini

Dalam hal ini, saya memiliki 3 geometri: kubus, bola, dan polyline di bagian atas permukaan kubus. Perhatikan bagaimana penggelapan dan penerangan garis dengan benar mengidentifikasi bagian-bagian geometri yang lebih dekat dengan kamera.

Jika saya menghapus skala negatif yang harus saya masukkan, saya melihat:

masukkan deskripsi gambar di sini

Jadi Anda bisa lihat saya masih di kapal yang sama - saya masih perlu membalik vertikal dan horizontal di matriks saya untuk mendapatkan hal-hal yang muncul dengan benar.

Demi memberi orang repro untuk dimainkan, berikut adalah kode lengkap yang diperlukan untuk menghasilkan hal di atas. Jika Anda ingin menjalankan melalui test harness, cukup instal paket 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
}
saya--
sumber
3
Apakah Anda terbiasa dengan konsep wenangan sistem koordinat? Lihat tautan untuk info lebih lanjut.
MooseBoys
Saya sedang memeriksa posting Anda dan sejujurnya saya tidak mengerti apa yang Anda coba tanyakan (mungkin ini saya) tetapi misalnya "Tujuannya adalah untuk mendukung ruang dunia dan layar seperti ini <gambar>" ?? dan melihat tes unit mereka melihat saya seperti mereka harus memiliki label dengan urutan terbalik? catatan lain mengapa kelas kamera memiliki matriks dunia, bukankah Anda sudah menyimpan posisi dan rotasi relatif terhadap dunia sehingga Anda dapat membuat matriks tampilan?
concept3d
dan saya pikir posting ini dapat membantu Anda lebih memahami matriks kamera 3dgep.com/?p=1700
concept3d
@ MooseBoys: Saya akrab dengan kidal, tetapi XNA bermaksud menjadi tangan kanan, yang saya pahami artinya Z harus keluar dari layar ke arah penonton. Karena saya telah menggunakan (0,0,1) sebagai arah kamera saya, saya tidak mengerti perlunya melakukan membalikkan hasilnya.
saya
@ concept3d: mungkin saya juga;) Pertanyaan utama saya adalah tebal pada akhirnya, tapi saya sadar bukan itu yang Anda maksud. Saya tidak tahu bahwa saya mengerti maksud Anda tentang membalik label pada UT - dari atas ke bawah, rendernya adalah dunia, tampilan, lalu layar. Jika saya salah, maka saya sangat bingung. Adapun untuk memasukkan matriks dunia dalam kamera, saya setuju: Saya belum benar-benar mengerti mengapa saya membutuhkan ini, terlepas dari kenyataan yang Viewport.Projectmembutuhkan matriks dunia. Oleh karena itu, saya menambahkan matriks dunia ke API saya. Mungkin saya akhirnya menghapusnya jika perlu.
saya

Jawaban:

1

Diagram Anda dapat ditafsirkan dalam satu dari dua cara. Ini adalah ilusi optik yang disebut kubus Necker. Ini artikel wikipedia. Karena itu, ketika Anda berpikir Anda melihat bagian atas, saya menduga Anda mungkin benar-benar melihat bagian bawah.

Jika Anda dapat, dalam kode asli Anda, meniadakan nilai z dari posisi kamera Anda.

shade4159
sumber
Terima kasih, tapi jujur ​​saya tidak berpikir ini dia. Saya mencoba saran Anda, dan saya melihat apa yang saya harapkan: adegan saya dari bawah, dan salah membalik sumbu x dan y. Juga, silakan lihat pembaruan 2 di pertanyaan saya.
saya--
Kamera Anda terletak di this.viewport.Height, melihat this.viewport.Height/2, yang berarti kamera Anda diarahkan ke arah -y. Coba atur lokasi kamera Anda (this.viewport.Width / 2, 0, 100).
shade4159
Akan mencoba segera, tetapi sesuai gambar pertama dalam pertanyaan saya, saya ingin itu menunjuk ke arah -y.
saya
Ya, itu tidak berhasil. Ini menempatkan asal di kiri bawah, sedangkan yang saya inginkan adalah (0,0,0) di kiri atas. Apakah Anda berhasil repro dengan kode yang saya posting?
saya
1

Mengingat ini 2.5D, ada dua hal yang aneh di sini:

this.projectionMatrix = Matrix.CreatePerspectiveFieldOfView(0.7853982f, viewport.AspectRatio, 1, 2)
* Matrix.CreateScale(1, -1, 1);
  1. Coba ubah FOV Anda menjadi Math.PiOver4().
  2. Dari nilai Near / Far Anda, Far Anda diatur ke 2. Coba atur nilai itu lebih besar (mulai dengan 1000).
ChocoMan
sumber
0,785 adalah hal yang sama dengan Pi / 4, tetapi saya mengubahnya MathHelper.PiOver4untuk membersihkan kode sedikit. Kedalaman viewport tidak ada bedanya dengan masalah yang dinyatakan, dan saya tidak bisa mengerti mengapa itu akan ...
saya
Apakah 2.5D ini seperti dalam 2D ​​yang terlihat 3D (gambar isometrik pada permukaan datar) atau 2.5D seperti dalam 3D yang berperilaku visual seperti 2D?
ChocoMan
yang terakhir. Semua matematika adalah 3D, tapi saya rendering menggunakan sprite 2D daripada model 3D. Permintaan maaf untuk segala kebingungan ...
saya
0

Menerapkan transformasi buta seperti skala negatif bukanlah ide yang baik untuk memahami masalahnya.

Pada tangkapan layar asli dan pembaruan 1, jika Anda melihat bingkai RGB, itu akan cocok dengan sistem koordinat tangan kanan sebagaimana mestinya karena meniadakan dua sumbu dari matriks tampilan menjaga tanda penentu tidak berubah.

Pada tangkapan pembaruan 2, Anda membalikkan hanya satu sumbu dari matriks proyeksi, dengan melakukan ini, Anda bergerak dari tangan kanan ke sistem tangan kiri. Gunakan ibu jari, jari telunjuk dan jari tengah Anda sebagai X, Y dan Z.

Karena XNA menggunakan koordinat tangan kanan dengan (+ X kanan, + Y atas, -Z maju), itu berarti benar-benar ada masalah dalam apa yang Anda tampilkan.

Anda memutuskan koordinat Z Anda adalah atas (seperti yang terlihat di bagian ruang dunia penangkapan). Ini berarti bahwa Anda memerlukan transformasi untuk bergerak dari ruang dunia kami (+ X ke kanan, + Z ke atas dan + Y ke depan) ke XNA.

Jika Anda melihat tangan Anda, itu akan mengungkapkan PI/2rotasi di sekitar sumbu X. Anda harus memasukkannya sebelum proyeksi.

Tanpanya, karena dua sistem yang berbeda, pesawat Anda bukan lantai melainkan dinding.

galop1n
sumber
Terima kasih, tetapi apa yang Anda maksud dengan "sebelum proyeksi"? Saya mencoba this.ProjectionMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) * Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, viewport.AspectRatio, 1, 2);dan this.ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, viewport.AspectRatio, 1, 2) * Matrix.CreateRotationX(MathHelper.PiOver2);dan tidak berhasil.
saya
Meskipun saya tidak mendapatkan jawaban dari ini, saya memberi Anda hadiah karena jawaban Anda yang paling dalam dan berusaha menjelaskan masalah yang sebenarnya.
saya