WPF-3D图形 WPF的3D功能可以在不编写任何c#代码的情况下进行绘制,只需要使用xaml即可完成3D图形的渲染。本文主要讲述了WPF-3D中的关键概念, 以及常用到的命中测试、2d控件如何在3D对象中进行渲染,除此之外,还演示了如何导入外部3D模型。
关键概念 视口 视口指的是图像要展示在哪里,可以理解为展示图形的舞台。在WPF中视口使用Viewport3D
标签表示。
相机 如果把视口比作舞台,那相机就可以理解为观众的眼睛,不同的眼睛位置会看到不同的角度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <Viewport3D > <Viewport3D.Camera > <PerspectiveCamera Position ="8,5,10" LookDirection ="-7,-2,-10" FarPlaneDistance ="40" NearPlaneDistance ="10" FieldOfView ="60" > <PerspectiveCamera.Transform > <RotateTransform3D CenterX ="1.5" CenterY ="1" CenterZ ="0.5" > <RotateTransform3D.Rotation > <AxisAngleRotation3D Angle ="45" Axis ="0,1,0" /> </RotateTransform3D.Rotation > </RotateTransform3D > </PerspectiveCamera.Transform > </PerspectiveCamera > </Viewport3D.Camera >
光源 没有光源也就看不到3D对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <ModelVisual3D > <ModelVisual3D.Content > <Model3DGroup > <AmbientLight Color ="#FFF" /> </Model3DGroup > </ModelVisual3D.Content > </ModelVisual3D >
材质 3D几何对象只是将轮廓定义出来,表面是没有定义的,所以需要使用材质来展现出不同的物体表面。也可以理解为3D几何对象只是勾勒出物体的轮廓,而材质则是上颜色。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <ModelUIElement3D > <ModelUIElement3D.Model > <GeometryModel3D > <GeometryModel3D.Material > <DiffuseMaterial Brush ="Blue" /> </GeometryModel3D.Material > <GeometryModel3D.Geometry > <MeshGeometry3D Positions ="0,0,1 0,2,1 3,2,1 3,0,1 0,0,0 0,2,0 3,2,0 3,0,0" TriangleIndices ="2,3,7 7,6,2 1,5,4 0,1,4" /> </GeometryModel3D.Geometry > </GeometryModel3D > </ModelUIElement3D.Model > </ModelUIElement3D >
3D对象 3D对象则是具体的对象,在WPF中视口使用<ModelUIElement3D>
标签表示。在WPF中,图形是以三角面片作为最基本的展示单元,因为三角形是最稳定的即三个点可以确定出唯一的一个平面,任何复杂的图形都是由多个三角面片组成的。在给TriangleIndices
属性赋值时,一定注意三个点的顺序。
命中测试(鼠标交互) 想要使用鼠标点击得到某个图形,可以在具体的某个3D对象中,增加MouseLeftButtonDown
事件
<ModelUIElement3D MouseLeftButtonDown="ModelUIElement3D_MouseLeftButtonDown">
事件中可以进行改变颜色等操作
1 2 3 4 5 6 private void ModelUIElement3D_MouseLeftButtonDown (object sender, MouseButtonEventArgs e ){ ModelUIElement3D mui3d = sender as ModelUIElement3D; var model = mui3d.Model as GeometryModel3D; (model.Material as DiffuseMaterial).Brush = Brushes.Orange; }
如果有很多3D对象,在每个具体的对象上面增加事件会很麻烦,也可以直接在Viewport3D
中增加事件
<Viewport3D MouseLeftButtonDown="Viewport3D_MouseLeftButtonDown">
在时间中急性转换处理
1 2 3 4 5 6 7 8 9 10 private void Viewport3D_MouseLeftButtonDown (object sender, MouseButtonEventArgs e ){ Viewport3D viewport3D=sender as Viewport3D; Point location=e.GetPosition(viewport3D); HitTestResult hitTestResult=VisualTreeHelper.HitTest(viewport3D, location); if (hitTestResult != null ) { ... } }
3D对象中2D控件渲染 如果要在3D对象中增加控件,可以使用Viewport2DVisual3D
标签,实现如下图所示的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <Viewport3D > <Viewport2DVisual3D > <Viewport2DVisual3D.Geometry > <MeshGeometry3D Positions ="0,0,1 0,2,1 3,2,1 3,0,1 0,0,0 0,2,0 3,2,0 3,0,0" TriangleIndices ="0,2,1 0,3,2 6,4,5 6,7,4" TextureCoordinates ="0,1 0,0 1,0 1,1" /> </Viewport2DVisual3D.Geometry > <Viewport2DVisual3D.Material > <DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial ="True" Brush ="White" /> </Viewport2DVisual3D.Material > <Viewport2DVisual3D.Visual > <Border BorderThickness ="1" BorderBrush ="Yellow" > <StackPanel > <TextBlock Text ="Hello World" Foreground ="Green" /> <Button Content ="Button" Click ="Button_Click" /> </StackPanel > </Border > </Viewport2DVisual3D.Visual > </Viewport2DVisual3D > <Viewport3D >
外部导入3D模型 在wpf中绘制3D模型还是非常麻烦的,在实际工作中用的比较多的是从外部导入已有的3d模型。推荐一个比较好的第三方库HelixToolKit
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 <Window x:Class ="WpfApp2.MainWindow" xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d ="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local ="clr-namespace:WpfApp2" xmlns:helix ="http://helix-toolkit.org/wpf" mc:Ignorable ="d" Title ="MainWindow" Height ="450" Width ="800" > <Grid > <helix:HelixViewport3D Name ="viewPort3d" ShowViewCube ="True" ViewCubeBackText ="后" ViewCubeFrontText ="前" ViewCubeHeight ="100" ViewCubeWidth ="100" ViewCubeVerticalPosition ="Bottom" ViewCubeHorizontalPosition ="Right" ShowCoordinateSystem ="True" CoordinateSystemLabelForeground ="Red" CoordinateSystemHorizontalPosition ="Left" CoordinateSystemVerticalPosition ="Bottom" ShowFrameRate ="True" IsViewCubeEdgeClicksEnabled ="False" > <helix:HelixViewport3D.Camera > <PerspectiveCamera FieldOfView ="45" LookDirection ="0,0,-414.387754871885" FarPlaneDistance ="30000" NearPlaneDistance ="0.1" Position ="9.9475983006414E-14,91.037123633789,414.387754871885" UpDirection ="0,1,0" /> </helix:HelixViewport3D.Camera > <helix:HelixViewport3D.Background > <LinearGradientBrush EndPoint ="0.5,1" StartPoint ="0.5,0" > <GradientStop Color ="#444" Offset ="0" /> <GradientStop Color ="#EEE" Offset ="1" /> </LinearGradientBrush > </helix:HelixViewport3D.Background > <helix:GridLinesVisual3D Width ="16000" Length ="16000" Thickness ="2" MinorDistance ="500" MajorDistance ="500" Fill ="Gray" /> <helix:DefaultLights /> <ModelVisual3D x:Name ="model" > </ModelVisual3D > </helix:HelixViewport3D > </Grid > </Window >
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 60 61 62 63 64 65 66 67 68 69 namespace WpfApp2 { public partial class MainWindow : Window { List<string > modelPaths = new List<string >(); string basePath = AppDomain.CurrentDomain.BaseDirectory + "\\ModelFiles\\" ; public MainWindow () { InitializeComponent(); modelPaths.Add("IRB4600_20kg-250_LINK1_CAD_rev04.stl" ); modelPaths.Add("IRB4600_20kg-250_LINK2_CAD_rev04.stl" ); modelPaths.Add("IRB4600_20kg-250_LINK3_CAD_rev005.stl" ); modelPaths.Add("IRB4600_20kg-250_LINK4_CAD_rev04.stl" ); modelPaths.Add("IRB4600_20kg-250_LINK5_CAD_rev04.stl" ); modelPaths.Add("IRB4600_20kg-250_LINK6_CAD_rev04.stl" ); modelPaths.Add("IRB4600_20kg-250_LINK3_CAD_rev04.stl" ); modelPaths.Add("IRB4600_20kg-250_CABLES_LINK1_rev03.stl" ); modelPaths.Add("IRB4600_20kg-250_CABLES_LINK2_rev03.stl" ); modelPaths.Add("IRB4600_20kg-250_CABLES_LINK3_rev03.stl" ); modelPaths.Add("IRB4600_20kg-250_BASE_CAD_rev04.stl" ); this .Loaded += MainWindow_Loaded; viewPort3d.RotateGesture = new MouseGesture(MouseAction.RightClick); viewPort3d.PanGesture = new MouseGesture(MouseAction.LeftClick); } private void MainWindow_Loaded (object sender, RoutedEventArgs e ) { viewPort3d.Camera.LookDirection = new Vector3D(2038 , -5200 , -2930 ); viewPort3d.Camera.UpDirection = new Vector3D(-0.145 , 0.372 , 0.917 ); viewPort3d.Camera.Position = new Point3D(-1571 , 4801 , 3774 ); this .model.Content = InitializeModels(this .modelPaths); } private Model3DGroup InitializeModels (List<string > modelsNames ) { Model3DGroup group = new Model3DGroup(); try { ModelImporter import = new ModelImporter(); foreach (string modelName in modelsNames) { var materialGroup = new MaterialGroup(); Color mainColor = Colors.White; DiffuseMaterial diffMat = new DiffuseMaterial(new SolidColorBrush(mainColor)); materialGroup.Children.Add(diffMat); var link = import.Load(basePath + modelName); GeometryModel3D model = link.Children[0 ] as GeometryModel3D; model.Material = materialGroup; model.BackMaterial = materialGroup; group .Children.Add(link); } } catch (Exception e) { MessageBox.Show("未知异常:" + e.StackTrace); } return group ; } } }
HelixToolKit使用文档