Camera cho trò chơi 2.5D


12

Tôi hy vọng ai đó có thể giải thích điều này cho tôi như tôi 5 tuổi, vì tôi đã phải vật lộn với điều này trong nhiều giờ và đơn giản là không thể hiểu những gì tôi đang làm sai.

Tôi đã viết một Cameralớp cho trò chơi 2.5D của mình. Mục đích là để hỗ trợ không gian thế giới và màn hình như thế này:

nhập mô tả hình ảnh ở đây

Máy ảnh là thứ màu đen bên phải. Trục + Z hướng lên trong hình ảnh đó, với -Z hướng xuống dưới. Như bạn có thể thấy, cả không gian thế giới và không gian màn hình đều có (0, 0) ở phía trên bên trái của chúng.

Tôi bắt đầu viết một số bài kiểm tra đơn vị để chứng minh rằng máy ảnh của tôi hoạt động như mong đợi và đó là nơi mọi thứ bắt đầu trở nên ... lạ. Các thử nghiệm của tôi vẽ đồ thị tọa độ trong thế giới, chế độ xem và không gian màn hình. Cuối cùng, tôi sẽ sử dụng so sánh hình ảnh để khẳng định rằng chúng là chính xác, nhưng bây giờ thử nghiệm của tôi chỉ hiển thị kết quả.

Logic kết xuất sử dụng Camera.ViewMatrixđể biến đổi không gian thế giới để xem không gian và Camera.WorldPointToScreenbiến đổi không gian thế giới thành không gian màn hình.

Dưới đây là một bài kiểm tra ví dụ:

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

Và đây là những gì bật lên khi tôi chạy thử nghiệm này:

nhập mô tả hình ảnh ở đây

Không gian thế giới có vẻ ổn, mặc dù tôi nghi ngờ trục z đang đi vào màn hình thay vì hướng về phía người xem.

Xem không gian đã làm tôi hoàn toàn bối rối. Tôi đã mong đợi máy ảnh sẽ ngồi phía trên (0, 0) và nhìn về phía trung tâm của cảnh. Thay vào đó, trục z dường như đi sai hướng và camera được đặt ở góc đối diện với những gì tôi mong đợi!

Tôi nghi ngờ không gian màn hình sẽ là một thứ khác hoàn toàn, nhưng bất cứ ai cũng có thể giải thích những gì tôi đang làm sai trong Cameralớp học của tôi ?


CẬP NHẬT

Tôi đã đạt được một số tiến bộ về việc khiến mọi thứ trông trực quan như tôi mong đợi, nhưng chỉ thông qua trực giác: không phải là một sự hiểu biết thực sự về những gì tôi đang làm. Bất kỳ sự giác ngộ sẽ được đánh giá rất cao.

Tôi nhận ra rằng không gian xem của tôi bị lật theo cả chiều dọc và chiều ngang so với những gì tôi mong đợi, vì vậy tôi đã thay đổi ma trận chế độ xem của mình theo tỷ lệ phù hợp:

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

Tôi có thể kết hợp hai CreateScalecuộc gọi, nhưng đã để chúng tách biệt cho rõ ràng. Một lần nữa, tôi không biết tại sao điều này lại cần thiết, nhưng nó đã sửa không gian xem của tôi:

nhập mô tả hình ảnh ở đây

Nhưng bây giờ không gian màn hình của tôi cần được lật theo chiều dọc, vì vậy tôi đã sửa đổi ma trận chiếu của mình cho phù hợp:

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

Và điều này dẫn đến kết quả mà tôi mong đợi từ lần thử đầu tiên:

nhập mô tả hình ảnh ở đây

Tôi cũng vừa thử sử dụng Camerađể kết xuất các sprite thông qua a SpriteBatchđể đảm bảo mọi thứ cũng hoạt động ở đó, và nó cũng vậy.

Nhưng câu hỏi vẫn còn: tại sao tôi cần phải thực hiện tất cả các lần lật trục này để có được tọa độ không gian theo cách tôi mong đợi?


CẬP NHẬT 2

