WPF换肤之八:创建3D浏览效果

时间:2021-02-03 20:37:17

原文:WPF换肤之八:创建3D浏览效果

上节中,我们展示了WPF中的异步以及界面线程交互的方式,使得应用程序的显示更加的流畅。这节我们主要讲解如何设计一个具有3D浏览效果的天气信息浏览器。

效果显示

下面我们看截图:

WPF换肤之八:创建3D浏览效果

WPF换肤之八:创建3D浏览效果

是不是能够感受到一种与众不同的感觉。如果你能够感受到它的与众不同,这也是我本节所要达到的目标。

实现方式

上面的只是一个简单的3D图形,它的产生需要依赖于WPF中的MeshGeometry3D对象,这个对象按照微软官方的解释就是用于生成3D形状的三角形基元,它有三个比较重要的属性:Positions(这个是必须的),TextureCoordinates以及TriangleIndices。其中Positions是指定当前的界面坐标,也就是由这些坐标形成什么样的界面形状;TextureCoordinates,官方说法是材质被映射到构成网格的顶点,通俗的说来,就是改变图像的显示顺序的,比如是正向显示,横向显示等等,也就是能够使界面倒个个儿;TriangleIndices则代表当前显示图形的正反面。

我们先看一个坐标图:

WPF换肤之八:创建3D浏览效果

在这个坐标图中,P0就是(-1,1,0)坐标,P1就是(-1,-1,0)坐标,P2就是(1,-1,0)坐标,P3就是(1,1,0)坐标,这四个象限按照逆时针方向来。那么在接下来的讲解中,这些坐标将会有辅助作用。

先对应这坐标说说TextureCoordinates, 在XAML代码中,可以看到其设置如下:

<!-- front side-->
<Viewport2DVisual3D Material="{StaticResource CubeSideMaterial }">
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="0,1,0 0,0,0 1,0,0 1,1,0"
TextureCoordinates="0,0 0,1 1,1 1,0"
TriangleIndices="0 1 2 0 2 3"/>
</Viewport2DVisual3D.Geometry>
....
</Viewport2DVisual3D> <!-- left side -->
<Viewport2DVisual3D Material="{StaticResource CubeSideMaterial}">
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="1,1,0 1,0,0 1,0,-1 1,1,-1"
TextureCoordinates="0,0 0,1 1,1 1,0"
TriangleIndices="0 1 2 0 2 3"/>
</Viewport2DVisual3D.Geometry>
....
</Viewport2DVisual3D> <!--Back side-->
<Viewport2DVisual3D Material="{StaticResource CubeSideMaterial}">
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="1,1,-1 1,0,-1 0,0,-1 0,1,-1"
TextureCoordinates="0,0 0,1 1,1 1,0"
TriangleIndices="0 1 2 0 2 3"/>
</Viewport2DVisual3D.Geometry>
....
</Viewport2DVisual3D> <!--Right side-->
<Viewport2DVisual3D Material="{StaticResource CubeSideMaterial}">
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="0,1,-1 0,0,-1 0,0,0 0,1,0"
TextureCoordinates="0,0 0,1 1,1 1,0"
TriangleIndices="0 1 2 0 2 3"/>
</Viewport2DVisual3D.Geometry>
....
</Viewport2DVisual3D>

可以看到,在这是个面中,它的值都是0,0 0,1 1,1 1,0, 这组值代表界面正向显示(默认情况下),其实,按照映射方式来的话(按照上图的P0,P1,P2,P3),也就是0,1被映射到了P0;0,0被映射到了P1;1,0被映射到了P2;1,1被映射到了P3。假如稍微改变下,为0,1 1,1 1,0 0,0 那么我们就可以看到界面显示如下:

WPF换肤之八:创建3D浏览效果

图像侧着显示了。

再来说说TriangleIndices,这个主要用来显示图形的正反面的,如果按照逆时针,即P0,P1,P2,P3的方向来的话,则可以显示正常的图形,也就是假如它的值为(0 1 2 0 2 3 ) 或者( 0 1 2  2 3 0 ) 或者 (1 2 3  3 0 1 )均可以显示出正面的图形来,需要注意的是,由于计算机的图形都是用三角形表示的,所以这里0 1 2为一组代表一个三角形,2 3 0 为一组,代表另一个三角形,这两个三角形就组成了一个矩形。

如果我们让它显示一半正面,一半反面,则可以使用(0 1 2  0 3 2 )来表示(一个为逆时针,一个为顺时针),得到的结果如下:

WPF换肤之八:创建3D浏览效果

由于其背面透明且无任何界面元素,所以看不到。

