WPF入门到跪下 第十三章 3D绘图 - 3D绘图基础

3D绘图基础

四大要点

WPF中的3D绘图涉及4个要点:

  • 视口,用来驻留3D内容
  • 3D对象
  • 照亮部分或整个3D场景的光源
  • 摄像机,提供在3D场景中进行观察的视点

一、视口

要展示3D内容,首先需要一个容器来装载3D内容。在WPF中,这个容器就是Viewport3D(3D视口),它继承自FrameworkElement,因此可以像其他元素那样在XAML中使用。

Viewport3D与其他元素相比较,仅增多了两个属性:CameraChildren

  • Camera:3D场景的观察者,也就是摄像机
  • Children:场景中的所有3D模型

此外,还有一个值得注意的属性,ClipToBounds,这个属性在使用上与其他元素没什么区别,只是如果在渲染复杂且频繁更新的3D场景中置为false,可以很好地提升性能。

二、3D对象

视口能够注入所有继承自Visual3D类的3D对象。但是如果我们要在WPF中通过XAML或者代码去创建可用的3D对象,其复杂程度是难以想象的,而WPF的原生库中也没有提供3D形状图元的集合。所以一些简单的立方体、圆柱或环形曲面都需要我们自己动手创建。
所幸的是,WPF采用与2D绘图类相同的方式来构造3D绘图类,所以在理解了2D绘图的基础上,进行3D构造操作会相对容易上手。

常用的几个基础类型
Visual3DVisual3D是所有3D对象的基类,可以使用其派生出轻量级或更为复杂的3D空间。其默认实现有ModelVisual3DModelUIElement3D等等。

Geometry3D:与2D图形中的Geometry类相似,Geometry3D用于表示3D表面。在2D中WPF虽然提供了几个2D几何图形,但是在3D中只提供了一个Geometry3D的具体实现类MeshGeometry3D,其在3D绘图中非常重要,用于定义所有的3D对象。

GeometryModel3D:用来使用Geometry3D对象填充Visual3D对象,类似于2D中的GeometryDrawing

Transform3D:控制3D对象进行移动、扭曲、旋转等操作,与2D的Transform有很多相同的地方。

1、几何体-MeshGeometry3D

为了过构建完整的3D对象,首先需要使用MeshGeometry3D定义几何形状,MeshGeometry3D表示3D网格,

  • 在WPF中,3D由用无数的三角形组合而成的网格(可以自行百度三角形组成3D)形成。

MeshGeometry3D通过顶点、法线、纹理坐标等信息描述了3D模型的形状和结构,GeometryModel3D的一个组成部分

常用属性

Positions:包含定义网格的所有点的集合,每个点都是组成网格的三角形中的一个顶点。

TriangleIndices:定义三角形,集合中的每个元素为Positions中每个顶点的序号,表示从哪个点到哪个点连成三角形。

Normals:为Positions集合中的每个顶点提供一个向量。

  • 这个向量指定了顶点在进行光照计算时使用的角度。当WPF为三角形表面着色时,使用法线向量为三角形的每个顶点度量光照。然后为了填充三角形表面,在这三个点之间进行插值。获取合适的法线向量,使得三位对象着色由很大的区别,例如可以使三角形之间的分割边混合在一起还是显示为一条清晰的线。

TextureCoordinates:为Positions集合中的每个顶点提供一个2D点。

  • 当使用VisualBrush对象绘制3D对象时,该属性定义了如何将一幅2D纹理映射到3D对象。

空间坐标

三维坐标比二维坐标多了一个Z轴,可以想象一下,屏幕的上下为Y轴,左右为X轴,至于Z轴则表示物体在屏幕中离我们距离,Z轴越小离我们越远,Z轴越大则离我们越近。

定义几何体

<MeshGeometry3D Positions="-1,0,0 0,1,0 1,0,0" TriangleIndices="0,2,1"/>

仔细查看上述定义的MeshGeometry3D对象:

  • Positions集合中定义了三个顶点,每个顶点由三个坐标轴的位置组成,(X,Y,Z)
  • TriangleIndices集合指定了三个顶点的连接顺序,定义了三角形。

这里有一个3D编程规则需要注意,定义形状时,必须以绕Z轴逆时针的顺序列出点,这样当我们迎着Z轴看向物体时才能看到正面,反之看到的是背面。

2、3D模型-GeometryModel3D

在创建了MeshGeometry3D对象后,需要将其封装到GeometryModel3D对象中。

GeometryModel3D结合几何形状表面材质来表示一个可渲染的3D模型。

