65.9K
CodeProject 正在变化。 阅读更多。
Home

增强现实 - XNA 模型

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2012年8月22日

CPOL

8分钟阅读

viewsIcon

47748

downloadIcon

953

可通过照相机查看的地理坐标定位的 Xna 模型。

引言

该应用结合了运动 API、Geocoordinatewatcher、Xna 3D 模型和 Silverlight 的 PhotoCamera 类。

放置你的模型,校准手机,然后四处走动,从各个侧面和角度欣赏你的 3D 作品……

这些技术都不是什么真正新的东西。它们只是以一种可以轻松为我们(希望很快就能看到)提供一些有趣应用起点的​​方式组合在一起。

背景

在一些古老的遗址中漫步,通过手机看到那个辉煌时期它会是什么样子的,这难道不好吗?或者,在这个例子中,在空中漂浮着一些茶壶,营造出一种诡异的感觉……但说实话,它们看起来有些不合时宜,而且脱离了上下文。一如既往,天空是极限,可能性是无限的……

先决条件

至少你需要一种带有 Windows Phone 软件开发工具包 (SDK 7.1) 的 Visual Studio 2010。

如何操作……

你首先从模板“Windows Phone Silverlight and XNA application”创建一个新项目。你可以将你的项目命名为 SlXnaGeoMotion、SlXnaTeapots 或其他任何名称。

如果你不打算使用自己的 *.fbx 或 *.x 类型的 3D 模型,则可以删除模板创建的两个额外的“SlnaGeoMotionContent”和“SlXnaGeoMotionLib”项目。请务必从剩余的项目中删除对“SlXnaGeoMotionLib”的引用。

创建一个名为 Primitives3D 的目录,并添加现有的项目,可以从附加的 SlXnaGeoMotion.zip 文件或从

http://xbox.create.msdn.com/en-US/education/catalog/sample/primitives_3d

你将使用这些来稍后创建你的飞行茶壶或任何你想要的 3D 几何 XNA 模型。

现在,在这个小项目中,我们唯一还会关注的文件是 GamPage.xaml 及其“代码隐藏”GamePage.xaml.cs。

XAML

让我们从 xaml 开始

<Grid x:Name="LayoutRoot" Background="Transparent">
    <Canvas x:Name="viewfinderCanvas" Height="800" Margin="-110,0,-110,0">
        <Canvas.Background>
            <VideoBrush x:Name="videoBrush">
                <VideoBrush.RelativeTransform>
                    <CompositeTransform x:Name="viewfinderTransform"
                            Rotation="90" CenterX="0.5" CenterY="0.5">
                    </CompositeTransform>
                </VideoBrush.RelativeTransform>
            </VideoBrush>
        </Canvas.Background>
    </Canvas>
    <TextBlock x:Name="Info"
               Text=""
               Style="{StaticResource PhoneTextLargeStyle}"
               Grid.Row="0">    
    </TextBlock>   
</Grid>

使用 VideoBrush 将 Microsoft.Devices.PhotoCamera 类中的输出显示在容器类型的控件中,这是从 Windows Phone 7.5 Mango 设备显示相机输出的常用且首选的方式。

这里需要注意的是 90 度旋转,以便以纵向模式查看。您还应该注意到,我们使用了 -110 的左右边距,这将使我们的 Canvas(命名为 viewfinderCanvas)的总宽度为 110 + 480 + 110 = 600。这给了我们 600 / 800 或 3/4 的纵横比,这正是我们手机中物理摄像头实际提供的。因此,我们将保持所见真实物体比例的正确性。

关于 xaml 方面,我们只需要做这么多。


C# - 代码隐藏

我喜欢删除所有解释显而易见内容的注释,并将模板生成的语句(以区域的形式)隔离出来。每次实现新功能时,将有关不同主题的语句分组在不同的“#region”标签内也很有用。

在此程序中,主要主题是

- Silverlight

- PhotoCamera

- Motion(运动)

- 3D

- GeoCoordinateWatcher

我将尝试按照相同的顺序解释这里发生的情况,您可以在附加的 zip 文件中的源代码中引用实际的“#region”。

Silverlight

为了在 Xna 中渲染 Silverlight,我们必须首先定义并实例化一个 Microsoft.Xna.Framework.Graphics.UIElementRenderer。

UIElementRenderer silverlightRenderer;
silverlightRenderer = new UIElementRenderer(this, (int)ActualWidth, (int)ActualHeight);

如果您在程序的“Ondraw”部分渲染并绘制此对象作为 Xna Sprite,您应该能够将现实世界显示为 Xna 世界的背景。

spriteBatch.Begin();
silverlightRenderer.Render();
spriteBatch.Draw(silverlightRenderer.Texture, Vector2.Zero, Color.White);
spriteBatch.End();

