WPF-3D圖形

步、步、為營發表於2023-01-26

WPF-3D圖形

WPF的3D功能可以在不編寫任何c#程式碼的情況下進行繪製,只需要使用xaml即可完成3D圖形的渲染。本文主要講述了WPF-3D中的關鍵概念, 以及常用到的命中測試、2d控制元件如何在3D物件中進行渲染,除此之外,還演示瞭如何匯入外部3D模型。

關鍵概念

視口

視口指的是影像要展示在哪裡,可以理解為展示圖形的舞臺。在WPF中視口使用Viewport3D標籤表示。

相機

如果把視口比作舞臺,那相機就可以理解為觀眾的眼睛,不同的眼睛位置會看到不同的角度。

<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>
        <!--正交相機,用法類似-->
        <!--<OrthographicCamera/>-->
</Viewport3D.Camera>

光源

沒有光源也就看不到3D物件

<!--光線-->
<ModelVisual3D>
    <ModelVisual3D.Content>
        <Model3DGroup>
            <!--散射光線-->
            <AmbientLight Color="#FFF"/>
            <!--平行光-->
            <!--<DirectionalLight Color="#FFF" Direction="0,-1,0"/>-->
            <!--點光源-->
            <!--<PointLight Position="0,0,0"/>-->
            <!--錐形輻射光:手電筒-->
            <!--<SpotLight Position="0,0,0" Direction="0,0,-3"/>-->
        </Model3DGroup>
    </ModelVisual3D.Content>
</ModelVisual3D>

材質

3D幾何物件只是將輪廓定義出來,表面是沒有定義的,所以需要使用材質來展現出不同的物體表面。也可以理解為3D幾何物件只是勾勒出物體的輪廓,而材質則是上顏色。

<ModelUIElement3D >
    <ModelUIElement3D.Model>
        <GeometryModel3D>
            <!--材質-->
            <GeometryModel3D.Material>
                <!--散射材質-->
                <DiffuseMaterial Brush="Blue"/>
                <!--鏡面材質-->
                <!--<SpecularMaterial SpecularPower="1" Brush="Blue"/>-->
                <!--自發光材質-->
                <!--<EmissiveMaterial Color="Green" />-->
            </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">事件中可以進行改變顏色等操作

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">在時間中急性轉換處理

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標籤,實現如下圖所示的效果。

image-20230126121543225

<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"/>
        <!--TextureCoordinates:表示的二維平面座標,原點:左上角-->
    </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

<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>
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;
                    //EmissiveMaterial emissMat = new EmissiveMaterial(new SolidColorBrush(mainColor));
                    DiffuseMaterial diffMat = new DiffuseMaterial(new SolidColorBrush(mainColor));
                    //SpecularMaterial specMat = new SpecularMaterial(new SolidColorBrush(mainColor), 2000);
                    //materialGroup.Children.Add(emissMat);
                    materialGroup.Children.Add(diffMat);
                    //materialGroup.Children.Add(specMat);

                    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使用文件