如何在Matrix3D中设置这种透视变换?

时间:2010-07-14 21:37:39

标签: .net silverlight image matrix gdi+

我有一个图像并且有一些值使它成为Silverlight中的透视图,但是无法弄清楚我需要做什么才能实现它。最重要的是我有一个叫做“视野”的角度( FOV )。

这是正常情况: normal

例如:

X =   30°             X =   30°             X   =  30°
FOV = 30°             FOV = 60°             FOV = 120°
        

X =   60°             X =   60°               X =  60°
FOV = 30°             FOV = 60°             FOV = 120°
                       



任何帮助都将非常感谢我带您完成数学计算,以便在Silverlight中重现这些内容。

7 个答案:

答案 0 :(得分:6)

我认为每个人都遇到的问题是视角转换需要视口转换。

试试这个:

private void ApplyProjection()
{
    double fovY = FOV * Math.PI / 180 / 2.0;
    double translationZ = -image1.ActualHeight / Math.Tan(fovY / 2.0);
    double theta = YourAngleX * Math.PI / 180.0;

    Matrix3D centerImageAtOrigin = TranslationTransform(
             -image1.ActualWidth / 2.0,
             -image1.ActualHeight / 2.0, 0);
    Matrix3D invertYAxis = CreateScaleTransform(1.0, -1.0, 1.0);
    Matrix3D rotateAboutY = RotateYTransform(theta);
    Matrix3D translateAwayFromCamera = TranslationTransform(0, 0, translationZ);
    Matrix3D perspective = PerspectiveTransformFovRH(fovY,
            image1.ActualWidth / image1.ActualHeight,   // aspect ratio
            1.0,                                                // near plane
            1000.0);                                            // far plane
    Matrix3D viewport = ViewportTransform(image1.ActualWidth, image1.ActualHeight);

    Matrix3D m = centerImageAtOrigin * invertYAxis;
    m = m * rotateAboutY;
    m = m * translateAwayFromCamera;
    m = m * perspective;
    m = m * viewport;

    Matrix3DProjection m3dProjection = new Matrix3DProjection();
    m3dProjection.ProjectionMatrix = m;

    image1.Projection = m3dProjection;
}

private Matrix3D TranslationTransform(double tx, double ty, double tz)
{
    Matrix3D m = new Matrix3D();

    m.M11 = 1.0; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
    m.M21 = 0.0; m.M22 = 1.0; m.M23 = 0.0; m.M24 = 0.0;
    m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
    m.OffsetX = tx; m.OffsetY = ty; m.OffsetZ = tz; m.M44 = 1.0;

    return m;
}

private Matrix3D CreateScaleTransform(double sx, double sy, double sz)
{
    Matrix3D m = new Matrix3D();

    m.M11 = sx; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
    m.M21 = 0.0; m.M22 = sy; m.M23 = 0.0; m.M24 = 0.0;
    m.M31 = 0.0; m.M32 = 0.0; m.M33 = sz; m.M34 = 0.0;
    m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;

    return m;
}

private Matrix3D RotateYTransform(double theta)
{
    double sin = Math.Sin(theta);
    double cos = Math.Cos(theta);

    Matrix3D m = new Matrix3D();

    m.M11 = cos; m.M12 = 0.0; m.M13 = -sin; m.M14 = 0.0;
    m.M21 = 0.0; m.M22 = 1.0; m.M23 = 0.0; m.M24 = 0.0;
    m.M31 = sin; m.M32 = 0.0; m.M33 = cos; m.M34 = 0.0;
    m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;

    return m;
}

private Matrix3D RotateZTransform(double theta)
{
    double cos = Math.Cos(theta);
    double sin = Math.Sin(theta);

    Matrix3D m = new Matrix3D();
    m.M11 = cos; m.M12 = sin; m.M13 = 0.0; m.M14 = 0.0;
    m.M21 = -sin; m.M22 = cos; m.M23 = 0.0; m.M24 = 0.0;
    m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
    m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
    return m;
}

