跳转至内容

使用XNA/3D开发/摄像机和灯光创建游戏

来自Wikibooks,开放世界中的开放书籍



摄像机

[编辑 | 编辑源代码]

摄像机是3D世界中非常重要的组成部分,因为它代表了用户的视角。在开始时,必须定义摄像机的两个基本要素:位置和观察方向,然后XNA才能将内容渲染到您的3D世界中。

坐标系

[编辑 | 编辑源代码]

您需要注意的是,不同的图形系统使用不同的轴系。XNA使用右手坐标系。X表示右,Y表示上,Z表示屏幕外。将一个系统转换为另一个系统的操作是通过反转任意一个轴(但只能反转一个)来完成的。

角度和弧度

[编辑 | 编辑源代码]
角度 PI
45度 1/4 PI
90度 1/2 PI
180度 PI
270度 3/2 PI
360度 2 PI

数学辅助函数MathHelper.ToDegree(radians)MathHelper.ToRadians(degrees)可以帮助您进行转换。

矩阵和空间

[编辑 | 编辑源代码]

在渲染任何3D几何图形之前,必须设置3个矩阵。

  • 世界矩阵
    将对象/模型空间转换为世界空间的2D变换
您从Maya、3ds Max等软件中导入的模型包含一堆顶点位置,这些位置与该对象的中心有关。要使用这些数据,您需要使用世界矩阵将其从所谓的对象/模型空间转换为世界空间中的对象。
Matrix worldTranslation=Matrix.CreateTranslation(new Vector3(x,y,z));
使用此函数,您可以通过使用向量创建一个将对象的位置转换为世界空间的矩阵。转换后,您可以缩放、旋转和平移对象。但请记住,矩阵乘法不满足交换律,在XNA中,您始终需要按照S-R-T的顺序执行此操作。
  • 视矩阵
    将世界空间转换为视空间的2D变换
要从特定点观察您的世界,必须使用视矩阵将其从世界空间转换为视空间。
  • 投影矩阵
实际看到的已查看3D数据(称为视锥体)必须转换为您的2D屏幕。必须使用投影矩阵将视空间转换为屏幕空间。



摄像机设置

[编辑 | 编辑源代码]

如果您想在2D屏幕上为用户可视化您的3D内容,则需要使摄像机工作。您可以通过使用上面提到的视矩阵和投影矩阵来完成此操作,它们会根据您的需要转换数据。

视矩阵

[编辑 | 编辑源代码]

它保存摄像机的位置和观察方向——为此,您必须设置摄像机的Position、Target和Up向量。您可以使用Matrix.CreateLookAt方法来完成此操作。

viewMatrix = Matrix.CreateLookAt(camPosition, camTarget, camUpVector);

这三个参数是向量。

  • 位置向量很容易解释:它显示了摄像机在3D世界中的位置。
  • 目标向量也很容易解释:它显示了摄像机在3D世界中观察的点。
  • 向上向量很重要。想象一下,您手中拿着一个手机,它是您的摄像机。它自动为您提供了位置向量。下一步是聚焦您想要拍摄的目标。现在,您获得了位置向量和目标向量的具体值,但仍然有许多方法可以通过围绕其中心旋转来握住手机。位置向量和目标向量保持不变,但由于旋转,您可以拍摄的照片会发生变化。这就是为什么您需要声明哪个方向是向上的原因。只有在设置了这三个向量后,您才能获得一个唯一的摄像机。


这部分的完整代码可能如下所示

Matrix viewMatrix;
Vector3 camPosition = new Vector3(x,y,z);
Vector3 camTarget = new Vector3(x,y,z);
Vector3 camUpVector = new Vector3(x,y,z);

viewMatrix = Matrix.CreateLookAt(camPosition, camTarget, camUpVector);


投影矩阵

[编辑 | 编辑源代码]

它保存视锥体,即通过摄像机看到的3D世界中的所有内容,并应渲染到您的2D屏幕上。将您的摄像机视为一个点。现在创建两个矩形/层,一个靠近的较小的矩形和一个较大的远的矩形。绘制一条从摄像机点开始并连接两个矩形/层的每个右上角的线,然后对两个矩形/层的其他三个角执行相同的操作。如果您这样做,您将得到一个金字塔,其锥形端是摄像机点,底部是较大的矩形/层。这两者之间的一切都称为视景体。近平面和远平面之间的空间称为视锥体视锥体中的所有细节都将渲染到您的2D屏幕上。

