XAML 中的 3D






3.23/5 (25投票s)
2004年10月20日
13分钟阅读

191825
关于在Mobiform和Longhorn XAML中使用3D标签的指南。
目录
3D基础
本节将解释在XAML中创建3D场景所需概念的基础知识。
坐标系
其他链接: MSDN。
此时,您应该已经熟悉使用2D坐标来放置XAML对象。放置对象非常相似,但需要一个额外的值,并且不是使用整数,所有值都可以是浮点数(使用小数点)。
下面是2D笛卡尔坐标系的表示,可用于定义2D中的任何点。旁边是3D坐标系,倾斜的线Z通常表示深度,或距离视点的距离。
这是可视化两个坐标系中事物的标准方法。在2D计算机图形中,0,0通常代表屏幕的左上角。虽然可能是这样,但在3D中很少是这样,0,0,0在屏幕上的显示完全取决于相机(您的视角)的位置。幸运的是,在XAML中,有一个简单的方法可以像处理任何其他2D控件一样定位整个场景,即ViewPort3D
。
设置场景
设置3D场景需要做的第一件事是定义ViewPort3D
,它将把您的场景定位在画布上。
<ViewPort3D Canvas.Top="50" Canvas.Left="50" Width="300" Height="300">
#Scene definition
</Viewport>
接下来,您绝对必须拥有一个相机。没有相机来表示您的视角(POV),您将看不到太多东西。这将包含在上述场景定义部分的上方代码中。
<ViewPort3D.Camera>
<PerspectiveCamera
Position="-250,250,200"
LookAtPoint="0,0,0"
Up="0,1,0"
FieldOfView="40"
NearPlaneDistance="1"
FarPlaneDistance="500"
/>
</ViewPort3D.Camera>
这需要一点解释,但最容易理解的方式就是把它想象成一只眼睛。前三组数字用于定位和定向眼睛,Position
给出眼睛将要放置的世界位置,接下来是LookAtPoint
,它描述了眼睛正在注视的空间点。仅前两者就描述了很多,但不足以描述完整的方向,Up
可以被认为是头部倾斜的方向。FieldOfView
是以度为单位的视角范围,可以将其想象成从眼睛向外投射一个圆锥,圆锥内的任何东西都可以看到。
NearPlaneDistance
和FarPlaneDistance
一起描述了您的视深范围。如果您测量眼睛到任何对象的距离,并且该距离超出近平面和远平面,您将看不到该对象。近平面和远平面非常重要,因为它们的影响不仅仅在于您是否能看到对象,还会影响DirectX中的深度缓冲区的准确性。因此,请避免将远平面设置为10亿以便看到所有内容。深度缓冲区精度有限,因此如果需要覆盖的空间太大,您将注意到渲染异常。
没有要查看的对象,相机就没有用处。您需要一些模型,但要有模型,首先需要一个模型集合。模型集合包含模型以及灯光。
<ViewPort3D>
#Scene definition
<ViewPort3D.Models>
<Model3DCollection>
#models and Lights go in here
</Model3DCollection>
</ViewPort3D.Models>
</ViewPort3D>
定义灯光
光照充足的场景总是看起来更好。没有灯光,有一个默认的环境光颜色,但您确实想添加一些灯光来营造氛围。有4种基本类型的灯光。
- 环境光
一种无处不在的光(不需要位置),它将平等地影响所有对象和对象的所有侧面。
AmbientLight Color="#404040"/>
- 定向光
另一种无处不在的光,它将影响场景中的所有对象,但会根据其方向照亮对象。面向光的方向的任何物体部分都将被充分照亮,背向方向的将不会被照亮。
<DirectionalLight Color="#C0C0C0" Direction="-0.5,-0.25,-1"/>
- Point
从单个点向所有方向发出光。它需要位置、颜色和衰减值。
*未实现。
- 聚光灯
向目标发射光;需要位置、方向、颜色、内外圆锥大小以及一些衰减值。
*未实现。
创建对象
有几种方法可以将对象添加到场景中。
- 导入X文件
假设您有
.x
文件可用,这可能是最简单的方法。这样做的缺点是文件必须存在并且对查看器可用。<MeshPrimitive3D> <MeshPrimitive3D.Mesh> <Mesh3D XFile3DSource="TorusKnot.x"/> </MeshPrimitive3D.Mesh> </MeshPrimitive3D>
- 在XAML中定义网格
这有点复杂,您必须定义网格点、三角形的索引和点法线。与X文件源不同,这不需要任何外部文件。
<MeshPrimitive3D> <MeshPrimitive3D.Mesh> <Mesh3D Normals="0,-1,0 0,1,0 0,0,-1 1,0,0 0,0,1 -1,0,0 0,-1,0 0,1,0 ... Positions="-5,0,-5 5,0,-5 -5,0,5 5,0,5 -5,10,-5 5,10,-5 ... TriangleIndices="0,3,2 3,0,1 4,7,5 7,4,6 8,9,10 9,8,11 ... /> </MeshPrimitive3D.Mesh> </MeshPrimitive3D>
*幸运的是,互联网上有一个工具可以将X文件转换为XAML定义: MSDN。
- 定义引用网格
类似于XAML定义的网格,此网格是全局定义的,因此可以重用。可重用定义在
Canvas.Resources
中定义,并可用于任何Mesh3D。请注意在Canvas
标签中定义的xmlns:def="Definition"
。<Canvas xmlns="http://schemas.microsoft.com/2003/xaml" xmlns:def="Definition"> <Canvas.Resources> <Mesh3D def:Name="Box" Normals="0,-1,0 0,1,0 0,0,-1 1,0,0 0,0,1 -1,0,0 0,-1,0 0,1,0 0,0,-1 ... Positions="-5,0,-5 5,0,-5 -5,0,5 5,0,5 -5,10,-5 5,10,-5 -5,10,5 5,10,5 ... TriangleIndices="0,3,2 3,0,1 4,7,5 7,4,6 8,9,10 9,8,11 1,7,12 7,1,13... /> </Canvas.Resources>…
稍后在模型集合中使用
<MeshPrimitive3D Mesh="{Box}"/>
定义材质
如果您选择在XAML中定义网格对象,或者链接到一个没有材质信息的X文件,您的模型很可能会显示为黑色。定义纯色画笔很简单,只需要一个颜色。
<MeshPrimitive3D.Material>
<BrushMaterial Brush="green"/>
</MeshPrimitive3D.Material>
这里有一个更完整的示例,动画化纯色画笔的颜色
<MeshPrimitive3D Mesh="{Box}">
<MeshPrimitive3D.Material>
<BrushMaterial>
<BrushMaterial.Brush>
<SolidColorBrush>
<SolidColorBrush.ColorAnimations>
<ColorAnimation
From="Green"
To="Red"
Begin="0.5"
Duration="1"
AutoReverse="True">
</SolidColorBrush.ColorAnimations>
</SolidColorBrush>
</BrushMaterial.Brush>
</BrushMaterial>
</MeshPrimitive3D.Material>
除了纯色,您可能还想为您的对象添加纹理。您可以指定图像文件、大小和不透明度。
<BrushMaterial.Brush>
<ImageBrush
ImageSource="TextureName.jpg"
ViewPort="0 0 1024 1024"
Opacity="1.0"
/>
</BrushMaterial.Brush>
命中测试
3D对象的命中测试很容易实现,目前只支持单击。
…
<MeshPrimitive3D Click="OnClick">
<MeshPrimitive3D.Material>
…
相机控制器
如果您想手动导航3D场景,相机控制器会很有用。缺点是,这不是Avalon支持的标签,在Longhorn中将不起作用。
有两种相机控制器
- 免费
此移动模式是无限的,允许用户在任何地方、任何方向移动。
- 按住左键拖动以改变方向。
- W、S键前后移动。
- A、D键左右移动。
- Q、E键上下移动。
<PerspectiveCamera Position="-250,0,0" LookAtPoint="0,0,0" Up="0,1,0" NearPlaneDistance="1" FarPlaneDistance="500" FieldOfView="40" CameraController="Free"/> </ViewPort3D.Camera>
- 定向
定向相机允许用户围绕特定点进行环绕,并进行内外移动。键与自由相机模式相同,但移动仅限于围绕目标。
<PerspectiveCamera Position="-250,0,0" LookAtPoint="0,0,0" Up="0,1,0" NearPlaneDistance="1" FarPlaneDistance="500" FieldOfView="40" CameraController="Target"/> </ViewPort3D.Camera>
变换
其他链接: MSDN。
此时,您应该已经能够创建一个包含相机、添加对象并为它们打光的场景。如果您尝试过,您可能会注意到所有对象都从0,0,0开始并停留在那里。变换将允许您定义如何在场景中放置、定向、缩放以及动画化对象。
与模型一样,变换也需要集合。所有变换都必须列在集合中,集合本身通常位于网格图元内部。
<MeshPrimitive3D Mesh="{Box}">
<MeshPrimitive3D.Transform>
<Transform3DCollection>
# transforms go here
</Transform3DCollection>
</MeshPrimitive3D.Transform>
</MeshPrimitive3D>
平移
<TranslateTransform3D Offset="50 0 0" />
平移变换用于在世界中移动对象。它基本上将列出的X、Y、Z值添加到网格的每个点,从而有效地移动对象。
缩放
<ScaleTransform3D ScaleVector="2 2 2" ScaleCenter”0,0,0”/>
缩放变换用于通过将网格中的所有点从提供的中心点移入或移出来使对象变大或变小。ScaleVector
中的每个值都会扩展或收缩任何点到中心点在关联轴上的距离。应用统一缩放(ScaleVector
的X、Y和Z值都相同)将按比例改变对象的大小。
例如,如果您正在缩放一个以0,0,0为中心的立方体,并且将其缩放比例设置为2,1,1,则所有点将在X轴上移至中心距离的两倍,从而有效地使盒子变宽两倍。由于缩放值会乘以距离,因此任何小于1的缩放值都会在该轴上缩小对象。如果最后一个示例提供的ScaleVector
是0.5,1,1,则盒子将是其原始宽度的一半。
如果提供的中心点不在网格的中心,或者它不是围绕0,0,0居中的,则缩放可能会在一个方向上比另一个方向拉伸网格更多。此外,如果中心点完全在模型外部,缩放可能会移动对象。
旋转
<RotateTransform3D Axis="0 1 0" Angle="45" Center=”0,0,0”/>
旋转变换用于围绕特定点旋转网格中的所有点。单个旋转仅限于绕单个轴旋转,但可以应用多个旋转来实现不同的结果,这将在后面介绍。与缩放一样,它需要一个中心点来围绕指定的角度(以度为单位)旋转点。在上面的XAML代码中,轴朝上,因此任何旋转都会使其像顶部一样旋转。在这种情况下,将是45度,想象一个放在桌子上的立方体,您将其旋转45度。
如果旋转中心点不居中,对象将围绕该点旋转。
定义动画
*在继续之前,您应该熟悉XAML 2D动画标签。
在定义3D动画时,有三种主要的动画数据类型:Point3DAnimation
、Vector3DAnimation
和QuaternionAnimation
。每种都必须包装在其适当的集合标签中,即Point3DAnimationCollection
、Vector3DAnimationCollection
和QuaternionAnimation
。Point3D和Vector3D基本上是相同的,都是X、Y、Z位置,只是在数学术语上的区别在于Point代表空间中的一个点,而Vector代表一个方向和/或大小。
那是最低级别,每个变换都有一个或多个可供动画化的内容
转换
Translate只公开一个动画容器,即OffsetAnimations
。
<TranslateTransform3D Offset="-1 0 0" >
<TranslateTransform3D.OffsetAnimations>
<Vector3DAnimationCollection>
<Vector3DAnimation From="-1,0,0" To="1,0,0"
Duration="3" RepeatCount="1" />
</Vector3DAnimationCollection>
</TranslateTransform3D.OffsetAnimations>
</TranslateTransform3D>
Scale
Scale公开两个动画容器:ScaleVectorAnimations
和ScaleCenterAnimations
。
<ScaleTransform3D ScaleVector="1 1 1">
<ScaleTransform3D.ScaleVectorAnimations>
<Vector3DAnimationCollection>
<Vector3DAnimation From="1,1,1" To="2,2,2" Duration="10"/>
</Vector3DAnimationCollection>
</ScaleTransform3D.ScaleVectorAnimations>
<ScaleTransform3D. ScaleCenterAnimations >
<Point3DAnimationCollection>
<Point3DAnimation From="0,0,0" To="20,0,0" Duration="5"/>
</Point3DAnimationCollection>
</ScaleTransform3D. ScaleCenterAnimations >
</ScaleTransform3D>
旋转
Rotation公开两个动画容器:QuaternionRotationAnimations
和CenterAnimations
。出于某种原因,唯一能动画化对象旋转的方法是使用四元数,这在使用文本编辑器时非常麻烦。在编辑器制作出来之前,有一个工具来帮助生成四元数动画会很有用。解释四元数远远超出了本文档的范围,希望一些示例能有所帮助。
这里有几点需要记住……首先,四元数将走最短的路线到达新的方向。如果您想将其重新定向到X轴上的3590,四元数将移动-10而不是+3590。此外,没有“绕组”的概念,因此如果您想让对象旋转多次,则必须添加多个标签。以下示例将对象完全旋转两次
<Transform3DCollection>
<RotateTransform3D QuaternionRotation="0,0,0,1" Angle="60">
<RotateTransform3D.QuaternionRotationAnimations>
<QuaternionAnimationCollection>
<QuaternionAnimation
From="0,0,0,1" To="0,-1,0,0"
Begin="0"
Duration="3"
RepeatDuration="1"
AutoReverse="false" />
<QuaternionAnimation
From="0,-1,0,0"
To="0,0,0,-1"
Begin="3"
Duration="3"
RepeatDuration="1"
AutoReverse="false" />
</QuaternionAnimationCollection>
<QuaternionAnimationCollection>
<QuaternionAnimation
From="0,0,0,1" To="0,-1,0,0"
Begin="6"
Duration="3"
RepeatDuration="1"
AutoReverse="false" />
<QuaternionAnimation
From="0,-1,0,0"
To="0,0,0,-1"
Begin="9"
Duration="3"
RepeatDuration="1"
AutoReverse="false" />
</QuaternionAnimationCollection>
</RotateTransform3D.QuaternionRotationAnimations>
</RotateTransform3D>
</Transform3DCollection>
变换集合
MSDN.
到目前为止,您可能已经多次看到TransformCollection
标签,并且没有多想它,除了它包含了某些变换。这是真的,但是当涉及到变换集合时,顺序很重要。先平移再旋转,其结果将与先旋转再平移不同。如果这不能立即让您理解,我建议进行大量实验以了解组合变换如何产生不同的结果。要了解有关变换顺序及其影响的更多信息,我建议您访问此链接。它是2D且面向程序员的,但有一个应用程序您可以玩,以各种有趣的方式组合变换。
否则,这里有几个混合变换的例子
示例 A
第一张图片应用旋转然后平移,这会将对象旋转45度,然后将其移到右侧(正X)。第二张图片移动盒子,然后旋转它。由于它与旋转原点有一段距离,它将围绕原点旋转450度。
示例 B
第一张图片应用平移然后缩放,这将放大盒子,然后移动它。第二张图片将移动盒子,然后使用中心点作为原点进行缩放。请注意,它会将盒子移离原点更远。
3D中的2D
解释
一种真正加快XAML渲染速度的方法是完全在DirectX中渲染,而不使用任何GDI调用。不幸的是,任何无法访问Windows源代码的第三方开发人员都无法像Avalon那样完全实现XAML。但是,有一些替代解决方案;其中一种是将所有打算GDI绘制的内容转换为三角形,以便由3D管道进行渲染。这个过程现在被称为三角剖分或三角化。三角化2D绘图调用的过程可能很复杂,在某些情况下,比GDI稍慢。回报是当构造的2D几何体在物理上不改变但使用标准变换进行大量动画时。这使得图形过程几乎完全在显卡上进行,因此避免了2D渲染的缓慢(主要是等待图像到达显卡)。缺点是三角化后的表示可能与其2D对应物看起来不完全相同,但如果速度是您的目标,那么这可能不是问题。
目前,Mobiform的全DirectX渲染模式已完成98%。虽然相同的GDI绘图和DirectX绘图之间存在明显的差异,但您已经可以看到速度差异。随着时间的推移,三角化和渲染应该几乎与GDI绘图无法区分,并且速度会快得多。
有效使用
获得速度提升的关键是减少从GDI绘图构建三角化几何体所花费的时间。极其复杂的绘图可能很慢,但一旦进行三角化,它就可以非常快。使用变换来移动和旋转绘图比动画化2D XAML对象的属性更可取。例如,您可以创建一个球体并为其动画化位置,或者您可以将其包装在TransformCollection
中并使用平移来移动它。
待办事项
- 如上所述,使用变换来动画化2D对象有助于提高速度,但可能并不总是实用。当XAML属性更改但物理上不改变形状时,需要进行一些工作以在内部减少三角化。目前,每次更改属性时都会重新进行三角化,这在处理文本时尤其慢。
- 抗锯齿(平滑边缘)。这是GDI和DirectX渲染管道之间最大的视觉差异。GDI+在抗锯齿方面非常有效,而DirectX则以其糟糕的表现而闻名,尤其是在纯粹的无纹理几何体上。除了表现不佳之外,DirectX中的良好抗锯齿尚未得到广泛支持。不过,有一些选择,随着时间的推移和该领域的进步,渲染很快就会看起来非常相似。
- 带有孔洞的形状。目前,任何带有孔洞的2D形状(任何文本,butterfly.xaml示例)都会因切孔过程而明显减慢速度。
- 优化三角化。尽管一般规则是避免这种情况,但有时还是必须做的。总有改进的空间,让它变得更快一点。
- 新的文本系统。由于三角化非常慢,并且通常会反复、完全地重新三角化相同的字符,因此应该创建一个新的文本系统来模仿DirectX中的GDI+。
结论
希望这为您提供了XAML中3D的一个良好概述,并且您已准备好开始在Mobiform和/或Longhorn中进行实验。尽管Avalon的3D API仍处于早期开发阶段(并且在下一个预发行版中很可能会发生重大变化),但这里涵盖了基础知识,至少应该让您在下一个版本中抢占先机。从现在开始只会越来越好。