Kể từ đó, tôi đã cải thiện logic kết xuất của mình trong bộ thử nghiệm để nó hỗ trợ hình học và để các đường kẻ nhẹ hơn khi chúng ở xa máy ảnh hơn. Tôi muốn làm điều này để tránh ảo ảnh quang học và để chứng minh thêm cho bản thân mình rằng tôi đang nhìn vào những gì tôi nghĩ tôi đang có.

Đây là một ví dụ:

nhập mô tả hình ảnh ở đây

Trong trường hợp này, tôi có 3 hình học: một hình khối, hình cầu và hình đa giác trên mặt trên của hình khối. Lưu ý cách làm tối và sáng của các đường xác định chính xác các phần của hình học gần với máy ảnh hơn.

Nếu tôi loại bỏ tỷ lệ âm mà tôi phải đưa vào, tôi thấy:

nhập mô tả hình ảnh ở đây

Vì vậy, bạn có thể thấy tôi vẫn ở trong cùng một chiếc thuyền - tôi vẫn cần những cú lật dọc và ngang trong ma trận của mình để khiến mọi thứ xuất hiện chính xác.

Trong lợi ích của việc cung cấp cho mọi người một repro để chơi với, đây là mã hoàn chỉnh cần thiết để tạo ra ở trên. Nếu bạn muốn chạy qua khai thác thử nghiệm, chỉ cần cài đặt gói 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
Bạn có quen thuộc với khái niệm về sự thuận tay của các hệ tọa độ không? Kiểm tra các liên kết để biết thêm.
MooseBoys

Tôi đã kiểm tra bài đăng của bạn và thành thật mà nói tôi không thể hiểu bạn đang cố hỏi điều gì (có thể là tôi) nhưng ví dụ "Ý định là hỗ trợ thế giới và không gian màn hình như <image>" ?? và nhìn vào các bài kiểm tra đơn vị họ nhìn tôi như thể họ nên có nhãn theo thứ tự ngược lại? một lưu ý khác tại sao một lớp máy ảnh có ma trận thế giới không phải bạn đã lưu trữ vị trí và xoay so với thế giới để bạn có thể xây dựng ma trận xem?
Concept3d

và tôi nghĩ rằng bài đăng này có thể giúp bạn hiểu rõ hơn về ma trận camera 3dgep.com/?p=1700
concept3d

@MooseBoys: Tôi quen thuộc với sự thuận tay, nhưng XNA có ý định thuận tay phải, điều mà tôi hiểu là Z nên ra khỏi màn hình về phía người xem. Vì tôi đã sử dụng (0,0,1) làm hướng lên cho máy ảnh của mình, tôi không hiểu sự cần thiết phải thực hiện bất kỳ lật kết quả nào.
tôi--

@ concept3d: cũng có thể là tôi;) Câu hỏi chính của tôi được in đậm ở cuối, nhưng tôi nhận ra đó không phải là ý bạn. Tôi không biết rằng tôi hiểu quan điểm của bạn về việc lật các nhãn trong UT - từ trên xuống dưới, kết xuất là thế giới, chế độ xem, sau đó là màn hình. Nếu tôi đã sai, thì tôi bối rối khủng khiếp. Về việc bao gồm một ma trận thế giới trong máy ảnh, tôi đồng ý: Tôi chưa thực sự hiểu tại sao tôi cần điều này, ngoài thực tế Viewport.Projectđòi hỏi phải có ma trận thế giới. Do đó, tôi đã thêm một ma trận thế giới vào API của mình. Nó có thể là tôi cuối cùng loại bỏ nó nếu cần thiết.
tôi--

Câu trả lời:


1

Sơ đồ của bạn có thể được giải thích theo một trong hai cách. Đó là một ảo ảnh quang học gọi là khối cổ. Đây là bài viết trên wikipedia. Bởi vì điều này, khi bạn nghĩ rằng bạn đang nhìn từ trên xuống, tôi nghi ngờ bạn thực sự có thể đang nhìn thấy đáy.

Nếu bạn có thể, trong mã gốc của bạn, phủ nhận giá trị z của vị trí máy ảnh của bạn.


Cảm ơn, nhưng tôi thực sự không nghĩ rằng đây là nó. Tôi đã thử đề xuất của bạn và tôi thấy những gì tôi mong đợi: cảnh của tôi từ bên dưới và lật không chính xác trên trục x và y. Ngoài ra, vui lòng xem cập nhật 2 trong câu hỏi của tôi.
tôi--