最后来说说Positions,它用来表示显示的图形,由于我们这里是一个正方体,所以每个面应该有4个点,4个点按照逆时针方向(P0 - > P1 - > P2 -> P3)方向排列即可。 如果一个正方体,它的正面的四个点按照逆序肯定是(0,1,0 0,0,0 1,0,0 1,1,0),依此类推,那么它的左边侧面的点肯定是(1,1,0 1,0,0 1,0,-1 1,1,-1)。 按照这样的点推下去,就能够得到正确的Positions的值。需要说明的是,复杂的图形,Positions的值是非常复杂的,那是推理不来的。

下面说说如何切换四个面:

首先,3D图形中,我们都需要有视点,这里称作(Camera),然后还得需要有光源(Light),这样图像才不至于黑乎乎一篇,在WPF中也是如此:

<Viewport3D x:Name="view" ClipToBounds="True" RenderOptions.EdgeMode="Aliased">
<!--Camera-->
<Viewport3D.Camera>
<PerspectiveCamera x:Name="camera" FieldOfView="59" Position="0.5,0.5,2" LookDirection="0,0,-1">
<PerspectiveCamera.Transform>
<RotateTransform3D x:Name="rot" CenterY="0.5" CenterX="0.5" CenterZ="-0.5">
<RotateTransform3D.Rotation>
<!-- rotation -->
<AxisAngleRotation3D x:Name="camRotation" Axis="0,1,0" Angle="0"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
</PerspectiveCamera.Transform>
</PerspectiveCamera>
</Viewport3D.Camera>
<!--Light-->
<ModelVisual3D>
<ModelVisual3D.Content>
<AmbientLight Color="White" />
</ModelVisual3D.Content>
</ModelVisual3D>

其中FieldOfView用于切换视角的远近,而Position用于控制物体的方位,LookDirection用控制视角查看的方向。

接下来,我们就可以通过上面的讲解设计出具体的界面代码了:

 <!-- front side-->
