引言
现在物联网概念这么火,如果监控的信息能够实时在手机的客服端中以3D形式展示给我们,那种体验大家可以发挥自己的想象。
那生活中我们还有很多地方用到这些,如上图所示的Kinect 在医疗上的应用,当然还有体感游戏等等。
3D 用来增加视觉效果,给人以更加直观,真实的感觉。
3D如此美妙,那我们在WPF中又该从何处入手开启我们的3D编程旅程?
WPF中3D开发技术的基础知识应该有以下几点:
- 3D开发基础知识
- WPF中3D开发的基础元素(Elements)
- WPF中3D变换和动画
- 常用辅助类
3D开发基础知识
坐标系 Coodinate System
WPF中二维图形的坐标系将原点定位在呈现区域(通常是屏幕)的左上角。 在二维系统中,x 轴上的正值朝右,y 轴上的正值朝下。而在三维坐标系中,原点位于呈现区域的中心,x 轴上的正值朝右,但是 y 轴上的正值朝上,z 轴上的正值从原点向外朝向观察者。传统的二维和三维坐标系表示形式如下图
由这些轴定义的空间是三维对象在 WPF 中的固定参考框架。
当您在该空间中生成模型并创建光源和照相机以查看这些模型时,一定要在向每个模型应用变换时,将固定参考框架或“全局空间”与您为该模型创建的局部参考框架区分开。
另请记住,根据光源和照相机设置,全局空间中的对象可能会看上去完全不同或者根本不可见,但是照相机的位置不会改变对象在全局空间中的位置。
3D的世界都是三角形的王国
如下图:
在3D的世界里所有的东西都是用一些列的“三角形”来描述的。那你一定会问为什么是“三角形”?
原因是三角形是用来描述一个平面的最细微的几何体,渲染引擎能够依据每个三角形的材质以及场景中的灯光角度来计算它的颜色。
其实就是三点确定一个平面,在一个平面上做计算最简单,考虑的因素最少。如果用三维空间中大于三个点来做渲染基本单位,那么如果这些点不在同一个平面上的话,渲染计算是相当复杂的。
3D 对象的表面叫做网格(Mesh),一个网格是由许多3D 点来定义的,这些点叫做顶点(vertices)。这些顶点通过缠绕模式(winding pattern)连接在一起组成一个一个的三角形(facet)(如下图箭头所示)。
三角形(facet)又分为“前”和“后”两面,能看到的部分为前面,看不到的部分为后面。
那怎么判定是前面还是后面?
如果三角形的三个点顺时针方向组成的面那么这个面就是前面。如下图
按照0,1,2的顺序三个点组成了的这个面是上面我们可以看到
目前主流(Direct3D and/or OpenGL)都会把三角形分为两个面(前面和后面)。
为帮助大家记忆(facet)“前面”的三维坐标,大拇指是Z+的方向正对着我们(及前面图示中Up方向),食指是y+方向,而中指是Y+方向。(+表示正数的方向)
WPF 3D的关键元素(Elements)
3D 画布
要画画总的有个画布,WPF中呈现3D也需要一个类似功能的东西。Viewport3D(投影3D场景的平面)是WPF中的3D画布,类于2D中的Canvas。其实WPF中也有一个名字开起来类似的东东Viewbox ,不过和3D没啥关系,它处理的都是2D的。
<Viewport3D> Children… </Viewport3D>
该图形系统将 Viewport3D 视为一个像 WPF 中的许多其他元素一样的二维可视化元素。 Viewport3D充当三维场景中的窗口(即视区)。 更准确地说,它是三维场景所投影到的图面。
相机
处理二维对象的开发人员习惯于将绘图基元置于二维屏幕上。 当您创建三维场景时,一定要记住您实际上是要创建三维对象的二维表示形式。 由于三维场景的外观会因观察者的观察位置不同而异,因此您必须指定观察位置。而观察位置就是由相机(Camera 类)来为三维场景指定的。
另一种理解三维场景在二维图上的描述方法就是,将3D场景投影到一个2D平面的表面。如下图:
从坐标系的角度来看下我们的ProjectionCamera(透视相机)和3D模型的位置,以及2D 投影屏幕的位置关系:
更详细的图解如下:
ProjectionCamera 的 NearPlaneDistance 和 FarPlaneDistance 属性限制照相机的投影范围。由于照相机可以位于场景中的任何位置,因此照相机实际上可能会位于模型内部或者紧靠模型,这使正确区分对象变得很困难。 通过 NearPlaneDistance,可以指定一个距离照相机的最小距离,超过该距离后即不绘制对象。 相反,使用 FarPlaneDistance,可以指定一个距离照相机的距离(即,在超过该距离后将不绘制对象),从而确保因距离太远而无法识别的对象将不包括在场景中。
对比WPF中两种相机
- PerspectiveCamera 可以指定不同的投影及其属性以更改观察者查看三维模型的方式。
- OrthographicCamera 指定三维模型到二维可视化图面上的正投影与其他照相机一样,它指定位置、观察方向和“向上”方向。 但是,与 PerspectiveCamera 不同的是,OrthographicCamera 描述了不包括透视收缩的投影。或者说OrthographicCamera 描述了一个侧面平行的取景框,而不是侧面汇集在场景中一点的取景框。
下图演示使用PerspectiveCamera 和 OrthographicCamera 查看同一模型时的不同效果。
灯光
和现实生活中一样,如果没有光我们将什么也看不到。因此我们需要在我们的场景中至少放置一盏灯来照亮我们场景中的模型。
WPF中支持不同类型的光源,如下:
AmbientLight(环境光)
它所提供的环境光会照亮所有的对象,而不考虑对象的位置或方向。DirectionalLight(平行光)
像远处的光源那样照亮(如太阳光)。将方向光的 Direction 指定为 Vector3D,但是没有为方向光指定位置。PointLight(点光源)
像近处的光源那样照亮。 PointLight 具有一个位置并从该位置投射光。 场景中的对象是根据对象相对于光源的位置和距离而被照亮的。 PointLightBase 公开 Range 属性,该属性确定一个距离,超过该距离后模型将无法由光源照亮。 PointLight 还公开了多个衰减属性,这些属性确定光源的亮度如何随距离的增加而减小。 您可以为光源的衰减指定恒定、线性或二次内插算法。SpotLight(聚光灯)
从 PointLight 继承。 Spotlight 的照亮方式与 PointLight 类似,但是它既具有位置又具有方向。 它们在 InnerConeAngle 和 OuterConeAngle 属性所设置的锥形区域(以度为单位指定)中投射光。
下图展示了各种光源的情况:
光源是 Model3D 对象,因此您可以转换光源对象并对光源属性(包括位置、颜色、方向和范围)进行动画处理。
组合灯光的效果
- Ambient color : Red
- Difusse color : Red
3D模型
说了半天啦,怎么主角还没出现了 ??
对,所有的一切都服务于我们的3D Model。
Model3D 是三维对象的抽象基类。若要生成三维场景,需要一些要查看的对象,而且构成场景图的对象必须派生自 Model3D。 目前,WPF 支持用 GeometryModel3D 对几何形状进行建模。 此模型的 Geometry 属性采用网格基元。
要生成模型,首先生成一个基元或网格。 三维基元是一系列构成单个三维实体的顶点。 大多数三维系统都提供在最简单的闭合图(由三个顶点定义的三角形)上建模的基元。 由于三角形的三个点在一个平面上,因此您可以继续添加三角形,以便对网格这样较为复杂的形状建模。
WPF 三维系统目前提供 MeshGeometry3D 类,使用该类可以指定任何几何形状;它目前不支持预定义的三维基元(如球体和立方体)。 首先通过将三角形顶点的列表指定为它的Positions 属性来创建 MeshGeometry3D。 每个顶点都指定为 Point3D。 (在可扩展应用程序标记语言 (XAML) 中,将该属性指定为三个一组的数字列表,每组中的三个数字表示每个顶点的坐标)。根据网格的几何形状,网格可能会由多个三角形组成,其中的一些三角形共用相同的角(顶点)。 若要正确地绘制网格,WPF 需要有关哪些顶点由哪些三角形共用的信息。 可以通过指定具有 TriangleIndices 属性的三角形索引列表来提供此信息。 此列表指定在 Positions 列表中指定的点将按哪种顺序确定三角形。
材质(Material )
我们生活在多彩的世界中,也不能让我们的3D模型如此单调,这时我们就用到了材质。
在二维中,可以使用 Brush 类来向屏幕中的区域应用颜色、图案、渐变或其他可视化内容。 但是,三维对象的外观是照明模型的功能,而不只是应用于它们的颜色或图案。 实际对象的图面质量不同,它们反射光的方式也会有所不同:光亮的图面与粗糙或不光滑的图面看上去不同,某些对象似乎可以吸收光,而某些对象似乎能够发光。 您可以向三维对象应用与应用于二维对象的完全相同的画笔,但是您不能直接应用它们。
Material 的具体子类用来确定模型图面的某些外观特征,每个子类还提供一个可以向其传递 SolidColorBrush、TileBrush 或 VisualBrush 的 Brush 属性。
- DiffuseMaterial 使用 DiffuseMaterial 与直接针对二维模型使用画笔非常相似;模型表面不反射光,就好像是自发光一样。使用 DiffuseMaterial 与直接针对二维模型使用画笔非常相似;模型表面不反射光,就好像是自发光一样
- SpecularMaterial 可以通过为 SpecularPower 属性指定一个值来设置系统将为纹理的反射特质(或“发光”)建议的度数。
- EmissiveMaterial 可以指定将应用纹理,就好像模型所发出的光与画笔的颜色相同。这不会使模型成为光源;但是,它参与阴影设置的方式将不同于用 DiffuseMaterial 或 SpecularMaterial 设置纹理时的情况。
为进一步提高性能,可以从场景中精选 GeometryModel3D 的背面(由于它们相对于照相机位于模型的背面,因此您将看不到这些面)。若要指定要应用于模型(如飞机)背面的Material,请设置模型的 BackMaterial 属性。
为了实现某些图面质量(如发光或发射效果),您可能希望向模型连续应用几个不同的画笔。 可以使用 MaterialGroup 类来应用和重用多个 Material。 MaterialGroup 的子级在多个呈现过程中按照从头到尾的顺序来应用。
WPF中3D变换和动画
变换
当您创建模型时,它们在场景中具有固定的位置。为了在场景中移动、旋转这些模型或者更改这些模型的大小而更改用来定义模型本身的顶点是不切实际的。 相反,您可以像在二维模型一样应用转换。
每个模型对象都有一个可用来对模型进行移动、重定向或调整大小的 Transform 属性。 当您应用转换时,实际上是按照由Transform 属性指定的向量或值来偏移模型的所有点。
也就是说变换了定义模型的坐标系(“模型空间”)而模型所在的整个场景的坐标系(“全局空间”)却没有改变,从而实现了3D模型的变换。
动画
WPF 三维实现与二维图形参与同一个计时和动画系统。也就是说,要对三维场景进行动画处理,也就是对其模型的属性进行动画处理。 可以直接对基元的属性进行动画处理,但是通常很容易更改模型位置或外观的变换进行动画处理。 由于可以向 Model3DGroup 对象及其各个模型应用转换,因此可以向 Model3DGroup 中某个对象应用一组动画,也可以向这一组子对象应用一组动画。 还可以通过对场景的照明属性进行动画处理来实现各种可视化效果。 最后,您可以选择通过对照相机的位置或视野进行动画处理来对投影本身进行动画处理。
要对 WPF 中的对象进行动画处理,可以创建时间线、定义动画(实际上是随着时间的推移而更改某个属性值)并指定要向其应用动画的属性。 由于三维场景中的所有对象都是Viewport3D 的子节点,因此要应用于场景的任何动画所面向的属性都是 Viewport3D 的属性。
常用辅助类
实例
<UserControl x:Class="HostingWpfUserControlInWf.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <Grid> <!-- Place a Label control at the top of the view. --> <Label HorizontalAlignment="Center" TextBlock.TextAlignment="Center" FontSize="20" Foreground="Red" Content="Model: Cone"/> <!-- Viewport3D is the rendering surface. --> <Viewport3D Name="myViewport" > <!-- Add a camera. --> <Viewport3D.Camera> <PerspectiveCamera FarPlaneDistance="20" LookDirection="0,0,1" UpDirection="0,1,0" NearPlaneDistance="1" Position="0,0,-3" FieldOfView="45" /> </Viewport3D.Camera> <!-- Add models. --> <Viewport3D.Children> <ModelVisual3D> <ModelVisual3D.Content> <Model3DGroup > <Model3DGroup.Children> <!-- Lights, MeshGeometry3D and DiffuseMaterial objects are added to the ModelVisual3D. --> <DirectionalLight Color="#FFFFFFFF" Direction="3,-4,5" /> <!-- Define a red cone. --> <GeometryModel3D> <GeometryModel3D.Geometry> <MeshGeometry3D Positions="0.293893 -0.5 0.404509 0.475528 -0.5 0.154509 0 0.5 0 0.475528 -0.5 0.154509 0 0.5 0 0 0.5 0 0.475528 -0.5 0.154509 0.475528 -0.5 -0.154509 0 0.5 0 0.475528 -0.5 -0.154509 0 0.5 0 0 0.5 0 0.475528 -0.5 -0.154509 0.293893 -0.5 -0.404509 0 0.5 0 0.293893 -0.5 -0.404509 0 0.5 0 0 0.5 0 0.293893 -0.5 -0.404509 0 -0.5 -0.5 0 0.5 0 0 -0.5 -0.5 0 0.5 0 0 0.5 0 0 -0.5 -0.5 -0.293893 -0.5 -0.404509 0 0.5 0 -0.293893 -0.5 -0.404509 0 0.5 0 0 0.5 0 -0.293893 -0.5 -0.404509 -0.475528 -0.5 -0.154509 0 0.5 0 -0.475528 -0.5 -0.154509 0 0.5 0 0 0.5 0 -0.475528 -0.5 -0.154509 -0.475528 -0.5 0.154509 0 0.5 0 -0.475528 -0.5 0.154509 0 0.5 0 0 0.5 0 -0.475528 -0.5 0.154509 -0.293892 -0.5 0.404509 0 0.5 0 -0.293892 -0.5 0.404509 0 0.5 0 0 0.5 0 -0.293892 -0.5 0.404509 0 -0.5 0.5 0 0.5 0 0 -0.5 0.5 0 0.5 0 0 0.5 0 0 -0.5 0.5 0.293893 -0.5 0.404509 0 0.5 0 0.293893 -0.5 0.404509 0 0.5 0 0 0.5 0 " Normals="0.7236065,0.4472139,0.5257313 0.2763934,0.4472138,0.8506507 0.5308242,0.4294462,0.7306172 0.2763934,0.4472138,0.8506507 0,0.4294458,0.9030925 0.5308242,0.4294462,0.7306172 0.2763934,0.4472138,0.8506507 -0.2763934,0.4472138,0.8506507 0,0.4294458,0.9030925 -0.2763934,0.4472138,0.8506507 -0.5308242,0.4294462,0.7306172 0,0.4294458,0.9030925 -0.2763934,0.4472138,0.8506507 -0.7236065,0.4472139,0.5257313 -0.5308242,0.4294462,0.7306172 -0.7236065,0.4472139,0.5257313 -0.858892,0.429446,0.279071 -0.5308242,0.4294462,0.7306172 -0.7236065,0.4472139,0.5257313 -0.8944269,0.4472139,0 -0.858892,0.429446,0.279071 -0.8944269,0.4472139,0 -0.858892,0.429446,-0.279071 -0.858892,0.429446,0.279071 -0.8944269,0.4472139,0 -0.7236065,0.4472139,-0.5257313 -0.858892,0.429446,-0.279071 -0.7236065,0.4472139,-0.5257313 -0.5308242,0.4294462,-0.7306172 -0.858892,0.429446,-0.279071 -0.7236065,0.4472139,-0.5257313 -0.2763934,0.4472138,-0.8506507 -0.5308242,0.4294462,-0.7306172 -0.2763934,0.4472138,-0.8506507 0,0.4294458,-0.9030925 -0.5308242,0.4294462,-0.7306172 -0.2763934,0.4472138,-0.8506507 0.2763934,0.4472138,-0.8506507 0,0.4294458,-0.9030925 0.2763934,0.4472138,-0.8506507 0.5308249,0.4294459,-0.7306169 0,0.4294458,-0.9030925 0.2763934,0.4472138,-0.8506507 0.7236068,0.4472141,-0.5257306 0.5308249,0.4294459,-0.7306169 0.7236068,0.4472141,-0.5257306 0.8588922,0.4294461,-0.27907 0.5308249,0.4294459,-0.7306169 0.7236068,0.4472141,-0.5257306 0.8944269,0.4472139,0 0.8588922,0.4294461,-0.27907 0.8944269,0.4472139,0 0.858892,0.429446,0.279071 0.8588922,0.4294461,-0.27907 0.8944269,0.4472139,0 0.7236065,0.4472139,0.5257313 0.858892,0.429446,0.279071 0.7236065,0.4472139,0.5257313 0.5308242,0.4294462,0.7306172 0.858892,0.429446,0.279071 " TriangleIndices="0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 " /> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <DiffuseMaterial> <DiffuseMaterial.Brush> <SolidColorBrush Color="Red" Opacity="1.0"/> </DiffuseMaterial.Brush> </DiffuseMaterial> </GeometryModel3D.Material> </GeometryModel3D> </Model3DGroup.Children> </Model3DGroup> </ModelVisual3D.Content> </ModelVisual3D> </Viewport3D.Children> </Viewport3D> </Grid> </UserControl>
Make a 3D cube with pictures on its sides with XAML and C# 源码
总结
3D开发首先要把三维坐标系搞清楚,才能构建出想要的3D Model,才能把灯光合理的照在3D模型上(环境光除外)得到不同的灯光效果,才能指定合适的相机(Camera)位置从而看到自己想要看到3D投影。
3D开发中所有的几何体最终都是由一系列的三角形来组成的。所以在您吧三维坐标系高清的前提下,您还需要考虑把您的3D模型分解成三角形。听来就很繁琐,幸运的是现在有一些辅助类帮助大家来处理这些三角形的分解,而您所做的就是提供关键点的坐标系。
由于WPF 3D 是基于Direct 3D,和WPF 2D一样直接利用显卡渲染的,并且默认开启了全景反锯齿(有个条件,需要你的显卡支持 兼容WDDM-Compliant)。
3D开发和2D开发复杂了很多,考虑的东西多了不少。但为了那更真实的,更炫酷的效果,这一切都值得您去学习。
祝各位在3D开发的旅途,一路愉快!
参考
WPF 3D Article, Tutorial with Chart Graphics C# Code
Getting started with 3D in WPF
Rendering Transparent 3D Surfaces in WPF with C#