กล้องสำหรับเกม 2.5D


12

ฉันหวังว่าบางคนสามารถอธิบายสิ่งนี้กับฉันได้เหมือนฉันอายุ 5 ขวบเพราะฉันดิ้นรนกับสิ่งนี้เป็นเวลาหลายชั่วโมงและไม่สามารถเข้าใจสิ่งที่ฉันทำผิดได้

ฉันเขียนCameraชั้นเรียนสำหรับเกม 2.5D ของฉัน ความตั้งใจคือการสนับสนุนโลกและพื้นที่หน้าจอเช่นนี้:

ป้อนคำอธิบายรูปภาพที่นี่

กล้องเป็นสีดำด้านขวา แกน + Z เลื่อนขึ้นด้านบนของภาพโดยให้ -Z ชี้ลง อย่างที่คุณเห็นทั้งอวกาศโลกและพื้นที่จอภาพมี (0, 0) ที่มุมบนซ้าย

ฉันเริ่มเขียนการทดสอบหน่วยเพื่อพิสูจน์ว่ากล้องของฉันทำงานได้ตามที่คาดไว้และนั่นคือสิ่งที่เริ่มแปลก ... การทดสอบของฉันพล็อตพิกัดในโลกมุมมองและพื้นที่หน้าจอ ในที่สุดฉันจะใช้การเปรียบเทียบภาพเพื่อยืนยันว่าถูกต้อง แต่สำหรับตอนนี้การทดสอบของฉันเพิ่งแสดงผลลัพธ์

ตรรกะการเรนเดอร์ใช้Camera.ViewMatrixในการแปลงพื้นที่ของโลกเพื่อดูพื้นที่และCamera.WorldPointToScreenเพื่อแปลงพื้นที่โลกเป็นพื้นที่หน้าจอ

นี่คือตัวอย่างทดสอบ:

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

และนี่คือสิ่งที่ปรากฏขึ้นเมื่อฉันรันการทดสอบนี้:

ป้อนคำอธิบายรูปภาพที่นี่

พื้นที่โลกดูโอเคแม้ว่าฉันสงสัยว่าแกน z กำลังเข้าสู่หน้าจอแทนที่จะเข้าหาผู้ชม

มุมมองพื้นที่ทำให้ฉันงุนงงอย่างสมบูรณ์ ฉันคาดหวังว่ากล้องจะอยู่ด้านบน (0, 0) และมองไปที่ศูนย์กลางของฉาก ดูเหมือนว่าแกน z นั้นจะผิดไปในทางที่ผิดและกล้องก็อยู่ในตำแหน่งตรงข้ามกับที่ฉันคาดไว้!

ฉันสงสัยว่าพื้นที่หน้าจอจะเป็นอีกสิ่งหนึ่งโดยสิ้นเชิง แต่ทุกคนสามารถอธิบายสิ่งที่ฉันทำผิดในCameraชั้นเรียนได้ไหม


UPDATE

ฉันทำให้ความคืบหน้าในแง่ของการได้รับสิ่งต่าง ๆ ให้มองเห็นตามที่ฉันคาดหวัง แต่ผ่านสัญชาตญาณ: ไม่ใช่ความเข้าใจที่แท้จริงของสิ่งที่ฉันทำ การตรัสรู้ใด ๆ จะได้รับการชื่นชมอย่างมาก

ฉันรู้ว่าพื้นที่การดูของฉันพลิกทั้งในแนวตั้งและแนวนอนเมื่อเทียบกับที่ฉันคาดไว้ดังนั้นฉันจึงเปลี่ยนมุมมองเมทริกซ์เพื่อปรับขนาดตาม:

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

ฉันสามารถรวมCreateScaleสายสองสายได้ แต่แยกสายไว้เพื่อความชัดเจน อีกครั้งฉันไม่รู้ว่าทำไมสิ่งนี้ถึงมีความจำเป็น แต่มันทำให้พื้นที่การดูของฉันคงที่:

ป้อนคำอธิบายรูปภาพที่นี่

แต่ตอนนี้พื้นที่หน้าจอของฉันต้องพลิกในแนวตั้งดังนั้นฉันจึงปรับเปลี่ยนเมทริกซ์การฉายตาม:

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

และผลลัพธ์นี้เป็นสิ่งที่ฉันคาดหวังจากความพยายามครั้งแรก:

ป้อนคำอธิบายรูปภาพที่นี่

ฉันเพิ่งลองใช้Cameraเพื่อแสดงสไปรต์ผ่านทางSpriteBatchเพื่อให้แน่ใจว่าทุกอย่างทำงานได้ที่นั่นเช่นกันและทำเช่นนั้น

แต่คำถามยังคงอยู่: ทำไมฉันต้องทำทุกอย่างเพื่อให้ได้พิกัดอวกาศตามที่ฉันคาดไว้


อัพเดท 2