private Matrix3D PerspectiveTransformFovRH(double fieldOfViewY, double aspectRatio, double zNearPlane, double zFarPlane)
{
    double height = 1.0 / Math.Tan(fieldOfViewY / 2.0);
    double width = height / aspectRatio;
    double d = zNearPlane - zFarPlane;

    Matrix3D m = new Matrix3D();
    m.M11 = width; m.M12 = 0; m.M13 = 0; m.M14 = 0;
    m.M21 = 0; m.M22 = height; m.M23 = 0; m.M24 = 0;
    m.M31 = 0; m.M32 = 0; m.M33 = zFarPlane / d; m.M34 = -1;
    m.OffsetX = 0; m.OffsetY = 0; m.OffsetZ = zNearPlane * zFarPlane / d; m.M44 = 0;

    return m;
}

private Matrix3D ViewportTransform(double width, double height)
{
    Matrix3D m = new Matrix3D();

    m.M11 = width / 2.0; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
    m.M21 = 0.0; m.M22 = -height / 2.0; m.M23 = 0.0; m.M24 = 0.0;
    m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
    m.OffsetX = width / 2.0; m.OffsetY = height / 2.0; m.OffsetZ = 0.0; m.M44 = 1.0;

    return m;
}

这将创建适当的透视转换并匹配PowerPoint正在生成的内容。

此代码改编自MSDN

答案 1 :(得分:3)

经过多次演绎之后,我实际上同意“Ladislav Mrnka”的矩阵答案是最简单的解决方案并且已经投了他们的答案。

只需离开下面的示例即可为您提供一些内容,但您需要通过Matrix3DProjection进行更新。

您似乎将源图片视为具有几种可能的视野之一,例如:好像用120°的广角镜头或30°的变焦镜头拍摄。然后,您将尝试在显示时重现原始场景的纵横比。这是对的吗?

如果是这样,您实际上想要在使用透视变换旋转图片之前水平拉伸图片以恢复隐式宽度的过程。这意味着你实际上试图解决两个单独的(更简单的)数学问题,例如:

  • 根据FOV,宽高比和幅度计算图像的显示宽度。宽度(使用X-Scaling)。
  • 计算所需的旋转以适合给定宽度的透视变换,使其达到所需的显示宽度(围绕Y轴的投影旋转)。

我遇到的困难是示例照片并未指出有关显示的任何特定规则。显示宽度都有所不同,所以我无法确定你的最终结果是什么。如果您能提供更多信息,我应该能够提供具体的计算。

好的,根据您在PowerPoint中使用Perspective设置,确实需要执行以下两个步骤:

  • 缩放水平尺寸(根据您的“X”角)
  • 应用投影变换以模拟PowerPoint中的透视角度

第一次计算非常简单。您需要将比例设置为余弦(X角)。 第二个是估计,因为Powerpoint透视角似乎与旋转无关。

我在下面提供了一个完整的示例XAML和代码隐藏,以生成显示的应用*。

alt text

***注意:有一个严重的缺陷,即投影变换无法将图像扭曲到您需要的程度。我正在尝试使用Matrix3DProjection,解决方案将遵循**

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    x:Class="PerspectivePhotosTest.PerspectivePhotos"
    d:DesignWidth="640" d:DesignHeight="480">

    <Grid x:Name="LayoutRoot">
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <Image x:Name="SampleImage" Height="101" Source="Image1.png" Stretch="Fill" VerticalAlignment="Center" HorizontalAlignment="Center" Width="128" RenderTransformOrigin="0.5,0.5">
                <Image.Projection>
                    <PlaneProjection x:Name="Rotation" RotationY="0"/>
                </Image.Projection>
                <Image.RenderTransform>
                    <CompositeTransform x:Name="Scale" ScaleX="1"/>
                </Image.RenderTransform>
            </Image>
            <Grid HorizontalAlignment="Left" VerticalAlignment="Top">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <TextBlock Text="&quot;X-Angle&quot;" VerticalAlignment="Top" HorizontalAlignment="Right"/>
                <TextBox x:Name="XAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=XValueSlider}" TextChanged="XAngleTextBox_TextChanged"/>
                <Slider x:Name="XValueSlider" Grid.Row="1" Grid.ColumnSpan="2" LargeChange="10" Maximum="80" SmallChange="1"/>
                <TextBlock Text="Perspective Angle" VerticalAlignment="Top" Grid.Row="2" HorizontalAlignment="Right"/>
                <TextBox x:Name="PerspectiveAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=PerspectiveSlider}" TextChanged="PerspectiveAngleTextBox_TextChanged"/>
                <Slider x:Name="PerspectiveSlider" Grid.Row="3" Grid.ColumnSpan="2" Maximum="120" SmallChange="1" LargeChange="10"/>
            </Grid>
        </StackPanel>
    </Grid>