常用属性

GeometryModel3D只有三个属性:GeometryMaterialBackMaterial

Geometry:使用MeshGeometry3D对象定义3D对象的形状。

Material:定义如何构成形状的正面,可以理解为定义表面的材质。

BackMaterial:定义背面的材质。

表面材质

对于3D模型来说,表面材质十分重要,其决定了物体的颜色以及灯光的表现效果。WPF提供了4个材质类型,这些类都继承自Material

  • DiffuseMaterial:平滑的无光泽表面,在各个方向上均匀地散射光纤。此材质最为常用,与显示世界中的大多数表面最为接近。
  • SpecularMaterial:有光泽、高亮度的外光(例如金属和玻璃)。像一面镜子,直接反向反射光线。
  • EmissiveMaterial:发光的外观,产生自己的光线(这些光线无法从场景中的其他对象反射回来)。
  • MaterialGroup:可以通过此属性组合多种材质,然后使用添加到MaterialGroup中的顺序层叠材质。(下文中有更为详细的解说)

需要注意的是,DiffuseMaterialSpecularMaterialEmissiveMaterial都提供了Brush属性,在设置这个属性时,如果希望使用除了SolidColorBrush以外的画刷,则需要进一步设置MeshGeometry3D对象的TextureCoordinates属性。

定义3D模型

  • 示例

    <GeometryModel3D>
        <GeometryModel3D.Geometry>
            <MeshGeometry3D Positions="-1,0,0,0,1,0,1,0,0" TriangleIndices="0,2,1"/>
        </GeometryModel3D.Geometry>
    
        <GeometryModel3D.Material>
            <DiffuseMaterial Brush="Yellow"/>
        </GeometryModel3D.Material>
    </GeometryModel3D>
    

注意,例子中,3D模型没有设置BackMaterial属性,所以如果从背面观察,三角形会消失不见。

3、光源

在WPF中,可以为3D场景添加一个或多个光源,然后根据选择的灯光类型、位置、方向以及强度来照亮3D对象。

与现实世界不同,WPF的光源具有如下两点特性:

  • 会分别为每个对象计算灯光效果,从一个对象反射的光不会影响另一个对象。同样的,一个对象不会在另一个对象上投射阴影。
  • 为每个三角形的每个顶点计算灯光,然后在三角形表面进行插值,因此可能会有很少的几个三角形对象无法被正确的照明。为了得到更好的光照效果,需要将形状分成数百换个或数千个三角形。

光源类型

WPF提供了4种灯光类型,都继承自Light抽象类

  • DirectionalLight:使用沿着指定方向传播的平行光线填充场景(类似太阳光),可以通过Direction属性设定方向。
  • AmbientLight:使用散射的光线填充场景。(仅次于DirectionalLight的常用光)
  • PointLight:从空间中的一点,向各个方向辐射的光线。
  • SpotLight:从一个点开始,以雏形向外辐射的光线。

查看下面的代码,表示使用白色的平行光线,指定光线从原点(0,0,0)出发并且经过点(-1,-1,-1)

<DirectionalLight Color="White" Direction="-1,-1,-1"/>

定义光源

所有的光源都为GeometryModel3D的子类,因此可以像处理3D对象一样处理光源,将她们放在ModelVisual3D对象的Content属性中,然后再放到视口里

  • 示例

    <Viewport3D>
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <DirectionalLight Color="White" Direction="-1,-1,-1"/>
            </ModelVisual3D.Content>
        </ModelVisual3D>
    
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <GeometryModel3D>
                    <GeometryModel3D.Geometry>
                        <MeshGeometry3D Positions="-1,0,0,0,1,0,1,0,0" TriangleIndices="0,2,1"/>
                    </GeometryModel3D.Geometry>
                    <GeometryModel3D.Material>
                        <DiffuseMaterial Brush="Yellow"/>
                    </GeometryModel3D.Material>
                </GeometryModel3D>
            </ModelVisual3D.Content>
        </ModelVisual3D>
    </Viewport3D>
    

三、摄像机

在渲染3D场景前,可以通过Camera对象设置ViewPort3D.Camera属性,在正确的位置放置摄像机,并使其朝向正确的方向。

摄像机类型

本质上,摄像机确定了如何将3D场景投影到Viewport对象的2D表面上。WPF提供了三种类型的的摄像机:

  • PerspectiveCamera:最常用的摄像机,会使得远处的对象看起来更小(透视投影)。
  • OrthographicCamera:平行投影3D对象,使得3D对象保持相同的尺寸(正交投影)。
  • MatrixCamera:用于将3D场景变换到2D视图的矩阵,在移植其他架构的代码时候需要使用这种类型的摄像机。