ฉันได้ปรับปรุงตรรกะการเรนเดอร์ของฉันในชุดการทดสอบของฉันเพื่อให้รองรับรูปทรงเรขาคณิตและเพื่อให้บรรทัดมีน้ำหนักเบายิ่งห่างจากกล้องมากขึ้น ฉันต้องการทำเช่นนี้เพื่อหลีกเลี่ยงภาพลวงตาและเพื่อพิสูจน์ตัวเองต่อไปว่าฉันกำลังดูสิ่งที่ฉันคิดว่าฉันเป็น

นี่คือตัวอย่าง:

ป้อนคำอธิบายรูปภาพที่นี่

ในกรณีนี้ฉันมีรูปทรง 3 แบบคือลูกบาศก์ทรงกลมและรูปหลายเหลี่ยมที่ด้านบนสุดของลูกบาศก์ ขอให้สังเกตว่าการปรับความเข้มและการลดแสงของเส้นระบุส่วนต่างๆของรูปทรงเรขาคณิตให้ใกล้กับกล้องมากขึ้นอย่างไร

หากฉันลบมาตราส่วนเชิงลบที่ฉันต้องใส่เข้าไปฉันจะเห็น:

ป้อนคำอธิบายรูปภาพที่นี่

ดังนั้นคุณจะเห็นว่าฉันยังอยู่ในเรือลำเดียวกัน - ฉันยังต้องการการพลิกแนวตั้งและแนวนอนในเมทริกซ์เพื่อให้สิ่งต่าง ๆ ปรากฏอย่างถูกต้อง

ในความสนใจของการให้คนเล่นซ้ำกับที่นี่เป็นรหัสที่สมบูรณ์ที่จำเป็นในการสร้างข้างต้น หากคุณต้องการเรียกใช้ผ่านชุดทดสอบทดสอบเพียงติดตั้งแพ็คเกจ 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
คุณคุ้นเคยกับแนวคิดของความถนัดของระบบพิกัดหรือไม่? ตรวจสอบลิงค์สำหรับข้อมูลเพิ่มเติม
MooseBoys

ฉันตรวจสอบโพสต์ของคุณและบอกตามตรงฉันไม่เข้าใจว่าคุณกำลังพยายามถามอะไร (อาจเป็นฉัน) แต่ตัวอย่างเช่น "ความตั้งใจคือการสนับสนุนโลกและพื้นที่หน้าจอเช่น <image>" นี้หรือไม่? และดูการทดสอบหน่วยที่พวกเขามองฉันว่าพวกเขาควรจะมีฉลากในลำดับที่กลับกัน? อีกข้อสังเกตว่าทำไมคลาสกล้องมีเมทริกซ์โลกคุณยังไม่ได้จัดเก็บตำแหน่งและการหมุนที่สัมพันธ์กับโลกเพื่อให้คุณสามารถสร้างเมทริกซ์มุมมองได้
concept3d

และฉันคิดว่าโพสต์นี้สามารถช่วยให้คุณเข้าใจเมทริกซ์กล้อง3dgep.com/?p=1700 ได้
concept3d

@MooseBoys: ฉันคุ้นเคยกับความถนัด แต่ XNA อ้างว่าเป็นมือขวาซึ่งฉันเข้าใจว่าหมายถึง Z ควรออกมาจากหน้าจอต่อผู้ชม เนื่องจากฉันมีการใช้ (0,0,1) เป็นทิศทางขึ้นของกล้องของฉันฉันไม่เข้าใจความจำเป็นในการพลิกผล
ฉัน -

@ concept3d: อาจเป็นฉันเช่นกัน;) คำถามหลักของฉันคือตัวหนาในตอนท้าย แต่ฉันรู้ว่านั่นไม่ใช่สิ่งที่คุณหมายถึง ฉันไม่ทราบว่าฉันเข้าใจประเด็นของคุณเกี่ยวกับการพลิกป้ายใน UTs - จากบนลงล่างแสดงว่าเป็นโลกมุมมองแล้วหน้าจอ ถ้าฉันทำผิดฉันก็สับสนอย่างมาก สำหรับการรวมเมทริกซ์โลกเข้าในกล้องฉันเห็นด้วย: ฉันยังไม่เข้าใจจริง ๆ ว่าทำไมฉันจึงต้องการสิ่งนี้นอกเหนือจากข้อเท็จจริงที่ว่าViewport.Projectต้องใช้เมทริกซ์โลก ดังนั้นฉันจึงเพิ่มเมทริกซ์ของโลกลงใน API ของฉัน อาจเป็นได้ว่าฉันต้องลบมันออกถ้าจำเป็น
ฉัน -

คำตอบ:


1

ไดอะแกรมของคุณสามารถตีความได้สองวิธี มันเป็นภาพลวงตาออพติคอลที่เรียกว่า Cube Necker นี่คือบทความวิกิพีเดีย ด้วยเหตุนี้เมื่อคุณคิดว่าคุณกำลังดูที่ด้านบนฉันสงสัยว่าคุณอาจเห็นด้านล่าง

หากเป็นไปได้ในรหัสดั้งเดิมให้ลบล้างค่า z ของตำแหน่งกล้องของคุณ


