最近做的几个WPF 3D特性的Demonstration

  下周轮到我给组里进行Knowledge Sharing,大家可能已经发现了我准备介绍WPF 3D(毕竟这两篇日志都是和WPF 3D有关的嘛)。

  最近几天在工作之余做了几个介绍WPF 3D各种特性的示例小程序(包括不同类型的摄像机、光源、材质等,较高级的四元数、矩阵摄像机、放射材质等没有介绍,大家可以自己试试)。这里放上来与大家分享一下。示例包括下图所示的几个部分(其中最后一个不演示)。


  一、简单的三维物体

  左图是由两个三角形拼接而成的正方形。三角形的正面材质为蓝色,反面为红色。通过定义不同顺序的TriangleIndices,可以定义三角形正面的朝向。如下XAML代码所示(注意对应关系和顺序):

<MeshGeometry3D Positions="0 0 0,5 0 0,0 5 0,0 5 0,5 0 0,5 5 0"
                                  TriangleIndices="0 1 2 4 3 5" />

  在一本书上学到,建议刚开始的时候把所有物体的背面全部设置成红色,这样可以方便地检测TriangleIndices是否设置正确。确认无误后把不可见物体的背面材质移除,以提高性能。

  右图则是一个入门专用的三维图形——立方体。按顺序把各面的三角形拼接好就能做成。虽然立方体只有8个顶点,但Positions属性一共有36个顶点,至于为啥待会介绍。


  二、共用顶点 vs 不共用顶点

  左图是示例一中的立方体更改颜色后做成。右图则是共用三角形顶点绘制成的立方体效果。两种方式的定义如下:

  可以看出,左图明显棱角分明,而共用顶点的立方体则显得很模糊。原因是:WPF根据光照的方向和顶点法向量的方向计算各顶点的光照强度。共用顶点时,由于一个顶点被多个三角形共用,此时WPF会取一个平均值作为法向量。共用顶点时,各三角形面的光照强度一致。用此种方法可以构造出颜色柔和的曲面。


  三、画刷和材质

  上图是一个立方体的三个面。顶面是一个播放视频的窗体,左侧是一个CheckBox空间,右侧是一张静态图片。三个面都是使用的DiffuseMaterial材质,光照采用AmbientLight。对DiffuseMaterial材质使用不同类型的画刷即可得到不同的内容(各画刷的性能有很大的区别,请参阅MSDN)。可以看出在WPF世界播放视频是一件很简单的时,画刷定义如下:

<VisualBrush>
         <VisualBrush.Visual>
                <MediaElement Source="C:Users\Public\Videos\Sample Videos\Wildlife.wmv" IsMuted="True" />
         </VisualBrush.Visual>
</VisualBrush>

  下图的绿色部分是DiffuseMaterial材质,该材质模拟漫反射物体的表面,例如木头,无能从哪个角度看光照强度和颜色都差不多。蓝色部分是定义了DiffuseMaterial材质并额外附加了SpecularMaterial材质。后者模拟反射光照强烈的物体表面,例如玻璃、瓷砖地板等。注意SpecularMaterial材质不能单独使用,否则看不到,必须和其他材质一起使用。对同一个物体定义多种材质需要使用MaterialGroup对象。


  四、摄像机和光源示例

  两个图中的立方体大小都是一样的。左图采用了透视投影摄像机,这种摄像机就是人眼看到的效果。越近的物体越大,而且平行线不一定平行(也称灭点投影,试想在一个无尽的马路中间,原本平行的马路边缘的直线会逐渐收缩成一个点)。

  右图是一个平行投影摄像机,它保证平行线一定平行,且与投影平面平行的物体与其投影全等。这种投影方式在数学上用得比较多。

  图中的立方体颜色是随机生成的。左图采用了点光源,可以看到正前方的物体的前面与顶端物体前面的光照明显不一样。这就像使用了电灯。

  右图采用了平行光源,可以看成是户外太阳的光源。可以想象有无数个点光源往同一个方向投射光线(这个方向是摄像机的Direction属性)。光源是从前上方投射到后下方的,可以看到每个立方体的前面和顶面都被照亮,而且同层立方体的同一个面光照强度一致。


  五、动画

  左图是一个不段变换颜色的立方体,右侧是一个旋转的立方体。这主要使用了WPF的动画功能,利用StoryBoard对象播放动画(可以创建类似于Flash的关键帧动画)。通过各类Animation实现各种动画效果。如左图是ColorAnimation,右图是DoubleAnimation。

  实现立方体旋转时,先要给三维物体(GeometryModel对象)设置一个Transform属性,告诉WPF按照如何旋转:

                                <GeometryModel3D.Transform>

                                    <RotateTransform3D CenterX="0" CenterY="0" CenterZ="0">

                                        <RotateTransform3D.Rotation>

                                            <AxisAngleRotation3D x:Name="rot_box" Axis="1 1 3" Angle="1" />

                                        </RotateTransform3D.Rotation>

                                    </RotateTransform3D>

                                </GeometryModel3D.Transform>

  之后在按钮的单击时间(或其他任何时间)的Triggers里播放StoryBoard,代码如下:

                <Button.Triggers>

                    <EventTrigger RoutedEvent="Button.Click">

                        <BeginStoryboard>

                            <Storyboard>

                                <DoubleAnimation From="0" To="360"

                                                 Storyboard.TargetName="rot_box"

                                                 Storyboard.TargetProperty="Angle"

                                                 RepeatBehavior="Forever"

                                                 Duration="0:0:5"

                                                 AutoReverse="False" />

                            </Storyboard>

                        </BeginStoryboard>

                    </EventTrigger>

                </Button.Triggers>

  六、圆

  WPF 3D是一个三角形的世界!所以圆也是由三角形拼接而成。从左至右从上至下的4个拼接而成的圆分别使用8个、16个、64个和256个三角形组成。可以看到使用的三角形数量越多,图形越逼真。但64个和256个三角形组成的圆以无太大的区别,要在保证图形质量的尽量减少三角形的数量。

  要画圆就不能简单地使用XAML脚本写了,这时要用到后台代码。画圆,以及下面示例的球体、圆柱体都要将物体进行三角形剖分,使用参数方程生成各离散点的坐标,然后进行组合。


  七、球体和圆柱体

  左图是绘制出的两个球体,较小的球体使用了SpecularMaterial材质,故反光效果很明显。右图示例了在球体上贴图。

  同样的道理,使用不同的画刷同样可以在球体上贴视频,如下图右侧所示。

  右侧的图示例的是除了在两个球体上播放视频外,“哑铃”还在不断旋转,过一会就变成下面这样了。

  有关如何生成球体和圆柱体,并如何设置其TextureCoordinate属性,你可以参考我之前写过的这篇文章

  好了,WPF 3D的基本介绍到这,我要准备下周讲了。现在已经将近下班一个小时了,我也早该撤了。

✏️ 有任何想法?欢迎发邮件告诉老夫:daozhihun@outlook.com