摄像机的位置和方向

在确定摄像机类型后,还要通过PositionLookDirection属性设置摄像机的位置和拍摄方向。

  • Position:设置摄像机在3D空间中的位置。
  • LookDirection:设定一个3D向量,指定摄像机的方向。
  • UpDirection:设置摄像机的原始倾斜角度,默认为(0,1,0),表示垂直向上

在这里插入图片描述

摄影机定点拍摄

在使用摄像机的时候,可以发现,如果修改Position属性移动了摄像机,但是没有同步调整LookDirection属性进行补偿,拍摄的视角就会偏离预期的拍摄目标。

为了在修改Position后,确保正确的设置LookDirection属性进行补偿,可应该选择一个点作为拍摄目标,然后LookDirection属性的值可以通过下面的公式来确定:

  • LookDirection = TargetCenterPoint - Position,例如 LookDirection = (0,0,0) - (-2,2,2)

这样不论Position如何设置,摄像机的方向都能始终指向目标点

其他属性

除了PositionLookDirectionUpDirection三个核心属性外,摄像机类还提供了如下一些关键属性:

  • FieldOfViewPerspectiveCamera摄像机对象的属性,用于设置摄像机能够看到多少场景,值越小,看到的场景越小但物体会被对应放大以适应Viewport3D对象。

    • 注意,该属性与调整Position的效果是不同的,且只能在PerspectiveCamera对象上使用,至于OrthographicCamera摄像机提供了类似的属性,Width
      在这里插入图片描述
      在这里插入图片描述
  • NearPlaneDistance:设置盲区的最小距离,比NearPlaneDistance更近的对象不会显示,默认为0.125。

    • 当复杂的网格离摄像机非常近时,显卡不能正确地确定对于摄像机而言哪个三角形是最近的,以及是否应当渲染,结果会在网格表面上造成伪影,此时可以增加NearPlaneDistance裁剪掉离摄像机非常近的对象
  • FarPlaneDistance:设置盲区的最大距离,比FarPlaneDistance更远的对象不会显示,默认为Double.PositiveInfinity

定义摄像机

  • 示例

    <Viewport3D>
        <Viewport3D.Camera>
            <PerspectiveCamera Position="-2,2,2" LookDirection="2,-2,-2" UpDirection="0,1,0"/>
        </Viewport3D.Camera>
        
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <DirectionalLight Color="White" Direction="-1,-1,-1"/>
            </ModelVisual3D.Content>
        </ModelVisual3D>
    
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <GeometryModel3D>
                    <GeometryModel3D.Geometry>
                        <MeshGeometry3D Positions="-1,0,0 0,1,0 1,0,0" TriangleIndices="0,2,1"/>
                    </GeometryModel3D.Geometry>
                    <GeometryModel3D.Material>
                        <DiffuseMaterial Brush="Yellow"/>
                    </GeometryModel3D.Material>
                </GeometryModel3D>
            </ModelVisual3D.Content>
        </ModelVisual3D>
    </Viewport3D>
    

四、立方体绘制

编写如下代码,可以进行立方体的绘制:

  • 示例

    <Viewport3D>
        <Viewport3D.Camera>
            <PerspectiveCamera Position="-12,22,22" LookDirection="2,-2,-2"/>
        </Viewport3D.Camera>
        
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <DirectionalLight Color="White" Direction="-1,-1,-1"/>
            </ModelVisual3D.Content>
        </ModelVisual3D>
    
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <GeometryModel3D>
                    <GeometryModel3D.Geometry>
                        <MeshGeometry3D Positions="0,0,0 10,0,0 0,10,0 10,10,0
                                                   0,0,10 10,0,10 0,10,10 10,10,10" 
                                        TriangleIndices="0,2,1 1,2,3 0,4,2 2,4,6
                                                        0,1,4 1,5,4 1,7,5 1,3,7
                                                        4,5,6 7,6,5 2,6,3 3,6,7"/>
                    </GeometryModel3D.Geometry>
                    <GeometryModel3D.Material>
                        <DiffuseMaterial Brush="Yellow"/>
                    </GeometryModel3D.Material>
                </GeometryModel3D>
            </ModelVisual3D.Content>
        </ModelVisual3D>
    </Viewport3D>
    
    

在这里插入图片描述

背面的面向

