一、坐标系
- WPF 的 3D 坐标系原点通常位于对象中心,X 轴向右,Y 轴向上,Z 轴朝向观察者(右手定则)。
二、甜甜圈的几何生成
甜甜圈 ,即圆环体(Torus)的几何生成:
- 参数方程:
R:主半径(中心到管道中心线的距离)
r:管道半径(截面圆的半径)
表面点坐标:
x = (R + r·cosφ)·cosθ
y = (R + r·cosφ)·sinθ
z = r·sinφ
θ 绕 Z 轴(大圆),φ 绕管道中心(小圆)。
-
法线:
精确法线为 (cosφ·cosθ, cosφ·sinθ, sinφ),与表面垂直,保证光照正确。
-
网格化:
uSegments 和 vSegments 分别控制两个方向的分辨率,生成四边形网格,每个四边形由两个三角形组成,形成完整的三角网格。
三、交互控制
-
视角控制:
PerspectiveCamera 的 FieldOfView 绑定到焦距滑块,实现视野缩放。
-
旋转控制:
三个滑块分别绑定到 X、Y、Z 轴的 AxisAngleRotation3D,通过 ElementName 绑定实现双向同步。
-
自动旋转:
使用 DispatcherTimer 定时递增 rotateY.Value,触发 UI 更新,无需手动操作变换矩阵。
四、 光照与材质
-
使用环境光 + 两个方向光提供柔和立体感。
-
前后材质分别设为橙色和深橙色,便于观察背面。
五、 相机位置
- 相机置于 (20, 10, 30),看向原点,适合显示半径 2 的圆环体。

六、MainWindow.xaml
<Window x:Class="WpfA_Torus.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:WpfA_Torus"
mc:Ignorable="d"
Title="甜甜圈 (Torus)" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="250"/>
</Grid.ColumnDefinitions>
<!-- 右侧控制面板 -->
<StackPanel Grid.Column="1" Margin="10">
<TextBlock Text="调节相机焦距" Margin="0,0,0,5"/>
<Slider x:Name="viewAngle" Width="200" Value="45" Minimum="5" Maximum="120" />
<TextBox Text="{Binding ElementName=viewAngle, Path=Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="80" Margin="0,5,0,10" HorizontalAlignment="Left"/>
<TextBlock Text="X轴旋转" Margin="0,0,0,5"/>
<Slider x:Name="rotateX" Width="200" Value="0" Minimum="0" Maximum="360" />
<TextBox Text="{Binding ElementName=rotateX, Path=Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="80" Margin="0,5,0,10" HorizontalAlignment="Left"/>
<TextBlock Text="Y轴旋转" Margin="0,0,0,5"/>
<Slider x:Name="rotateY" Width="200" Value="0" Minimum="0" Maximum="360" />
<TextBox Text="{Binding ElementName=rotateY, Path=Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="80" Margin="0,5,0,10" HorizontalAlignment="Left"/>
<TextBlock Text="Z轴旋转" Margin="0,0,0,5"/>
<Slider x:Name="rotateZ" Width="200" Value="0" Minimum="0" Maximum="360" />
<TextBox Text="{Binding ElementName=rotateZ, Path=Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="80" Margin="0,5,0,10" HorizontalAlignment="Left"/>
<Button x:Name="btnAutoRotate" Content="启动自动旋转" Width="120" Margin="0,20,0,5" Click="BtnAutoRotate_Click"/>
<Button x:Name="btnExit" Content="退出程序" Width="120" Click="BtnExit_Click"/>
</StackPanel>
<Viewport3D Grid.Column="0">
<ModelVisual3D x:Name="modelVisual">
<ModelVisual3D.Content>
<Model3DGroup>
<!-- 甜甜圈模型,几何体在后台生成 -->
<GeometryModel3D x:Name="TorusModel">
<GeometryModel3D.Material>
<DiffuseMaterial Brush="Orange" />
</GeometryModel3D.Material>
<GeometryModel3D.BackMaterial>
<DiffuseMaterial Brush="DarkOrange" />
</GeometryModel3D.BackMaterial>
</GeometryModel3D>
<!-- 光源 -->
<AmbientLight Color="#404040" />
<DirectionalLight Color="White" Direction="1, -3, -2" />
<DirectionalLight Color="Gray" Direction="-1, 2, 3" />
</Model3DGroup>
</ModelVisual3D.Content>
<ModelVisual3D.Transform>
<Transform3DGroup>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="1,0,0" Angle="{Binding Value, ElementName=rotateX}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0,1,0" Angle="{Binding Value, ElementName=rotateY}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0,0,1" Angle="{Binding Value, ElementName=rotateZ}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
</Transform3DGroup>
</ModelVisual3D.Transform>
</ModelVisual3D>
<Viewport3D.Camera>
<PerspectiveCamera Position="20,10,30" LookDirection="-20,-10,-30"
FieldOfView="{Binding Value, ElementName=viewAngle}"
UpDirection="0,1,0"/>
</Viewport3D.Camera>
</Viewport3D>
</Grid>
</Window>
七、MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Threading;
namespace WpfA_Torus
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private DispatcherTimer autoRotateTimer;
private bool isAutoRotating = false;
public MainWindow()
{
InitializeComponent();
// 生成甜甜圈:主半径2.0,管半径0.8,大圆分段60,小圆分段30
GenerateTorus(2.0, 0.8, 60, 30);
}
/// <summary>
/// 生成圆环体网格数据
/// </summary>
/// <param name="R">主半径(中心到管道中心)</param>
/// <param name="r">管道半径</param>
/// <param name="uSegments">大圆周分段数</param>
/// <param name="vSegments">截面圆周分段数</param>
private void GenerateTorus(double R, double r, int uSegments, int vSegments)
{
int numVertices = (uSegments + 1) * (vSegments + 1);
Point3D[] positions = new Point3D[numVertices];
Vector3D[] normals = new Vector3D[numVertices];
Point[] textureCoords = new Point[numVertices];
int index = 0;
for (int u = 0; u <= uSegments; u++)
{
double theta = u * 2 * Math.PI / uSegments; // 大圆角度
for (int v = 0; v <= vSegments; v++)
{
double phi = v * 2 * Math.PI / vSegments; // 小圆角度
// 圆环面参数方程
double x = (R + r * Math.Cos(phi)) * Math.Cos(theta);
double y = (R + r * Math.Cos(phi)) * Math.Sin(theta);
double z = r * Math.Sin(phi);
positions[index] = new Point3D(x, y, z);
// 法线方向(单位向量)
double nx = Math.Cos(phi) * Math.Cos(theta);
double ny = Math.Cos(phi) * Math.Sin(theta);
double nz = Math.Sin(phi);
normals[index] = new Vector3D(nx, ny, nz);
// 纹理坐标(用于贴图,此处仅作展示)
textureCoords[index] = new Point((double)u / uSegments, (double)v / vSegments);
index++;
}
}
// 生成三角形索引(每个四边形分为两个三角形)
int numTriangles = uSegments * vSegments * 2;
int[] triangleIndices = new int[numTriangles * 3];
int triIndex = 0;
for (int u = 0; u < uSegments; u++)
{
for (int v = 0; v < vSegments; v++)
{
int p0 = u * (vSegments + 1) + v;
int p1 = p0 + 1;
int p2 = (u + 1) * (vSegments + 1) + v;
int p3 = p2 + 1;
// 三角形1
triangleIndices[triIndex++] = p0;
triangleIndices[triIndex++] = p1;
triangleIndices[triIndex++] = p2;
// 三角形2
triangleIndices[triIndex++] = p1;
triangleIndices[triIndex++] = p3;
triangleIndices[triIndex++] = p2;
}
}
// 构建MeshGeometry3D
MeshGeometry3D mesh = new MeshGeometry3D();
mesh.Positions = new Point3DCollection(positions);
mesh.Normals = new Vector3DCollection(normals);
mesh.TextureCoordinates = new PointCollection(textureCoords);
mesh.TriangleIndices = new Int32Collection(triangleIndices);
// 赋给模型
TorusModel.Geometry = mesh;
}
// 自动旋转按钮事件
private void BtnAutoRotate_Click(object sender, RoutedEventArgs e)
{
if (!isAutoRotating)
{
autoRotateTimer = new DispatcherTimer();
autoRotateTimer.Interval = TimeSpan.FromMilliseconds(30);
autoRotateTimer.Tick += (s, args) =>
{
// 沿X,Y,Z轴每次增加1度
rotateX.Value = (rotateX.Value + 1) % 360;
rotateY.Value = (rotateY.Value + 1) % 360;
rotateZ.Value = (rotateZ.Value + 1) % 360;
};
autoRotateTimer.Start();
isAutoRotating = true;
btnAutoRotate.Content = "停止自动旋转";
}
else
{
autoRotateTimer?.Stop();
isAutoRotating = false;
btnAutoRotate.Content = "启动自动旋转";
}
}
// 退出按钮事件
private void BtnExit_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
}