</UserControl>

代码背后:

using System;
using System.Windows.Controls;

namespace PerspectivePhotosTest
{
    public partial class PerspectivePhotos : UserControl
    {
        public PerspectivePhotos()
        {
            InitializeComponent();
        }

        private void XAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            Scale.ScaleX = CalcScale(DegreeToRadian(double.Parse(XAngleTextBox.Text)));
        }

        private void PerspectiveAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            Rotation.RotationY = CalcTransform(double.Parse(PerspectiveAngleTextBox.Text));
        }

        private double CalcScale(double angleInRadians)
        {
            return Math.Cos(angleInRadians) * 2 + 0.3;
        }

        private double CalcTransform(double angleInDegrees)
        {
            return angleInDegrees / 2;
        }

        private double DegreeToRadian(double angle)
        {
            return Math.PI * angle / 180.0;
        }
    }
}

这应该为您提供一个句柄测试框架来尝试变体。我重新考虑了计算步骤,使其更加明显。

答案 2 :(得分:2)

请参阅thisthis文章。在页面中搜索“perspective”。

答案 3 :(得分:2)

This似乎完全解释了你的情景

答案 4 :(得分:2)

使用Matrix3DProjection并根据Direct3D this function末尾提供的矩阵设置转换。您需要以弧度表示的FOV,屏幕的纵横比和剪裁的两个距离(您定义的是有限的截头体)。如果你正在寻找更进一步的解释为什么这样设置你应该得到一些关于计算机图形的书。通常,用于投影变换的矩阵仅设置视图截头。 X轴周围的Rotatin对象是通过单独的转换来执行的,但它是计算机图形学的一般做法,我不确定它是否在Silverlight中相同。

修改

  

如果你需要同时使用旋转和   投影在单个矩阵中尝试使用   这一个:

xScale  0              0                     0
0       cos(X)*yScale  -sin(X)*z*zf/(zf-zn)  -sin(X)
0       sin(X)*yScale  cox(X)*z*zf/(zf-zn)   cos(X)
0       0              (-zn*zf)/(zf-zn)      0
     

cos(X)和sin(X)中的X是你的   围绕X asis以弧度旋转

     

z是Z方向的平移   因为你将不得不搬家   你的形象可以看到它。

     

yScale = cot(FOV / 2)FOV在   弧度

     

xScale = yScale / aspectRatio Aspect   比率由高度和宽度定义   用于图像渲染的面板

     

zn = Z near - 此前的一切   被修剪。 zf = Z far - evrything   在此之后被剪裁。请注意z   图像的坐标必须介于两者之间   那两个。

     

自从我最后这么做以来已经很久了   时间,所以我希望我已经计算好了   转型正确。矩阵   乘法应该是正确的但是   我有可能将它倍增   错误的顺序。

<强> EDIT2:

我以前的建议不起作用。用于计算的第一个矩阵是不正确的,因为Silverlight使用转置版本。不使用第二个转换图像到中心和视口转换。我已将Alison的建议代码(也可以找到here)与修改结合起来,以便拥有FovX和HiTech Magic的Silverlight应用程序。我以前从未写过Silverlight应用程序......这是工作样本:

<Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <Grid x:Name="Canvas" Background="White" Height="150" Width="200">
            <Image x:Name="SampleImage" Source="Penguins.jpg" Stretch="Fill" VerticalAlignment="Center" HorizontalAlignment="Center" Height="120" Width="160" RenderTransformOrigin="0.5,0.5" >
                <Image.Projection>
                    <Matrix3DProjection x:Name="Matrix" ProjectionMatrix="1, 0, 0, 0, 
                                                                      0, 1, 0, 0, 
                                                                      0, 0, 1, 0, 
                                                                      0, 0, 0, 1"/>
                </Image.Projection>
            </Image>
        </Grid>
        <Grid HorizontalAlignment="Center" VerticalAlignment="Top">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <TextBlock Text="&quot;X-Angle&quot;" VerticalAlignment="Top" HorizontalAlignment="Right"/>
            <TextBox x:Name="XAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=XValueSlider}" TextChanged="XAngleTextBox_TextChanged"/>
            <Slider x:Name="XValueSlider" Grid.Row="1" Grid.ColumnSpan="2" LargeChange="10" Maximum="80" SmallChange="1"/>
            <TextBlock Text="Perspective Angle" VerticalAlignment="Top" Grid.Row="2" HorizontalAlignment="Right"/>
            <TextBox x:Name="PerspectiveAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=PerspectiveSlider}" TextChanged="PerspectiveAngleTextBox_TextChanged" />
            <Slider x:Name="PerspectiveSlider" Grid.Row="3" Grid.ColumnSpan="2" Maximum="120" SmallChange="1" LargeChange="10" Value="60"/>
        </Grid>
    </StackPanel>
</Grid>