当定义三角形时,必须以逆时针顺序定义它们,从而使它们的前面面向前。但是上述代码中,可以看到背面上的三角形是以顺时针顺序定义的,包括索引“02,1”和“1,2,3”。这是因为立方体后侧面上的三角形必须面向后面,当绕Y轴旋转立方体看到背面时,面向后方的三角形将刚好面向前方。

着色和法线

从上面的图像可以看到一个现象,在我们定义的三角面分界线存在明显的光影着色,这是由于WPF计算光照的方式造成的,具体与每个面的共享顶点和其法向量有关,解决方案之一是通过多次声明每个顶点(每使用一次就声明一次),确保在三角形之间没有共享的顶点。

  • 示例

    <GeometryModel3D>
    	  <GeometryModel3D.Geometry>
    	      <MeshGeometry3D Positions="0,0,0 10,0,0 0,10,0 10,10,0
    	                                 0,0,0 0,0,10 0,10,0 0,10,10
    	                                 0,0,0 10,0,0 0,0,10 10,0,10
    	                                 10,0,0 10,10,10 10,0,10 10,10,0
    	                                 0,0,10 10,0,10 0,10,10 10,10,10
    	                                 0,10,0 0,10,10 10,10,0 10,10,10"
    	                      TriangleIndices="0,2,1 1,2,3 
    									                      4,5,6 6,5,7
    									                      8,9,10 9,11,10
    									                      12,13,14 12,15,13
    									                      16,17,18 19,18,17
    									                      20,21,22 22,21,23"/>
    	  </GeometryModel3D.Geometry>
    	  <GeometryModel3D.Material>
    	      <DiffuseMaterial Brush="Yellow"/>
    	  </GeometryModel3D.Material>
    </GeometryModel3D>
    

在这里插入图片描述

如上图所示,但是这种效果没有对错之分,只是根据实际情况来选择自己需要的效果。

另外一种方式是通过MeshGeometry3D对象的Normals属性来为每一个顶点的设置法向量,这里就不再展开了。

五、Model3DGroup和MaterialGroup

1、模型组合-Model3DGroup

Model3DGroupModel3D 的一个派生类(GeometryModel3DLight也继承自Model3D)。其主要作用是将多个 3D 模型组合在一起,以便进行统一的操作、变换和渲染。

主要作用

Model3DGroup的主要作用如下:

  • 模型组合:Model3DGroup 可以将多个 Model3D 对象(如 GeometryModel3DModel3D 的其他子类,包括嵌套使用Model3DGroup )组合成一个单独的模型。使管理和组织复杂的 3D 场景变得更加简单。
  • 变换支持:可以对 Model3DGroup 应用变换,比如平移、旋转和缩放。这些变换会影响到组内的所有模型,便于整体操作。
  • 视觉层次结构:Model3DGroup 能够保持视觉结构的层次,使得场景中的不同模型可以作为子元素轻松管理。
  • 性能优化:通过组合多个模型,有时可以优化渲染性能,因为渲染引擎能够更有效地处理分组的对象。

在复杂的3D场景中,通常需要安排多个3D对象,虽然一个ViewPort3D对象可以包含多个Visual3D对象,可以通过多个Visual3D 使用不同的网格来表示多个不同的3D对象,但是这不是构建 3D 场景的最好方法。正确的做法应该是通过使用Model3DGroup 在单个Visual3D对象中放置多个3D模型对象。

下面是使用Model3DGroup 的一段伪代码,实际上复杂的3D模型可以通过专业软件进行绘制后再转为XAML,不会直接在XAML中编写。

  • 示例

    <ModelVisual3D>
        <ModelVisual3D.Content>
            <Model3DGroup x:Name="group1" Transform="{DynamicResource SceneTR20}">
                <AmbientLight .../>
                <DirectionalLight .../>
                <DirectionalLight .../>
                <Model3DGroup x:Name="group2">
                    <GeometryModel3D x:Name="model1" .../>
                    <GeometryModel3D x:Name="model2" .../>
                </Model3DGroup>
            </Model3DGroup>
        </ModelVisual3D.Content>
    </ModelVisual3D>
    

2、材质组合-MaterialGroup

前面的学习中,3D模型都只使用了DiffuseMaterial材质,在定义DiffuseMaterial材质时,需要提供Brush对象,然后模型的整体颜色会由画刷和光照共同来决定。如果有直射的、最强的光线,将看到准确的画刷颜色,但如果光线以一定角度照射到表面将看到更暗淡的带阴影的颜色。

  • WPF 允许使 3D对象部分透明。最简单的方法是设置材质使用的画刷的Opacity属性,使其值小于 1。