对于我们这里使用的简单 3D 模型,接下来的语句几乎没有必要,但您应该习惯性地将它们包含进去。

SharedGraphicsDeviceManager.Current.GraphicsDevice.BlendState =
                                              BlendState.Opaque;
SharedGraphicsDeviceManager.Current.GraphicsDevice.DepthStencilState =
                                              DepthStencilState.Default;
SharedGraphicsDeviceManager.Current.GraphicsDevice.SamplerStates[0] =
                                              SamplerState.LinearWrap;

这将重新配置您的 GraphicsDeviceManager,以便更好地处理更高级的“蒙皮”3D 纹理模型,这些模型通常使用 Autocad、Maya 或 Blender 等工具创建。

PhotoCamera

这里没有什么特别的

PhotoCamera photoCamera;
photoCamera = new PhotoCamera();
videoBrush.SetSource(photoCamera);

这里最后的两行位于“OnNavigatedTo”中的“#region Silverlight”,抱歉,最后一行是我们在 GamePage.xaml 中的 xaml 代码中定义的并实例化的“photoCamera”被设置为“viewfinderCanvas”的 VideoBrush“videoBrush”的源。

运动

WP Mango 中一个“新”类,主要处理您如何握持相机、观察方向、纵向、横向等,基于您手机的罗盘、加速度计、可能的陀螺仪等。它提供关于 X、Y、Z 轴旋转的信息,以弧度或四元数表示,或者如本项目中那样以矩阵表示。还提供有关加速度的信息,但在此项目中并未考虑。

Motion motion;
motion = new Motion();
motion.TimeBetweenUpdates = timer.UpdateInterval;
motion.Start();

提供对此的访问

motion.CurrentValue.Attitude.RotationMatrix;

只需从“Microsoft.Devices.Sensors.Motion”实例化一个 motion,启动它,然后就可以通过使用 Motion 的“CurrentValue”中的“Attitude”来开始。有些人更喜欢连接 CurrentValueChanged 事件,但在这种简单的例子中,我并没有真正看到这样做的意义。

3D

初始化

TeapotPrimitive teapot;
Matrix teapotWorld;
Matrix projection;
Matrix view;
teapot = new TeapotPrimitive(
       SharedGraphicsDeviceManager.Current.GraphicsDevice);

从你的 Primitives3D TeaPotPrimitive 类创建茶壶。请确保在你的 GamePage.xaml.cs 文件中声明你正在“使用 Primitives3D”。对于那些没有注意到的人。使用茶壶作为例子,当然是为了故意调侃所有 Java 爱好者。 微笑 | <img src= " src="https://codeproject.org.cn/script/Forums/Images/smiley_smile.gif" />

更新或绘制部分

view = Matrix.CreateRotationX(MathHelper.PiOver2);
view *= motion.CurrentValue.Attitude.RotationMatrix;
view *= Matrix.CreateLookAt(Vector3.Zero, new Vector3(0.0f, 0.0f, -0.001f), Vector3.Up);
projection = Matrix.CreatePerspectiveFieldOfView(
  1, SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.AspectRatio, 0.1f, 100f);

首先,您需要用真实世界的视图定向您的 XNA 世界。这是通过围绕 X 轴将您的 XNA 世界视图旋转 90 度来完成的。真实世界的 Y 轴(海拔)和 XNA 世界的 Y 轴现在都指向天空。

这时 Motion API 就派上用场了。投影矩阵的创建应该真正移到 OnNavigatedTo 事件中,以节省游戏循环的执行时间。(不需要不断更新。)

茶壶

teapotWorld = Matrix.CreateScale(3.0f)
        * Matrix.CreateFromYawPitchRoll(
                 MathHelper.ToRadians(0), MathHelper.ToRadians(0), MathHelper.ToRadians(0))
        * Matrix.CreateTranslation(new Vector3(
                (float)PositionChangeX + 0.0f, 0.0f, (float)(PositionChangeZ - 5.0f)));
teapot.Draw(teapotWorld, view, projection, Color.Red);

也许有点难以消化,但请慢慢来,您应该会轻松应对。

Matrix teapotWorld 用于操作此 Model 在您的 XNA 世界中的位置。

您可以根据需要缩放、旋转和翻译(定位)您的茶壶。请记住,这只能以缩放、旋转、翻译的顺序进行。先旋转、再翻译然后缩放可能会导致意外结果。矩阵乘法在这方面不像普通乘法。

永远不要使用以 0 作为参数的 CreateScale Matrix 并期望在您的视图中看到任何东西。

如果您想保持模型的当前尺寸,则使用 1。(或者在这种情况下,您可以完全省略此矩阵)

在 Matrix.CreateFromYawPitchRoll(决定茶壶的哪一面朝向您)中,我喜欢使用 MathHelper.ToRadians,因为我总是很难想到弧度。我发现用旋转度来思考更容易。