public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void UpdateMatrix()
    {
        double val;


        double X = double.TryParse(XAngleTextBox.Text, NumberStyles.Any, 
            CultureInfo.InvariantCulture, out val) ? val : 0.0;
        double FOV = double.TryParse(PerspectiveAngleTextBox.Text, NumberStyles.Any, 
            CultureInfo.InvariantCulture, out val) ? val : 0.0;

        ApplyProjection(FOV, X);
    }

    private double DegreeToRadian(double angle)
    {
        return Math.PI * angle / 180.0;
    }

    private void XAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
    {
        UpdateMatrix();
    }

    private void PerspectiveAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
    {
        UpdateMatrix();
    }

    private void ApplyProjection(double FOV, double X)
    {
        // Translate the image along the negative Z-axis such that it occupies 50% of the
        // vertical field of view.
        double fov = DegreeToRadian(FOV);
        double translationZ = -SampleImage.Width / Math.Tan(fov / 2.0); 
        double theta = DegreeToRadian(X);

        // You can create a 3D effect by creating a number of simple 
        // tranformation Matrix3D matrixes and then multiply them together.
        Matrix3D centerImageAtOrigin = TranslationTransform(
                 -SampleImage.Width / 2.0,
                 -SampleImage.Height / 2.0, 0);
        Matrix3D invertYAxis = CreateScaleTransform(1.0, -1.0, 1.0);
        Matrix3D rotateAboutY = RotateYTransform(theta);
        Matrix3D translateAwayFromCamera = TranslationTransform(0, 0, translationZ);
        Matrix3D perspective = PerspectiveTransformFovRH(fov,
                Canvas.ActualWidth / Canvas.ActualHeight,   
                1.0,                                                // near plane
                1000.0);                                            // far plane
        Matrix3D viewport = ViewportTransform(Canvas.ActualWidth, Canvas.ActualHeight);

        Matrix3D m = centerImageAtOrigin * invertYAxis;
        m = m * rotateAboutY;
        m = m * translateAwayFromCamera;
        m = m * perspective;
        m = m * viewport;

        Matrix3DProjection m3dProjection = new Matrix3DProjection();
        m3dProjection.ProjectionMatrix = m;

        SampleImage.Projection = m3dProjection;
    }

    private Matrix3D TranslationTransform(double tx, double ty, double tz)
    {
        Matrix3D m = new Matrix3D();

        m.M11 = 1.0; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
        m.M21 = 0.0; m.M22 = 1.0; m.M23 = 0.0; m.M24 = 0.0;
        m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
        m.OffsetX = tx; m.OffsetY = ty; m.OffsetZ = tz; m.M44 = 1.0;

        return m;
    }

    private Matrix3D CreateScaleTransform(double sx, double sy, double sz)
    {
        Matrix3D m = new Matrix3D();

        m.M11 = sx; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
        m.M21 = 0.0; m.M22 = sy; m.M23 = 0.0; m.M24 = 0.0;
        m.M31 = 0.0; m.M32 = 0.0; m.M33 = sz; m.M34 = 0.0;
        m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;

        return m;
    }

    private Matrix3D RotateYTransform(double theta)
    {
        double sin = Math.Sin(theta);
        double cos = Math.Cos(theta);

        Matrix3D m = new Matrix3D();

        m.M11 = cos; m.M12 = 0.0; m.M13 = -sin; m.M14 = 0.0;
        m.M21 = 0.0; m.M22 = 1.0; m.M23 = 0.0; m.M24 = 0.0;
        m.M31 = sin; m.M32 = 0.0; m.M33 = cos; m.M34 = 0.0;
        m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;

        return m;
    }

    private Matrix3D RotateZTransform(double theta)
    {
        double cos = Math.Cos(theta);
        double sin = Math.Sin(theta);

        Matrix3D m = new Matrix3D();
        m.M11 = cos; m.M12 = sin; m.M13 = 0.0; m.M14 = 0.0;
        m.M21 = -sin; m.M22 = cos; m.M23 = 0.0; m.M24 = 0.0;
        m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
        m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
        return m;
    }

    private Matrix3D PerspectiveTransformFovRH(double fieldOfView, double aspectRatio, double zNearPlane, double zFarPlane)
    {
        double width = 1.0 / Math.Tan(fieldOfView / 2.0);
        double height = width * aspectRatio;
        double d = zNearPlane - zFarPlane;

        Matrix3D m = new Matrix3D();
        m.M11 = width; m.M12 = 0; m.M13 = 0; m.M14 = 0;
        m.M21 = 0; m.M22 = height; m.M23 = 0; m.M24 = 0;
        m.M31 = 0; m.M32 = 0; m.M33 = zFarPlane / d; m.M34 = -1;
        m.OffsetX = 0; m.OffsetY = 0; m.OffsetZ = zNearPlane * zFarPlane / d; m.M44 = 0;

        return m;
    }

    private Matrix3D ViewportTransform(double width, double height)
    {
        Matrix3D m = new Matrix3D();

        m.M11 = width / 2.0; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
        m.M21 = 0.0; m.M22 = -height / 2.0; m.M23 = 0.0; m.M24 = 0.0;
        m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
        m.OffsetX = width / 2.0; m.OffsetY = height / 2.0; m.OffsetZ = 0.0; m.M44 = 1.0;

        return m;
    }

    private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
    {
        UpdateMatrix();
    }

}

答案 5 :(得分:1)

我的线性代数有点生疏有信心帮助,但 this article 看起来对你来说可能是一个很好的起点

答案 6 :(得分:1)

