`
peizhiinfo
  • 浏览: 1425043 次
文章分类
社区版块
存档分类
最新评论

基于Visual C#的DirectX开发实例——透视投影矩阵

 
阅读更多

透视投影矩阵

投影就是将一个三维空间中某点(或线、面等)根据一定的规则转换到二维空间中,投影根据其投影方式又分为透视投影和正射投影,如下图即为透视投影的示意图:

透视投影有四个重要的参数,即视角(φ)、宽高比(Width/Height)、近平面(ZNearPlane)和远平面(ZFarPlane),所以在摄像机中能看到的区域就是视角范围内、近平面到远平面之间的范围。对于世界空间中任意一点或向量P,要将其投影到屏幕上显示,首先要将向量O表示世界空间的坐标原点)转换到摄像机空间,即向量C表示摄像机空间的坐标原点),其转换公式如下:

在此,摄像机矩阵CameraMatrix即摄像机视图矩阵。在得到摄像机空间中P点的向量后,然后根据摄像机投影相关参数将该点P投影到屏幕上,如下图所示:

由上图可知:

摄像机坐标原点至屏幕的计算距离为:

P点在单位平面上的投影位置为:

最后将单位平面上的投影位置转换到屏幕平面上,如下图所示:

所以在屏幕平面上的投影坐标为:

如下代码绘制三个空间向量到屏幕上的投影:

Vector4 calV1 = new Vector4(0f,0f,0f,1f);

calV1 =Vector4.Transform(calV1, viewMatrixComp);

int x1 = (int)(((float)calV1.X*this.Height/(float)(2 * calV1.Z * Math.Tan((float)Math.PI / 4))) + this.Width / 2);

int y1 = (int)(-calV1.Y * (float)this.Height / (2 * calV1.Z * Math.Tan((float)Math.PI / 4)) + this.Height / 2);

myFont.DrawText(sprite, "1", x1, y1, Color.Yellow);

Vector4 calV2 = new Vector4(5f, 10f, 0, 1);

calV2 = Vector4.Transform(calV2, viewMatrixComp);

int x2 = (int)(((float)calV2.X * this.Height / (float)(2 * calV2.Z * Math.Tan((float)Math.PI / 4))) + this.Width / 2);

int y2 = -(int)(calV2.Y * (float)this.Height / (2 * calV2.Z * Math.Tan((float)Math.PI / 4))) + this.Height / 2;

myFont.DrawText(sprite, "2", x2, y2, Color.Yellow);

Vector4 calV3 = new Vector4(10f, 0f, 0, 1);

calV3 = Vector4.Transform(calV3, viewMatrixComp);

int x3 = (int)(((float)calV3.X * this.Height / (float)(2 * calV3.Z * Math.Tan((float)Math.PI / 4))) + this.Width / 2);

int y3 = (int)(-calV3.Y * (float)this.Height / (2 * calV3.Z * Math.Tan((float)Math.PI / 4)) + this.Height / 2);

myFont.DrawText(sprite, "3", x3, y3, Color.Yellow);

其结果如下图所示:

DirectX中提供了创建投影矩阵的方法,如Matrix.PerspectiveFovLH()Matrix.PerspectiveFovRH()Matrix.PerspectiveLH()Matrix.PerspectiveRH()Matrix.PerspectiveOffCenterLH()Matrix.PerspectiveOffCenterRH()。下面主要介绍Matrix.PerspectiveFovLH()方法,其定义如下:

public staticMatrix PerspectiveFovLH(float fieldOfViewY,float aspectRatio,float znearPlane,float zfarPlane);

其中参数fieldOfViewY表示垂直Y轴平面(即XZ平面)上的视角,参数aspectRatio表示屏幕宽高比,参数znearPlane表示摄像机可见的最小距离,参数zfarPlane表示摄像机可见的最远距离。如果用h表示视图屏幕高度,则h=cot(fieldofViewY/2)(此处计算的为相对于真实屏幕尺寸的比例);视图屏幕宽度用w表示,则w=h×aspectRatio,则生成的投影矩阵各参数为:

如下代码生成的一个投影矩阵:

Matrix projMatrix = Matrix.PerspectiveFovLH((float)Math.PI / 2, this.Width / this.Height, 1.0f, 1000.0f);

device.Transform.Projection = projMatrix;

所以对于一个世界坐标系中的向量VectorOP,需要先经过摄像机空间坐标转换,然后经过投影矩阵变换,最后乘以屏幕矩阵才得到其在屏幕上的坐标位置,如下公式(在此向量都为四维向量、矩阵为四阶矩阵):

VectorOPP=VectorOP*viewMatrix*projectionMatrix*ScaleMatrix*ScreenMatrix

其中ScreenMatrix矩阵为屏幕变换矩阵,其各元素值为:

ScaleMatrix矩阵为缩放矩阵,该矩阵中缩放因子都一样,其值为向量VectorOP’=VectorOP*viewMatrix*projectionMatrix中最后一个值,如:

如下通过上面公式计算投影至平面位置的代码:

Vector4 VectorOP = new Vector4(5f, 10f, 0f, 1f);

VectorOP = Vector4.Transform(VectorOP, viewMatrixComp);

VectorOP = Vector4.Transform(VectorOP, projMatrix);

Matrix ScreenMatrix = Matrix.Zero;

ScreenMatrix.M11 = ScreenMatrix.M41= (float)this.Width / 2; ScreenMatrix.M22 = -(float)this.Height / 2;

ScreenMatrix.M42 = (float)this.Height / 2;

VectorOP = Vector4.Multiply(VectorOP, 1.0f / VectorOP.W);

VectorOP = Vector4.Transform(VectorOP, ScreenMatrix);

myFont.DrawText(sprite, "+", (int)VectorOP.X, (int)VectorOP.Y, Color.Yellow);

运行程序,由于设置显示向量与前面定义第二个向量的值一样,所以在此显示的位置与前面显示位置相同,如下图所示:

现在根据前面三个向量位置来绘制一个三角形来和前面自定义的算法来进行比较,如下代码(该段代码也位于Render()函数中):

Vector3 cameraPosition = new Vector3(0, 0, -30);

Vector3 cameraTarget = new Vector3(0f, 0.0f, 0.0f);

Vector3 cameraUpVector = new Vector3(0, 1, 0);

Matrix viewMatrixComp = Matrix.LookAtLH(cameraPosition, cameraTarget, cameraUpVector);

device.Transform.View = viewMatrixComp;

Matrix projMatrix = Matrix.PerspectiveFovLH((float)Math.PI / 2, (float)this.Width / this.Height, 1.0f, 1000.0f);

device.Transform.Projection = projMatrix;

device.RenderState.CullMode = Cull.None;

device.RenderState.Lighting = false;

device.RenderState.FillMode = FillMode.WireFrame;

CustomVertex.PositionColored[] vertices = new CustomVertex.PositionColored[3];//定义顶点

vertices[0].Position = new Vector3(0f, 0f, 0f);

vertices[0].Color = Color.Red.ToArgb();

vertices[1].Position = new Vector3(5f, 10f, 0f);

vertices[1].Color = Color.Green.ToArgb();

vertices[2].Position = new Vector3(10f, 0f, 0f);

vertices[2].Color = Color.Yellow.ToArgb();

device.VertexFormat = CustomVertex.PositionColored.Format;

device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, vertices);

运行程序,其结果如下图所示:

右上图可见通过自定义算法与DirectX中提供的投影算法得到的结果有差距,具体就是Y方向位置有差距,这主要是由Windows窗体的标题栏及边框宽度造成的,可以将前面所有的代码this.Width更改为this.ClientSize.Width、将this.Height更改为this.ClientSize.Height即可解决。当然也可以Main()函数中添加如下代码使得窗体没有框架:

MatrixExample.FormBorderStyle = FormBorderStyle.None;//设置窗体无框架

运行程序,其结果如下图所示:

这样得到的结果就会没有差距,当然也可以在程序中计算投影到屏幕上Y方向时减去标题栏的宽度来实现。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics