🎮 WPF 3D 入門實戰:從零打造一個可交互的立方體模型
標題:
🚀《WPF 3D 開發全攻略:實現旋轉、平移、縮放與法線顯示》
💡 引言
在現代圖形應用中,3D 可視化已經成為不可或缺的一部分。WPF 提供了強大的 Viewport3D
控件,支持我們在桌面應用中輕松構建三維場景。
本文將手把手帶你實現一個完整的 WPF 3D 應用程序,包括:
- 創建基礎立方體模型
- 添加光源和材質
- 實現鼠標控制視角(旋轉、平移、縮放)
- 法線計算與可視化展示
- 完整項目結構與代碼解析
非常適合剛接觸 WPF 3D 的開發者入門學習!
🧱 第一步:創建窗口與視口
我們使用 Viewport3D
來承載所有 3D 內容,并為其添加相機和光源。
? XAML 部分(MainWindow.xaml)
<Window x:Class="_3D_WPF_Demo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="3D 立方體交互演示" Height="900" Width="1600"><Grid><Viewport3D Name="viewPort"MouseLeftButtonDown="ViewPort_MouseLeftButtonDown"MouseLeftButtonUp="ViewPort_MouseLeftButtonUp"MouseMove="ViewPort_MouseMove"PreviewMouseWheel="ViewPort_PreviewMouseWheel"MouseDown="ViewPort_MouseDown"MouseUp="ViewPort_MouseUp"><!-- 環境光 --><ModelVisual3D><ModelVisual3D.Content><Model3DGroup><AmbientLight Color="White"/></Model3DGroup></ModelVisual3D.Content></ModelVisual3D><!-- 方向光 --><ModelVisual3D><ModelVisual3D.Content><Model3DGroup><DirectionalLight Color="White" Direction="0,1,0"/></Model3DGroup></ModelVisual3D.Content></ModelVisual3D><!-- 相機 --><Viewport3D.Camera><PerspectiveCamera x:Name="mainCamera"Position="0,0,300"LookDirection="0,0,-1"UpDirection="0,1,0"FieldOfView="60"/></Viewport3D.Camera></Viewport3D></Grid>
</Window>
📦 第二步:創建立方體模型
我們手動定義立方體頂點和三角形索引,并通過工具類生成幾何體。
🧰 工具類 _3DModelOperateHelper.cs
using System.Windows.Media;
using System.Windows.Media.Media3D;namespace _3D_WPF_Demo.Tools
{public static class _3DModelOperateHelper{public static GeometryModel3D CreateSingleColorCube(int width, int height, int depth, DiffuseMaterial diffuseMaterial){Point3DCollection points = new Point3DCollection(new Point3D[] {new Point3D(-0.5*width, -0.5*height, -0.5*depth), // 0new Point3D(0.5*width, -0.5*height, -0.5*depth), // 1new Point3D(0.5*width, 0.5*height, -0.5*depth), // 2new Point3D(-0.5*width, 0.5*height, -0.5*depth), // 3new Point3D(-0.5*width, -0.5*height, 0.5*depth), // 4new Point3D(0.5*width, -0.5*height, 0.5*depth), // 5new Point3D(0.5*width, 0.5*height, 0.5*depth), // 6new Point3D(-0.5*width, 0.5*height, 0.5*depth) // 7});Int32Collection triangles = new Int32Collection(new int[] {// 底面 (Z-)0, 1, 2, 2, 3, 0,// 頂面 (Z+)4, 5, 6, 6, 7, 4,// 前面 (Y+)3, 2, 6, 6, 7, 3,// 后面 (Y-)0, 1, 5, 5, 4, 0,// 右面 (X+)1, 2, 6, 6, 5, 1,// 左面 (X-)4, 7, 3, 3, 0, 4});MeshGeometry3D mesh = new MeshGeometry3D();mesh.Positions = points;mesh.TriangleIndices = triangles;mesh.Normals = CalculateNormals(mesh); // 計算法線mesh.Freeze();return new GeometryModel3D{Geometry = mesh,Material = diffuseMaterial,BackMaterial = diffuseMaterial};}private static Vector3DCollection CalculateNormals(MeshGeometry3D mesh){var normals = new Vector3DCollection();for (int i = 0; i < mesh.TriangleIndices.Count; i += 3){int i1 = mesh.TriangleIndices[i];int i2 = mesh.TriangleIndices[i + 1];int i3 = mesh.TriangleIndices[i + 2];Point3D p1 = mesh.Positions[i1];Point3D p2 = mesh.Positions[i2];Point3D p3 = mesh.Positions[i3];Vector3D v1 = p2 - p1;Vector3D v2 = p3 - p1;Vector3D normal = Vector3D.CrossProduct(v1, v2);normal.Normalize();normals.Add(normal);normals.Add(normal);normals.Add(normal);}return normals;}}
}
🖱? 第三步:實現交互操作
在 MainWindow.xaml.cs
中實現鼠標控制相機旋轉、平移和縮放功能。
🧩 主要邏輯(MainWindow.xaml.cs)
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;namespace _3D_WPF_Demo
{public partial class MainWindow : Window{private bool isDragging = false;private Point lastMousePosition;private double cameraDistance = 300;private Point3D centerPoint = new Point3D(0, 0, 0);private bool isPanning = false;private Point lastPanMousePosition;private Vector3D panOffset = new Vector3D(0, 0, 0);private ModelVisual3D modelVisual;private TranslateTransform3D modelTranslation = new TranslateTransform3D();private double yaw = 0;private double pitch = 0;public MainWindow(){InitializeComponent();viewPort.LostMouseCapture += (s, e) =>{isDragging = false;isPanning = false;Mouse.Capture(null);};LoadModel();UpdateCamera();}private void LoadModel(){var material = new DiffuseMaterial(new SolidColorBrush(Colors.Orange));GeometryModel3D model = _3DModelOperateHelper.CreateSingleColorCube(10, 10, 10, material);modelVisual = new ModelVisual3D();modelVisual.Content = model;modelVisual.Transform = modelTranslation;viewPort.Children.Add(modelVisual);if (model.Geometry is MeshGeometry3D mesh){var normalGroup = new Model3DGroup();AddNormalLines(mesh, normalGroup);var normalVisual = new ModelVisual3D { Content = normalGroup };viewPort.Children.Add(normalVisual);}}private void AddNormalLines(MeshGeometry3D mesh, Model3DGroup group){if (mesh.Positions == null || mesh.TriangleIndices == null || mesh.Normals == null)return;int indexCount = mesh.TriangleIndices.Count;for (int i = 0; i < indexCount; i += 3){int i1 = mesh.TriangleIndices[i];int i2 = mesh.TriangleIndices[i + 1];int i3 = mesh.TriangleIndices[i + 2];Point3D p1 = mesh.Positions[i1];Point3D p2 = mesh.Positions[i2];Point3D p3 = mesh.Positions[i3];Point3D center = new Point3D((p1.X + p2.X + p3.X) / 3,(p1.Y + p2.Y + p3.Y) / 3,(p1.Z + p2.Z + p3.Z) / 3);Vector3D normal = mesh.Normals[i / 3];Point3D endPoint = center + normal * 5;var lineMesh = new MeshGeometry3D();lineMesh.Positions.Add(center);lineMesh.Positions.Add(endPoint);lineMesh.TriangleIndices.Add(0);lineMesh.TriangleIndices.Add(1);lineMesh.TriangleIndices.Add(1);var material = new DiffuseMaterial(Brushes.Magenta);var model = new GeometryModel3D(lineMesh, material);group.Children.Add(model);}}private void ViewPort_MouseLeftButtonDown(object sender, MouseButtonEventArgs e){isDragging = true;lastMousePosition = e.GetPosition(viewPort);Mouse.Capture(viewPort);}private void ViewPort_MouseLeftButtonUp(object sender, MouseButtonEventArgs e){isDragging = false;Mouse.Capture(null);}private void ViewPort_MouseMove(object sender, MouseEventArgs e){var current = e.GetPosition(viewPort);if (isDragging){Vector delta = current - lastMousePosition;yaw += delta.X * 0.3;pitch -= delta.Y * 0.3;UpdateCamera();lastMousePosition = current;}else if (isPanning){UpdateCamera();lastPanMousePosition = current;}}private void ViewPort_PreviewMouseWheel(object sender, MouseWheelEventArgs e){double zoomFactor = e.Delta > 0 ? 1.1 : 0.9;cameraDistance *= zoomFactor;cameraDistance = Math.Max(50, Math.Min(cameraDistance, 1000));UpdateCamera();}private void ViewPort_MouseDown(object sender, MouseButtonEventArgs e){if (e.ChangedButton == MouseButton.Middle){isPanning = true;lastPanMousePosition = e.GetPosition(viewPort);Mouse.Capture(viewPort);e.Handled = true;}}private void ViewPort_MouseUp(object sender, MouseButtonEventArgs e){if (e.ChangedButton == MouseButton.Left){isDragging = false;Mouse.Capture(null);}if (e.ChangedButton == MouseButton.Middle){isPanning = false;Mouse.Capture(null);e.Handled = true;}}private void UpdateCamera(){Vector3D initialLookDirection = new Vector3D(0, 0, -1);Matrix3D rotationMatrix = new Matrix3D();rotationMatrix.Rotate(new Quaternion(new Vector3D(0, 1, 0), yaw));rotationMatrix.Rotate(new Quaternion(new Vector3D(1, 0, 0), pitch));Vector3D rotatedLookDirection = rotationMatrix.Transform(initialLookDirection);mainCamera.Position = new Point3D(centerPoint.X - rotatedLookDirection.X * cameraDistance,centerPoint.Y - rotatedLookDirection.Y * cameraDistance,centerPoint.Z - rotatedLookDirection.Z * cameraDistance);mainCamera.LookDirection = rotatedLookDirection;mainCamera.UpDirection = new Vector3D(0, 1, 0);Vector3D forward = rotatedLookDirection;forward.Normalize();Vector3D right = Vector3D.CrossProduct(new Vector3D(0, 1, 0), forward);if (right.Length == 0) right = new Vector3D(1, 0, 0);right.Normalize();Vector3D up = Vector3D.CrossProduct(forward, right);up.Normalize();if (isPanning){double panSpeed = 0.1;var delta = lastPanMousePosition - Mouse.GetPosition(viewPort);panOffset += right * (panSpeed * delta.X);panOffset += up * (panSpeed * delta.Y);modelTranslation.OffsetX = panOffset.X;modelTranslation.OffsetY = panOffset.Y;modelTranslation.OffsetZ = panOffset.Z;lastPanMousePosition = Mouse.GetPosition(viewPort);}}}
}
🎨 功能總結
功能 | 描述 |
---|---|
🧱 立方體建模 | 手動定義頂點與三角形索引 |
🔆 光源設置 | 環境光 + 方向光增強視覺效果 |
🖱? 鼠標交互 | 支持左鍵旋轉、中鍵平移、滾輪縮放 |
🧭 相機控制 | 使用四元數進行相機旋轉,避免萬向節死鎖 |
📐 法線可視化 | 繪制每個面的法線方向,便于理解光照原理 |
📚 小結與展望
本項目是一個非常完整的 WPF 3D 入門示例,涵蓋了從模型創建到交互控制的核心知識點。你可以在此基礎上繼續擴展:
- 加載
.obj
或.fbx
模型文件 - 添加 UI 控件切換顯示/隱藏法線
- 使用 Helix Toolkit 實現更高級的功能
- 實現動畫、碰撞檢測、粒子系統等進階功能
📢 結語
如果你是 WPF 初學者或想了解 3D 編程,這個項目是非常好的起點。它不僅展示了如何使用 Viewport3D
和 Media3D
,還幫助你掌握三維空間中的基本變換和交互技巧。
📌 下期預告:《WPF 3D 進階篇:使用 Helix Toolkit 快速開發 3D 應用》敬請期待!
如需下載項目源碼,關注群名片,我會提供壓縮包鏈接。歡迎點贊、收藏、轉發分享給更多學習者!👏