好。我将Ladislav Mrnka的矩阵变换答案与我之前的示例应用程序结合起来,但是他们的3D Matrix示例中似乎存在一些拼写错误,并且我对3D Matrix数学不够强大以纠正它。最终结果是空白显示,其中图像应为:(

为了让这个派对开始,我已经提供了一个完整的交互式测试应用程序(下面)与Xaml,以便Ladislav Mrnka(或其他具有更好3D数学的人)可以解决问题。

<Grid x:Name="LayoutRoot" Background="White">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <Image x:Name="SampleImage" Height="101" Source="Image1.png" Stretch="Fill" VerticalAlignment="Center" HorizontalAlignment="Center" Width="128" RenderTransformOrigin="0.5,0.5">
            <Image.Projection>
                <Matrix3DProjection x:Name="Matrix" ProjectionMatrix="1, 0, 0, 0,
                                                                      0, 1, 0, 0,
                                                                      0, 0, 1, 0,
                                                                      0, 0, 0, 1"/>
            </Image.Projection>
        </Image>
        <Grid HorizontalAlignment="Left" VerticalAlignment="Top">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <TextBlock Text="&quot;X-Angle&quot;" VerticalAlignment="Top" HorizontalAlignment="Right"/>
            <TextBox x:Name="XAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=XValueSlider}" TextChanged="XAngleTextBox_TextChanged"/>
            <Slider x:Name="XValueSlider" Grid.Row="1" Grid.ColumnSpan="2" LargeChange="10" Maximum="80" SmallChange="1"/>
            <TextBlock Text="Perspective Angle" VerticalAlignment="Top" Grid.Row="2" HorizontalAlignment="Right"/>
            <TextBox x:Name="PerspectiveAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=PerspectiveSlider}" TextChanged="PerspectiveAngleTextBox_TextChanged"/>
            <Slider x:Name="PerspectiveSlider" Grid.Row="3" Grid.ColumnSpan="2" Maximum="120" SmallChange="1" LargeChange="10"/>
            <TextBlock Text="Z-Near" VerticalAlignment="Top" Grid.Row="4" HorizontalAlignment="Right"/>
            <TextBox x:Name="ZNearTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=ZNearSlider}" TextChanged="ZNearTextBox_TextChanged"/>
            <Slider x:Name="ZNearSlider" Grid.Row="5" Grid.ColumnSpan="2" Minimum="-1000" Maximum="1000" SmallChange="1" LargeChange="10"/>
            <TextBlock Text="Z-Far" VerticalAlignment="Top" Grid.Row="6" HorizontalAlignment="Right"/>
            <TextBox x:Name="ZFarTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Row="6" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=ZFarSlider}" TextChanged="ZFarTextBox_TextChanged"/>
            <Slider x:Name="ZFarSlider" Grid.Row="7" Grid.ColumnSpan="2" Minimum="-1000" Maximum="1000" SmallChange="1" LargeChange="10"/>
        </Grid>
    </StackPanel>
</Grid>

代码隐藏:

using System;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace PerspectivePhotoTest
{
    public partial class PerspectivePhotosMatrix : UserControl
    {
        public PerspectivePhotosMatrix()
        {
            InitializeComponent();
        }

        private void UpdateMatrix()
        {
            double X = DegreeToRadian(double.Parse(XAngleTextBox.Text));
            double xScale = X;
            double FOV = DegreeToRadian(double.Parse(PerspectiveAngleTextBox.Text));
            double yScale = Math.Cos(FOV / 2) / Math.Sin(FOV / 2);
            double zn = double.Parse(ZNearTextBox.Text);
            double zf = double.Parse(ZFarTextBox.Text);
            double z = 0;

            Matrix.ProjectionMatrix = new Matrix3D(xScale, 0, 0, 0,
                0, Math.Cos(X) * yScale, -Math.Sin(X) * z * zf / (zf - zn), -Math.Sin(X),
                0, Math.Sin(X) * yScale, Math.Cos(X) * z * zf / (zf - zn), Math.Cos(X),
                0,0,(-zn*zf)/(zf-zn), 0);
        }

        private double DegreeToRadian(double angle)
        {
            return Math.PI * angle / 180.0;
        }

        private void XAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            UpdateMatrix();
        }

        private void PerspectiveAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            UpdateMatrix();
        }

        private void ZNearTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            UpdateMatrix();
        }

        private void ZFarTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            UpdateMatrix();
        }
    }
}