ขอบคุณ แต่ฉันก็ไม่คิดว่าอย่างนี้ ฉันลองใช้คำแนะนำของคุณและฉันเห็นสิ่งที่คาดหวัง: ฉากของฉันจากด้านล่างและพลิกแกน x และ y ไม่ถูกต้อง นอกจากนี้โปรดดูการปรับปรุง 2 ในคำถามของฉัน
ฉัน -

กล้องของคุณตั้งอยู่ที่this.viewport.Heightดูthis.viewport.Height/2ซึ่งหมายความว่ากล้องของคุณชี้ไปในทิศทางที่ -y (this.viewport.Width / 2, 0, 100)ลองตั้งค่าตั้งกล้องของคุณไป
shade4159

จะลองเร็ว ๆ นี้ แต่ตามรูปแรกในคำถามของฉันฉันต้องการให้มันชี้ไปในทิศทางของฉัน
ฉัน -

ใช่มันไม่ทำงาน มันวางต้นกำเนิดที่ด้านล่างซ้ายในขณะที่สิ่งที่ฉันต้องการคือ (0,0,0) ที่มุมบนซ้าย คุณจัดการทำซ้ำด้วยรหัสที่ฉันโพสต์ได้หรือไม่
ฉัน -

1

ระบุว่านี่คือ 2.5D สองสิ่งที่ฉันพบที่แปลกคือ:

this.projectionMatrix = Matrix.CreatePerspectiveFieldOfView(0.7853982f, viewport.AspectRatio, 1, 2)
* Matrix.CreateScale(1, -1, 1);
  1. ลองเปลี่ยน FOV ของคุณเป็น Math.PiOver4().
  2. จากค่า Near / Far ของคุณFarของคุณถูกตั้งค่าเป็น 2 ลองตั้งค่าที่ใหญ่กว่า (เริ่มต้นด้วย 1,000)

0.785 นั้นเหมือนกับ Pi / 4 แต่ฉันเปลี่ยนมันMathHelper.PiOver4เพื่อทำความสะอาดโค้ดสักหน่อย ความลึกของวิวพอร์ตไม่ได้แตกต่างจากปัญหาที่ระบุไว้และฉันไม่สามารถเข้าใจได้ว่าทำไมมันจะ ...
ฉัน -

นี่คือ 2.5D เหมือนใน 2D ที่มีลักษณะ 3D (ภาพวาดสามมิติบนพื้นผิวเรียบ) หรือ 2.5D เหมือนกับใน 3D ที่มีลักษณะการมองเห็นเหมือน 2D หรือไม่?
ChocoMan

หลัง. คณิตศาสตร์ทั้งหมดเป็น 3D แต่ฉันเรนเดอร์โดยใช้ 2D sprite มากกว่าแบบจำลอง 3 มิติ ขออภัยในความสับสน ...
ฉัน -

0

การประยุกต์ใช้การแปลงแบบ blind เช่นสเกลเนกาทีฟไม่ใช่ความคิดที่ดีที่จะเข้าใจปัญหา

ในการจับภาพหน้าจอดั้งเดิมและการอัปเดต 1 ถ้าคุณดูที่กรอบ RGB มันจะจับคู่กับระบบพิกัดที่อยู่ทางขวาเท่าที่ควรเพราะลบสองแกนของเมทริกซ์มุมมอง

ในการอัปเดตการจับภาพ 2 คุณกลับด้านเดียวของแกนฉายภาพโดยการทำเช่นนี้คุณกำลังย้ายจากมือขวาไปยังระบบมือซ้าย ใช้นิ้วโป้งนิ้วชี้และนิ้วกลางเป็น X, Y และ Z

เนื่องจาก XNA ใช้พิกัดทางขวาด้วย (+ X ขวา, + Y ขึ้น, -Z ไปข้างหน้า) นั่นหมายความว่ามีปัญหาจริงๆในสิ่งที่คุณกำลังแสดง

คุณตัดสินใจว่าพิกัด Z ของคุณเพิ่มขึ้น (ดังที่เห็นในส่วนอวกาศของการจับ) หมายความว่าคุณต้องการการเปลี่ยนแปลงเพื่อย้ายจากอวกาศโลกของเรา (+ X ขวา, + Z ขึ้นและ + Y ไปข้างหน้า) ไปยัง XNA

หากคุณมองมือของคุณมันจะแสดงการPI/2หมุนรอบแกน X คุณควรแทรกก่อนฉายภาพ

ถ้าไม่มีมันเพราะทั้งสองระบบต่างกันระนาบของคุณไม่ใช่พื้น แต่เป็นกำแพง


ขอบคุณ แต่สิ่งที่คุณหมายถึง "ก่อนการฉาย"? ฉันพยายาม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);และไม่ทำงาน
ฉัน -

แม้ว่าฉันจะไม่ได้รับคำตอบจากสิ่งนี้ แต่ฉันก็มอบรางวัลให้คุณเพราะคำตอบของคุณนั้นลึกที่สุดและพยายามอธิบายปัญหาที่เกิดขึ้นจริง
ฉัน -
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.