<Viewport2DVisual3D Material="{StaticResource CubeSideMaterial }">
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="0,1,0 0,0,0 1,0,0 1,1,0"
TextureCoordinates="0,0 0,1 1,1 1,0"
TriangleIndices="0 1 2 0 2 3"/>
</Viewport2DVisual3D.Geometry>
<Border BorderThickness="1" x:Name="FrontSide" BorderBrush="White" CornerRadius="4" PreviewMouseDown="FrontSide_PreviewMouseDown" >
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" />
</LinearGradientBrush>
</Border.Background>
<StackPanel Height="450" Width="450" OpacityMask="White" >
<Button PreviewMouseLeftButtonDown="Button_MouseLeftButtonDown" Style="{StaticResource CloseRadialButton}" HorizontalAlignment="Right" Margin="0,2,2,0"></Button>
<Border Margin="15,0,15,15" BorderThickness="1" CornerRadius="8" Height="30" VerticalAlignment="Top" PreviewMouseDown="Border_PreviewMouseDown">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF333333" Offset=".1"/>
<GradientStop Color="Red" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<TextBlock Foreground="White" HorizontalAlignment="Center" FontSize="16" Margin="3" Text="天气信息"/>
</Border>
<Border Margin="15,0,15,0" HorizontalAlignment="Left" BorderThickness="0,0,0,1" BorderBrush="White" >
<TextBlock Foreground="White" Margin="0,3,0,0" >
直辖市:上海
</TextBlock>
</Border>
<Border Margin="15,0,15,0" HorizontalAlignment="Left" BorderThickness="0,0,0,1" BorderBrush="White" >
<TextBlock Foreground="White" Margin="0,3,0,0">
2012-8-10 23:58:13 最低气温:27℃/最高气温:33℃
</TextBlock>
</Border>
<Border Margin="15,0,15,0" HorizontalAlignment="Left" BorderThickness="0,0,0,1" BorderBrush="White" >
<TextBlock Foreground="White" Margin="0,3,0,0" TextWrapping="Wrap" TextAlignment="Left">
今日天气实况:气温:28℃;风向/风力:北风 1级;湿度:80%;空气质量:良;紫外线强度:中等 穿衣指数:天气炎热,建议着短衫、短裙、短裤、薄型T恤衫、敞领短袖棉衫等清凉夏季服装。
</TextBlock>
</Border>
<Border Margin="15,0,15,0" HorizontalAlignment="Left" BorderThickness="0,0,0,1" BorderBrush="White" >
<TextBlock Foreground="White" Margin="0,3,0,0" TextWrapping="Wrap" TextAlignment="Left">
感冒指数:暂无。 运动指数:有降水,风力较强,较适宜在户内开展低强度运动,若坚持户外运动,请选择避雨防风地点。 洗车指数:不宜洗车,未来24小时内有雨,如果在此期间洗车,雨水和路上的泥水可能会再次弄脏您的爱车。 晾晒指数:有降水,可能会淋湿晾晒的衣物,不太适宜晾晒。请随时注意天气变化。 旅游指数:有阵雨,气温较高,但风较大,能缓解湿热的感觉,还是适宜旅游,您仍可陶醉于大自然的美丽风光中。 路况指数:有降水,路面潮湿,车辆易打滑,请小心驾驶。 舒适度指数:天气较热,虽然有降水,但仍然无法削弱较高气温给人们带来的暑意,这种天气会让您感到不很舒适。
</TextBlock>
</Border>
<Border Margin="15,0,15,0" HorizontalAlignment="Left" BorderThickness="0,0,0,1" BorderBrush="White" >
<TextBlock Foreground="White" Margin="0,3,0,0" TextWrapping="Wrap" TextAlignment="Left">
空气污染指数:气象条件有利于空气污染物稀释、扩散和清除,可在室外正常活动。 紫外线指数:属中等强度紫外线辐射天气,外出时建议涂擦SPF高于15、PA+的防晒护肤品,戴帽子、太阳镜。
</TextBlock>
</Border>
</StackPanel>
</Border>
</Viewport2DVisual3D> <!-- left side -->
<Viewport2DVisual3D Material="{StaticResource CubeSideMaterial}">
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="1,1,0 1,0,0 1,0,-1 1,1,-1"
TextureCoordinates="0,0 0,1 1,1 1,0"
TriangleIndices="0 1 2 0 2 3"/>
</Viewport2DVisual3D.Geometry>
<Border BorderThickness="1" x:Name="LeftSide" BorderBrush="White" CornerRadius="4" PreviewMouseDown="LeftSide_PreviewMouseDown" >
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" />
</LinearGradientBrush>
</Border.Background>
<StackPanel Height="450" Width="450" OpacityMask="White">
<Button Style="{StaticResource CloseRadialButton}" HorizontalAlignment="Right" Margin="0,2,2,0" PreviewMouseLeftButtonDown="Button_MouseLeftButtonDown"></Button>
<Border Margin="15,0,15,15" BorderThickness="1" CornerRadius="8" Height="30" VerticalAlignment="Top">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF333333" Offset=".1"/>
<GradientStop Color="Red" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<TextBlock Foreground="White" HorizontalAlignment="Center" FontSize="16" Margin="3">未来天气信息</TextBlock>
</Border>
<Border Margin="15,0,15,0" HorizontalAlignment="Left" BorderThickness="0,0,0,1" BorderBrush="White" >
<TextBlock Foreground="White" Margin="0,3,0,0" >
8月11日 阵雨转多云 东南风4-5级 27℃/34℃
</TextBlock>
</Border>
<Border Margin="15,0,15,0" HorizontalAlignment="Left" BorderThickness="0,0,0,1" BorderBrush="White" >
<TextBlock Foreground="White" Margin="0,3,0,0">
8月12日 多云 南风3-4级 28℃/34℃
</TextBlock>
</Border>
<Border Margin="15,0,15,0" HorizontalAlignment="Left" BorderThickness="0,0,0,1" BorderBrush="White" >
<TextBlock Foreground="White" Margin="0,3,0,0" TextWrapping="Wrap" TextAlignment="Left">
8月13日 阵雨 南风3-4级
</TextBlock>
</Border>
</StackPanel>
</Border>
</Viewport2DVisual3D> <!--Back side-->
<Viewport2DVisual3D Material="{StaticResource CubeSideMaterial}">
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="1,1,-1 1,0,-1 0,0,-1 0,1,-1"
TextureCoordinates="0,0 0,1 1,1 1,0"
TriangleIndices="0 1 2 0 2 3"/>
</Viewport2DVisual3D.Geometry>
<Border BorderThickness="1" x:Name="BackSide" BorderBrush="White" CornerRadius="4" PreviewMouseDown="BackSide_PreviewMouseDown" >
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" />
</LinearGradientBrush>
</Border.Background>
<StackPanel Height="450" Width="450" OpacityMask="White">
<Button PreviewMouseLeftButtonDown="Button_MouseLeftButtonDown" Style="{StaticResource CloseRadialButton}" HorizontalAlignment="Right" Margin="0,2,2,0"></Button>
<Border Margin="15,0,15,15" BorderThickness="1" CornerRadius="8" Height="30" VerticalAlignment="Top">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF333333" Offset=".1"/>
<GradientStop Color="Red" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<TextBlock Foreground="White" HorizontalAlignment="Center" FontSize="16" Margin="3">城市简介</TextBlock>
</Border>
<Border Margin="15,0,15,0" HorizontalAlignment="Left" BorderThickness="0,0,0,1" BorderBrush="White" >
<TextBlock Foreground="White" Margin="0,3,0,0" TextWrapping="Wrap" TextAlignment="Left" >
上海简称:沪,位置:上海地处长江三角洲前缘,东濒东海,南临杭州湾,西接江苏,浙江两省,北界长江入海,正当我国南北岸线的中部,北纬31°14′,东经121°29′。面积:总面积7823.5平方公里。人口:人口1000多万。上海丰富的人文资源、迷人的城市风貌、繁华的商业街市和欢乐的节庆活动形成了独特的都市景观。游览上海,不仅能体验到大都市中西合壁、商儒交融、八方来风的氛围,而且能感受到这个城市人流熙攘、车水马龙、灯火璀璨的活力。上海在中国现代史上占有着十分重要的地位,她是中国**党的诞生地。许多震动中外的历史事件在这里发生,留下了众多的革命遗迹,处处为您讲述着一个个使人永不忘怀的可歌可泣的故事,成为包含民俗的人文景观和纪念地。在上海,每到秋祭,纷至沓来的人们在这里祭祀先烈、缅怀革命历史,已成为了一种风俗。大上海在中国近代历史中,曾是风起云涌可歌可泣的地方。在这里荟萃多少风云人物,散落在上海各处的不同住宅建筑,由于其主人的非同寻常,蕴含了耐人寻味的历史意义。这里曾留下许多革命先烈的足迹。瞻仰孙中山、宋庆龄、鲁迅等故居,会使您产生抚今追昔的深沉遐思,这里还有无数个达官贵人的住宅,探访一下李鸿章、蒋介石等人的公馆,可以联想起主人那段显赫的发迹史。
</TextBlock>
</Border>
</StackPanel>
</Border>
</Viewport2DVisual3D> <!--Right side-->
<Viewport2DVisual3D Material="{StaticResource CubeSideMaterial}">
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="0,1,-1 0,0,-1 0,0,0 0,1,0"
TextureCoordinates="0,0 0,1 1,1 1,0"
TriangleIndices="0 1 2 0 2 3"/>
</Viewport2DVisual3D.Geometry>
<Border BorderThickness="1" x:Name="RightSide" BorderBrush="White" CornerRadius="4" PreviewMouseDown="RightSide_PreviewMouseDown" >
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" />
</LinearGradientBrush>
</Border.Background>
<StackPanel Height="450" Width="450" OpacityMask="White">
<Button PreviewMouseLeftButtonDown="Button_MouseLeftButtonDown" Style="{StaticResource CloseRadialButton}" HorizontalAlignment="Right" Margin="0,2,2,0"></Button>
<Border Margin="15,0,15,15" BorderThickness="1" CornerRadius="8" Height="30" VerticalAlignment="Top">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF333333" Offset=".1"/>
<GradientStop Color="Red" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<TextBlock Foreground="White" HorizontalAlignment="Center" FontSize="16" Margin="3">图表显示</TextBlock>
</Border>
<Border Margin="15,0,15,0" HorizontalAlignment="Left" BorderThickness="0,0,0,1" BorderBrush="White" >
<TextBlock Foreground="White" Margin="0,3,0,0" TextWrapping="Wrap" TextAlignment="Left" >
这个地方是图表显示温度
</TextBlock>
</Border>
</StackPanel>
</Border>
</Viewport2DVisual3D> </Viewport3D>