CreateTranslation 部分使用了变量 PositionChangeX 和 PositionChangeZ,我将在“GeoCoordinateWatcher”->“Position Change”部分再讨论它们。

GeoCoordinateWatcher

初始化

const double MeterInLatitude = 111290.91975341858;
const double MeterInLongitude = 55729.5173743959;
double PositionChangeX = 0;
double PositionChangeZ = 0;
GeoCoordinateWatcher geoWatcher;
GeoPositionStatus currentState = GeoPositionStatus.Initializing;
public GeoPosition<GeoCoordinate> InitialGeoCoordinate { get; set; }
public GeoPosition<GeoCoordinate> CurrentGeoCoordinate { get; set; }

我们将在“Points Of Interest”部分讨论“MeterInLatitude”和“MeterInLongitude”。

实例化

geoWatcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
geoWatcher.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>
(geoWatcher_StatusChanged);
geoWatcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>
(geoWatcher_PositionChanged);
geoWatcher.MovementThreshold = 0.0;
geoWatcher.Start();

连接了两个事件,然后启动了该设备……

状态

void geoWatcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
{
    currentState = e.Status;
    switch (e.Status)
    {
        case GeoPositionStatus.Disabled:
            if (geoWatcher.Permission == GeoPositionPermission.Denied)
            {
                string[] strings = { "ok" };
                MessageBox.Show("Please turn on geo-location service in the settings tab.");
            }
            else
                if (geoWatcher.Permission == GeoPositionPermission.Granted)
                {
                    string[] strings = { "ok" };
                    MessageBox.Show("Your device doesn't support geo-location service.");
                }
            break;
        case GeoPositionStatus.Ready:
            InitialGeoCoordinate = geoWatcher.Position;
            break;
    }
}

记录下在冒险开始时您在世界上的位置……

位置变化

void geoWatcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
    if (currentState == GeoPositionStatus.Ready)
    {
        CurrentGeoCoordinate = e.Position;
        PositionChangeZ =
           (CurrentGeoCoordinate.Location.Latitude - InitialGeoCoordinate.Location.Latitude)
           * MeterInLatitude;
        PositionChangeX =
           (InitialGeoCoordinate.Location.Longitude - CurrentGeoCoordinate.Location.Longitude)
           * MeterInLongitude;
    }
}

每次 PositionChanged 事件触发时,我们都会估算您在经度(X)和纬度(Z)位置上移动了多少米。海拔(Y)位置目前尚未考虑在内。尽管“Location”有一个“Altitude”属性,但目前您的硬件几乎无法利用它。

关注点

常量

GeoCoordinate GeoCoordinate1 = new  GeoCoordinate(59.949702262878418, 10.763088226318359);
double meterInLatitude = GeoCoordinate1.GetDistanceTo(
                         new  GeoCoordinate(60.949702262878418, 10.763088226318359));
// 1 Latitude degree = 111290.91975341858 meter
double meterInLongitude = GeoCoordinate1.GetDistanceTo(
                          new  GeoCoordinate(59.949702262878418, 11.763088226318359));
// 1 Longitude degree = 55729.5173743959 meter

我通过使用“GeoCoordinate”的“GetDistanceTo”函数来估算了常数“MeterInLongitude”和“MeterInLatitude”的值。只需检查在同一纬度上经度相差 1 的两个 GeoCoordintes 之间的距离,反之亦然。

动画

通过在估算 teapotWorld 时使用 GameTimerEventArgs 的 Totaltime 乘以某个因子来更新 CreateFromYawPitchRoll 的 Yaw(绕 Y 轴)部分,可以让您的茶壶旋转起来并不难。事实上,一项有趣的挑战可能是实时动画日本“子弹”列车新干线的模型,当它从东京移动到长崎时。(开玩笑的)。

世界中的固定位置

如果您有一个模型,比如埃菲尔铁塔,即使您本人在乌拉圭,您也总是希望它位于巴黎,那么您应该计算自己的特定“EiffelTowerPositionChangeX”和“EiffelTowerPositionChangeZ”。这基于铁塔的实际地理坐标(而不是使用“InitialGeoCoordinate”),并在您的 EiffelTowerWorld 中使用这些新变量。当然,如果您是法国人并且住在巴黎,您就不会让您的小模型遮挡住这个“壮丽”地标的真实视野。

警告

当您在户外(GeoCoordinateWatcher 在开阔地带效果最好)寻找飞行茶壶时,请注意周围的交通。不是所有东西都是“增强”的,您知道的。

而且四处奔跑,挥舞着手机,试图看到那些并不真正存在的东西,可能会让您的同事认为您有点疯……

 

非常欢迎关于如何改进功能的评论……

祝您好运,玩得开心,用飞行的茶壶装饰世界……


历史

创建于 2012 年 8 月
附加图片和 zip 文件下载……




© . All rights reserved.