由于SpecularMaterialEmissiveMaterial材质的工作特性,这两种材质通常会与DifseMaterial材质结合使用。

SpecularMaterial

SpecularMaterial材质比DiffuseMaterial材质能更尖锐的反射光纤,可以通过SpecularPower属性来控制高光的集中度和锐利度

  • 较低的SpecularPower值(如 1-10)会产生较大的、柔和的高光,例如磨砂面。
  • 较高的SpecularPower值(如 20-128)会导致高光变得更小且尖锐,呈现出更明显的焦点效果,例如,光滑的金属表面。

SpecularMaterial 材质常用于为 DiffuseMaterial材质添加强光效果,在黑色DiffuseMaterial材质上使用白色的SpecularMaterial材质来可以造成类似塑料的表面,而更暗的SpecularMaterial和黑色的DifuseMaterial材质可产生更具金属质感的效果。

  • 示例

    <GeometryModel3D.Material>
        <MaterialGroup>
            <DiffuseMaterial>
                <DiffuseMaterial.Brush>
                    <SolidColorBrush Color="DarkBlue"/>
                </DiffuseMaterial.Brush>
            </DiffuseMaterial>
            <SpecularMaterial SpecularPower="30">
                <SpecularMaterial.Brush>
                    <SolidColorBrush Color="LightBlue"/>
                </SpecularMaterial.Brush>
            </SpecularMaterial>
        </MaterialGroup>
    </GeometryModel3D.Material>
    
    

下图中,曲面上有几个地方显示强光是因为场景中包含了两个指向不同方向的定向光

在这里插入图片描述

需要注意的是,在白色表面上放置 SpecularMaterialEmissiveMaterial 材质,根本就看不到任何内容。因为 SpecularMaterialEmissiveMaterial 材质会附加地呈现它们的颜色。要查看 SpecularMaterialEmissiveMaterial材质的完整效果,需要将它们放在黑色表面上(或在黑色 DifuseMaterial 材质上使用它们)。

EmissiveMaterial

同样,可通过在 DiffuseMaterial 材质上层叠 EmissiveMaterial 材质来得到更有趣的效果。因为EmissiveMaterial 材质的附加性质,会使得颜色进行混合。例如,如果在蓝色的 DifuseMaterial 材质上放置红色的 EmissiveMaterial 材质,形状会变成紫色,这是因为EmissiveMaterial材质在形状的整个表面会呈现相同数量的红色,而 DiffuseMaterial 材质会根据场景中的光源进行着色。

六、纹理映射

上文的学习中,一直都在使用SolidColorBrush画刷绘制对象。实际上,除了SolidColorBrush,WPF 支持使用任何画刷绘制 DifuseMaterial 对象。这意味着可使用渐变颜色(LinearGradientBrushRadialGradientBrush)、向量或位图图像(ImageBrush)以及来自 2D 元素(VisualBrush)的内容进行绘制。

在使用SolidColorBrush以外的画刷时,需要使用MeshGeometry3D.TextureCoordinates集合提供附加信息来告诉WPF如何将画刷的2D内容映射到3D表面。

1、ImageBrush画刷

这里使用上文的立方体模型,然后使用ImageBrush画刷,将要展示的位图映射到立方体上面。

  • 示例

    <Viewport3D>
        <Viewport3D.Camera>
            <PerspectiveCamera Position="-12,22,22" LookDirection="2,-2,-2"/>
        </Viewport3D.Camera>
    
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <DirectionalLight Color="White" Direction="-1,-1,-1"/>
            </ModelVisual3D.Content>
        </ModelVisual3D>
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <DirectionalLight Color="White" Direction="1,1,1"/>
            </ModelVisual3D.Content>
        </ModelVisual3D>
    
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <GeometryModel3D>
                    <GeometryModel3D.Geometry>
                       <GeometryModel3D.Geometry>
    										    <MeshGeometry3D Positions="0,0,0 10,0,0 0,10,0 10,10,0
    										                               0,0,0 0,0,10 0,10,0 0,10,10
    										                               0,0,0 10,0,0 0,0,10 10,0,10
    										                               10,0,0 10,10,10 10,0,10 10,10,0
    										                               0,0,10 10,0,10 0,10,10 10,10,10
    										                               0,10,0 0,10,10 10,10,0 10,10,10" 
    										            TriangleIndices="0,2,1 1,2,3 
    										                             4,5,6 6,5,7
    										                             8,9,10 9,11,10
    										                             12,13,14 12,15,13
    										                             16,17,18 19,18,17
    										                             20,21,22 22,21,23"
    										            TextureCoordinates="1,1 0,1 1,0 0,0
    										                                0,1 1,1 0,0 1,0
    										                                1,1 1,0 0,1 0,0
    										                                1,1 0,0 0,1 1,0
    										                                0,1 1,1 0,0 1,0
    										                                0,1 1,1 0,0 1,0"/>
                    </GeometryModel3D.Geometry>
                    <GeometryModel3D.Material>
                        <DiffuseMaterial>
                            <DiffuseMaterial.Brush>
                                <ImageBrush ImageSource="/3DWPF;component/WPF.jpeg"></ImageBrush>
                            </DiffuseMaterial.Brush>
                        </DiffuseMaterial>
                    </GeometryModel3D.Material>
                </GeometryModel3D>
            </ModelVisual3D.Content>
        </ModelVisual3D>
    </Viewport3D>
    
    