那么在后台如何做到点击切换呢?

在后台我们是通过一个DispatcherTimer来控制切换的动态显示,并且通过设置RotateTransform.的Angle来控制四个面的逐一显现的。当Angle为0时,显现的是第一面;为90时,显现的是左侧面;为180时,显现的是背面;为270时,显现的是右侧面;为360时,回到原位,这就相当于摄像机的位置和视角改变一样。

具体代码如下:

 public _3DWeatherWindow(string[] WeatherList)
{
InitializeComponent();
weatherList = WeatherList; if (clock == null) clock = new DispatcherTimer();
clock.Tick += new EventHandler(clock_Tick);
clock.Interval = new TimeSpan(, , , , );
} private string[] weatherList; DispatcherTimer clock = null;
double rotAngle = ; private void clock_Tick(object sender, EventArgs e)
{
camRotation.Angle += ;
if (camRotation.Angle >= rotAngle) clock.Stop();
} private void FrontSide_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
//初始化值
camRotation.Angle = ;
rotAngle = ; clock.Start();
} private void LeftSide_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
rotAngle = ;
clock.Start();
} private void BackSide_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
rotAngle = ;
clock.Start();
} private void RightSide_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
rotAngle = ;
clock.Start();
}

好了,希望对你有用。

源码下载:

参考文章:WPF:MeshGeometry3D   Making 3D Application with WPF

点击下载源码

2013 10 17更新下最新文件:

http://files.cnblogs.com/scy251147/TimeZoneDaemonApp%283D%29.20131017.rar