Máy ảnh của bạn được đặt tại this.viewport.Height, nhìn vào this.viewport.Height/2, có nghĩa là máy ảnh của bạn được chỉ theo hướng -y. Hãy thử đặt vị trí máy ảnh của bạn thành (this.viewport.Width / 2, 0, 100).
bóng4159

Sẽ cố gắng sớm, nhưng theo hình ảnh đầu tiên trong câu hỏi của tôi, tôi muốn nó chỉ theo hướng -y.
tôi--

Vâng, nó không hoạt động. Nó đặt gốc ở phía dưới bên trái, trong khi cái tôi muốn là (0,0,0) ở phía trên bên trái. Bạn đã quản lý để repro với mã tôi đã đăng?
tôi--

1

Cho rằng đây là 2.5D, hai điều ở đây tôi thấy lạ là:

this.projectionMatrix = Matrix.CreatePerspectiveFieldOfView(0.7853982f, viewport.AspectRatio, 1, 2)
* Matrix.CreateScale(1, -1, 1);
  1. Hãy thử thay đổi FOV của bạn thành Math.PiOver4().
  2. Từ các giá trị Gần / Xa của bạn, Xa của bạn được đặt thành 2. Thử đặt giá trị đó lớn hơn (bắt đầu bằng 1000).

0,785 tương tự như Pi / 4, nhưng tôi đã thay đổi nó MathHelper.PiOver4để làm sạch mã lên một chút. Độ sâu khung nhìn không có gì khác biệt đối với vấn đề đã nêu và tôi không thể hiểu tại sao nó lại ...
tôi--

Đây có phải là 2.5D như trong 2D trông 3D (bản vẽ đẳng cự trên bề mặt phẳng) hoặc 2.5D như trong 3D hoạt động trực quan như 2D không?
ChocoMan

cái sau Tất cả toán học là 3D, nhưng tôi kết xuất bằng cách sử dụng các họa tiết 2D thay vì mô hình 3D. Xin lỗi vì bất kỳ sự nhầm lẫn nào ...
tôi--

0

Áp dụng biến đổi mù như thang âm không phải là một ý tưởng tốt để hiểu vấn đề.

Trên bản chụp màn hình gốc và bản cập nhật 1, nếu bạn nhìn vào khung RGB, nó sẽ khớp với hệ tọa độ tay phải vì nó phủ định hai trục của ma trận chế độ xem giữ nguyên dấu hiệu xác định.

Khi chụp bản cập nhật 2, bạn chỉ đảo ngược một trục của ma trận chiếu, bằng cách này, bạn đang chuyển từ một tay phải sang một hệ thống thuận tay trái. Sử dụng ngón tay cái, ngón trỏ và ngón giữa của bạn là X, Y và Z.

Vì XNA đang sử dụng tọa độ thuận tay phải với (+ X phải, + Y lên, -Z về phía trước), điều đó có nghĩa là thực sự có vấn đề trong những gì bạn đang hiển thị.

Bạn quyết định tọa độ Z của bạn là lên (như đã thấy trong phần không gian thế giới của ảnh chụp). Điều đó có nghĩa là bạn cần một phép biến đổi để chuyển từ không gian thế giới của chúng ta (+ X sang phải, + Z lên và + Y về phía trước) sang XNA.

Nếu bạn nhìn vào bàn tay của bạn, nó sẽ tiết lộ một PI/2 vòng quay quanh trục X. Bạn nên chèn nó trước khi chiếu.

Không có nó, vì hai hệ thống khác nhau, máy bay của bạn không phải là một tầng mà là một bức tường.


Cảm ơn, nhưng ý của bạn là "trước khi chiếu" là gì? Tôi cố gắng this.ProjectionMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) * Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, viewport.AspectRatio, 1, 2);this.ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, viewport.AspectRatio, 1, 2) * Matrix.CreateRotationX(MathHelper.PiOver2);và không làm việc.
tôi--

Mặc dù tôi không nhận được câu trả lời từ điều này, tôi đã trao cho bạn tiền thưởng vì câu trả lời của bạn đã đi sâu nhất và cố gắng giải thích vấn đề thực tế.
tôi--
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.