这个TextureCoordinates集合实际上告诉 WPF 让表示画刷内容的矩形的左下角使用点(0,0),并将该点映射到 3D 空间中的相应点(0,0,0)。类似地,右下角使用(0,1),并且映射到(0,0,10);使左上角(1,0)映射到(0,10,0);使右上角(1,1)映射到(0,10,10),以此类推,完成每一个面的画刷映射。

在这里插入图片描述

这个过程一般不会自己去手动填写,当面对复杂的3D模型时,手写是在太离谱了,一般都会使用建模工具或者第三方的插件来完成,这里只需要了解一下即可。

2、视频和VisualBrush画刷

除了普通画像,还可以将具有动画渐变的画刷、视频映射到3D表面。当播放视频时,内容实时显示在3D表面上。实际上要实现这个效果相对简单,只需要使用VisualBrush替代ImageBrush,并为可视化对象使用一个MediaElement元素即可。

  • 示例

    <GeometryModel3D.Material>
        <DiffuseMaterial>
            <DiffuseMaterial.Brush>
                <VisualBrush>
                    <VisualBrush.Visual>
                        <MediaElement>
                            <MediaElement.Triggers>
                                <EventTrigger RoutedEvent="MediaElement.Loaded">
                                    <EventTrigger.Actions>
                                        <BeginStoryboard>
                                            <Storyboard>
                                                <MediaTimeline Source="/3DWPF;component/test.mp4"/>
                                            </Storyboard>
                                        </BeginStoryboard>
                                    </EventTrigger.Actions>
                                </EventTrigger>
                            </MediaElement.Triggers>
                        </MediaElement>
                    </VisualBrush.Visual>
                </VisualBrush>
            </DiffuseMaterial.Brush>
        </DiffuseMaterial>
    </GeometryModel3D.Material>
    

七、变换

与 2D 内容一样,改变 3D场景某一方面最强大并且最灵活的方法是使用变换。想要对3D场景进行修改,只需要使用3D对象的

Transform属性,具体有如下几种常用的变换场景:

  • 通过GeometryModel3D.TransformModel3DGroup.Transform进行单个3D模型或一组3D模型的变换。

  • 通过ModelVisual3D.Transform属性,进行应用于该ModelVisual3D对象的变换,从而改变整个场景。

  • 通过DirectionalLight.Transform属性,进行灯光的变换,从而改变场景中的光照(可实现日出、日落效果)。

  • 通过PerspectiveCamera.Transform属性,进行摄像机的变换,可以在场景中移动摄像机。

  • 示例

    <Viewport3D>
        <Viewport3D.Camera>
            <PerspectiveCamera Position="-12,22,22" LookDirection="2,-2,-2"/>
        </Viewport3D.Camera>
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <DirectionalLight Color="White" Direction="-1,-1,-1"/>
            </ModelVisual3D.Content>
        </ModelVisual3D>
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <DirectionalLight Color="White" Direction="1,1,1"/>
            </ModelVisual3D.Content>
        </ModelVisual3D>
        <ModelVisual3D>
            <ModelVisual3D.Content>
                <GeometryModel3D>
                    <GeometryModel3D.Geometry>
                        <MeshGeometry3D Positions="0,0,0 10,0,0 0,10,0 10,10,0
                                                   0,0,0 0,0,10 0,10,0 0,10,10
                                                   0,0,0 10,0,0 0,0,10 10,0,10
                                                   10,0,0 10,10,10 10,0,10 10,10,0
                                                   0,0,10 10,0,10 0,10,10 10,10,10
                                                   0,10,0 0,10,10 10,10,0 10,10,10" 
                                        TriangleIndices="0,2,1 1,2,3 
                                                         4,5,6 6,5,7
                                                         8,9,10 9,11,10
                                                         12,13,14 12,15,13
                                                         16,17,18 19,18,17
                                                         20,21,22 22,21,23"/>
                    </GeometryModel3D.Geometry>
                    <GeometryModel3D.Material>
                        <DiffuseMaterial Brush="Yellow"/>
                    </GeometryModel3D.Material>
                    <GeometryModel3D.Transform>
                        <Transform3DGroup>
                            <TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="2"/>
                            <ScaleTransform3D ScaleX="0.5" ScaleY="1" ScaleZ="1"/>
                            <RotateTransform3D>
                                <RotateTransform3D.Rotation>
    		                            <!--表示绕哪个轴进行多少个角度的旋转,这里是绕着y轴-->
                                    <AxisAngleRotation3D Angle="25" Axis="0 1 0"/>
                                </RotateTransform3D.Rotation>
                            </RotateTransform3D>
                        </Transform3DGroup>
                    </GeometryModel3D.Transform>
                </GeometryModel3D>
            </ModelVisual3D.Content>
        </ModelVisual3D>
    </Viewport3D>
    

在这里插入图片描述

根据不同的变换效果再与动画进行结合,就可以产生不同的交互效果。各个变换对象和属性的使用可以参考2D图形的变换,他们都有着类似的作用和效果。

旋转动画

如下实例中,让前文中的立方体在按下按钮后,围绕三个轴进行旋转。

  • 示例

    <Grid>
        <Viewport3D>
            <Viewport3D.Camera>
                <PerspectiveCamera Position="-12,22,22" LookDirection="2,-2,-2"/>
            </Viewport3D.Camera>
    
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <DirectionalLight Color="White" Direction="-1,-1,-1"/>
                </ModelVisual3D.Content>
            </ModelVisual3D>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <DirectionalLight Color="White" Direction="1,1,1"/>
                </ModelVisual3D.Content>
            </ModelVisual3D>
    
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <GeometryModel3D x:Name="cube">
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D Positions="0,0,0 10,0,0 0,10,0 10,10,0
                                                       0,0,0 0,0,10 0,10,0 0,10,10
                                                       0,0,0 10,0,0 0,0,10 10,0,10
                                                       10,0,0 10,10,10 10,0,10 10,10,0
                                                       0,0,10 10,0,10 0,10,10 10,10,10
                                                       0,10,0 0,10,10 10,10,0 10,10,10" 
                                    TriangleIndices="0,2,1 1,2,3 
                                                     4,5,6 6,5,7
                                                     8,9,10 9,11,10
                                                     12,13,14 12,15,13
                                                     16,17,18 19,18,17
                                                     20,21,22 22,21,23"
                                    TextureCoordinates="1,1 0,1 1,0 0,0
                                                        0,1 1,1 0,0 1,0
                                                        1,1 1,0 0,1 0,0
                                                        1,1 0,0 0,1 1,0
                                                        0,1 1,1 0,0 1,0
                                                        0,1 1,1 0,0 1,0"/>
                        </GeometryModel3D.Geometry>
                        <GeometryModel3D.Material>
                            <DiffuseMaterial>
                                <DiffuseMaterial.Brush>
                                    <ImageBrush ImageSource="/3DWPF;component/WPF.jpeg"></ImageBrush>
                                </DiffuseMaterial.Brush>
                            </DiffuseMaterial>
                        </GeometryModel3D.Material>
                        <GeometryModel3D.Transform>
                            <RotateTransform3D>
                                <RotateTransform3D.Rotation>
                                    <AxisAngleRotation3D x:Name="rotate" Angle="0" Axis="1 1 1"/>
                                </RotateTransform3D.Rotation>
                            </RotateTransform3D>
                        </GeometryModel3D.Transform>
                    </GeometryModel3D>
                </ModelVisual3D.Content>
            </ModelVisual3D>
        </Viewport3D>
        <Button Content="Rotate" Height="40" Width="100" VerticalAlignment="Bottom" HorizontalAlignment="Left">
            <Button.Triggers>
                <EventTrigger RoutedEvent="Button.Click">
                    <BeginStoryboard>
                        <Storyboard RepeatBehavior="Forever">
                            <DoubleAnimation Storyboard.TargetName="rotate" Storyboard.TargetProperty="Angle" To="360" Duration="0:0:2.5"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Button.Triggers>
        </Button>
    </Grid>
    
    

在这里插入图片描述

八、3D表面上的2D元素

这里所说的3D表面上的2D元素,指的是在3D模型的表面上附加可交互的2D控件。前文中讲解了可以使用VisualBrush画刷对3D模型的表面进行纹理映射,虽然也可以通过VisualBrush获取普通控件的可视化外观来进行映射,但那只是控件的外观,并不能像真正的控件那样去使用。针对这个问题的解决方案是在Viewport3D中使用Viewport2DVisual3D对象。

可以简单的理解为,Viewport2DVisual3D对象可以指定一个3D平面,并使用纹理映射,将我们需要的控件映射到该平面上,且保持控件的原有特性。

Viewport2DVisual3D对象提供了如下属性,方便我们进行设置:

  • Geometry:定义3D表面网格。

  • Visual:设置要放到3D表面上的2D控件,只能使用一个控件,但是通过容器控件可以解决这个问题。

  • Material:用于渲染2D内容的材质,通常使用DiffuseMaterial材质,且必须在材质上使用附加属性Viewport2DVisual3D.IsVisualHostMaterial="True",使材质能用于显示控件内容。

  • Transform:设置变换。

  • 示例

    <Grid>
        <Viewport3D>
            <Viewport3D.Camera>
                    <PerspectiveCamera Position="-12,22,22" LookDirection="2,-2,-2"/>
                </Viewport3D.Camera>
            <ModelVisual3D>
                    <ModelVisual3D.Content>
                        <DirectionalLight Color="White" Direction="-1,-1,-1"/>
                    </ModelVisual3D.Content>
                </ModelVisual3D>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <DirectionalLight Color="White" Direction="1,1,1"/>
                </ModelVisual3D.Content>
            </ModelVisual3D>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <GeometryModel3D x:Name="cube">
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D Positions="0,0,0 10,0,0 0,10,0 10,10,0
                                               0,0,10 10,0,10 0,10,10 10,10,10" 
                                    TriangleIndices="0,2,1 1,2,3 0,4,2 2,4,6
                                                    0,1,4 1,5,4 1,7,5 1,3,7
                                                    4,5,6 7,6,5 2,6,3 3,6,7"/>
                        </GeometryModel3D.Geometry>
                        <GeometryModel3D.Material>
                            <DiffuseMaterial Brush="Yellow"/>
                        </GeometryModel3D.Material>
                        <GeometryModel3D.Transform>
                            <RotateTransform3D>
                                <RotateTransform3D.Rotation>
                                    <AxisAngleRotation3D Angle="{Binding ElementName=slider,Path=Value}" Axis="0 1 0"/>
                                </RotateTransform3D.Rotation>
                            </RotateTransform3D>
                        </GeometryModel3D.Transform>
                    </GeometryModel3D>
                </ModelVisual3D.Content>
            </ModelVisual3D>
            <Viewport2DVisual3D>
                <Viewport2DVisual3D.Geometry>
                    <MeshGeometry3D
                            Positions="0,0,0 0,0,10 0,10,0 0,10,10"
                            TriangleIndices="0,1,2 2,1,3"
                            TextureCoordinates="0,1 1,1 0,0 1,0"/>
                </Viewport2DVisual3D.Geometry>
                <Viewport2DVisual3D.Material>
                    <DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial="True"/>
                </Viewport2DVisual3D.Material>
                <Viewport2DVisual3D.Visual>
                    <Border BorderBrush="Yellow" BorderThickness="1">
                        <StackPanel Margin="10">
                            <TextBlock Margin="3">在3D模型平面上附加2D内容</TextBlock>
                            <Button Margin="3">点击</Button>
                            <TextBox Margin="3">输入框</TextBox>
                        </StackPanel>
                    </Border>
                </Viewport2DVisual3D.Visual>
                <Viewport2DVisual3D.Transform>
                    <RotateTransform3D>
                        <RotateTransform3D.Rotation>
                            <AxisAngleRotation3D Angle="{Binding ElementName=slider,Path=Value}" Axis="0 1 0"/>
                        </RotateTransform3D.Rotation>
                    </RotateTransform3D>
                </Viewport2DVisual3D.Transform>
            </Viewport2DVisual3D>
        </Viewport3D>
        <Slider VerticalAlignment="Top" Margin="10" Maximum="360" x:Name="slider"/>
    </Grid>
    

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SchuylerEX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值