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


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

    效果显示

    下面我们看截图:

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

    实现方式

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

    我们先看一个坐标图:

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

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

    View Code
    <!-- 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 那么我们就可以看到界面显示如下:

    图像侧着显示了。

    再来说说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 )来表示(一个为逆时针,一个为顺时针),得到的结果如下:

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

    最后来说说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中也是如此:

    View Code
    <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用控制视角查看的方向。

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

    View Code
     <!-- 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时,回到原位,这就相当于摄像机的位置和视角改变一样。

    具体代码如下:

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

    好了,希望对你有用。

    源码下载:

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

    点击下载源码

    2013 10 17更新下最新文件:

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

  • 相关阅读:
    重写与重载的区别
    UDP模式与TCP模式的区别
    什么是GC?为什么会有GC?
    centos 7-8 安装 ms sql server 2019
    Phaser3 游戏开发入门——自定义构建Phaser库
    Visual Studio 下C#编译器在解析属性名时如果增加一个get_[您的另一个已经包含在类中属性名]的属性会报错,微软大哥这是什么鬼?
    Visual Studio 2015 Update 3 ISO
    react项目中引用amap
    js 截取网址中的某一段字符串
    解决react下找不到原生高德地图AMap类的问题
  • 原文地址:https://www.cnblogs.com/scy251147/p/2638924.html
Copyright © 2020-2023  润新知