创建投影矩阵的方法称为Matrix.CreatePerspectiveFieldOfView,应如下所示

projectionMatrix=Matrix.CreatePerspectiveFieldOfView(2f * (float)Math.Atan((float)Math.Tan(fieldOfView / 2f) / (aspectAxisConstraint == (int)aspectAxis.Horizontal ? zoomFactor : aspectRatio / originalAspect / zoomFactor)), aspectRatio, nearPlaneDistance, farPlaneDistance);
  • fieldOfView指定y方向的视野(弧度)。
  • aspectRatio是视空间宽度除以视空间高度的比值。包含渲染的3D世界的2D屏幕的纵横比。
  • nearPlaneDistance是摄像机与近平面之间的距离。
  • farPlaneDistance是摄像机与远平面之间的距离。

其他与视图相关的参数(例如更改纵横比轴的约束(以保持水平或垂直视空间,或使用指定的纵横比在低于该纵横比时更改方向)和缩放因子)可以在其结构体中指定为子参数,并且可以像这样更改

public enum AspectAxis : int
{
    Horizontal,
    Vertical
}
float originalAspect = 16f / 9f
float zoomFactor = 1f;
int aspectAxisConstraint = aspectAxis.Vertical;

上面两个FOV缩放子参数的默认值均为1。

例如,如果约束设置为1,原始纵横比设置为1.777777777778,当前纵横比为1.3333333333,则4:3分辨率下的视图将比16:9更高。

近平面和远平面也称为裁剪平面。请记住,前面的大型对象可能会挡住后面几乎大部分的3D世界,因此使用此平面将它们裁剪掉。对于非常小的远处的对象也适用,也许它们几乎不可见,但需要渲染它们。如果要节省资源,请裁剪它们。

  • 世界矩阵计算您想要渲染的每个数据及其位置。
  • 视图矩阵将在用户输入导致位置或方向发生变化时每次都会被重新计算。
  • 投影矩阵仅在窗口纵横比发生变化时才会被重新计算。因此,这通常发生在游戏的开始。



照亮场景似乎非常简单。将您的3D对象附加到您的世界中,使用上面提到的矩阵集,通过定义灯光的位置将灯光引入,一切就完成了。但它并不那么容易,如果没有正确的灯光设置,您的3D场景看起来不会很逼真。

每个3D对象都由三角形组成,这些三角形必须正确地被照亮。为此,您需要为每个三角形指定一个法向量。请记住准确设置它;法向量应该指向对象外部,如果它指向内部,则不会被正确照亮。借助光线方向和法线方向的信息,显卡可以计算需要“绘制”到三角形表面的光量。如果光线方向和法线方向垂直,则没有光线照射,投影为0。如果两个向量平行,则投影最大;表面将以最大强度被照亮。

现在您需要一个VertexPositionColorTexture类的实例,它应该如下所示

dataVertices[0] =  new VertexPositionNormalTexture(new Vector3(x,y,z), new Vector3(x,y,z), new Vector2(x,y));
  • 一个用于xyz位置的Vector3
  • 一个用于xyz表面法线的Vector3
  • 一个用于uv纹理坐标的Vector2


BasicEffect

[编辑 | 编辑源代码]

如果您想使用基本的灯光效果,您可以使用XNA中的BasicEffect类。使用它,您可以快速设置带灯光的3D世界。此代码可能如下所示

BasicEffect basicEffect;
basicEffect = new BasicEffect(GraphicsDevice, null);

设置变量并实例化它

basicEffect.World = worldMatrix;
basicEffect.View = viewMatrix;
basicEffect.Projection = projectionMatrix;
basicEffect.TextureEnabled = true;

设置上面提到的世界、视图和投影矩阵。如果您使用纹理,则需要启用它们。

basicEffect.LightingEnabled = true;
basicEffect.AmbientLightColor = new Vector3(0.1f, 0.1f, 0.1f); ;

启用灯光设置并定义一个环境颜色,以便您的对象始终被灯光照亮。

basicEffect.DirectionalLight0.Direction = new Vector3(x,y,z);
basicEffect.DirectionalLight0.DiffuseColor = new Vector3(0, 0, 0.5f);
basicEffect.DirectionalLight0.Enabled = true; 

您可以定义不同的光源(最多三个),设置方向和颜色并启用它们


最后...

effect.Begin();
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
   pass.Begin();
   
   pass.End();
}
effect.End();


Manissel681

华夏公益教科书