在 XAML 中生成球体网格






4.57/5 (35投票s)
2006 年 6 月 11 日
2分钟阅读

152837

5159
本文展示了一种在 C# 和 XAML 中创建 3D 球体的方法。
引言
几天前,我突然想尝试 WPF 的新 3D 功能。在那之前,我只用 XAML 看过 2D 图形和动画。 当然,我的起点是 MSDN。 您可以在 MSDN 找到关于 XAML 的 3D 图形的一个很好的介绍。 还有另一篇 CodeProject 上的文章:XAML 中的 3D,它能让你很好地入门 XAML 中的 3D。 在我的文章中,我不会涉及相机、网格、灯光等基础知识。
当我在 MSDN 中读到 WPF “目前不支持预定义的 3D 图元,如球体和立方体形式”时,我很惊讶。 它为您提供了 MeshGeometry3D
类,允许您将任何几何体构建为三角形列表。 因此,我决定我在 WPF 中的第一个 3D 小型项目将是一个生成表示球体的网格的算法。
不幸的是,我不是编写 3D 图形代码的专家。 因此,我决定实现一个相当简单的算法,该算法从三角形网格生成一个球体:我做的很像开源 3D 建模器 Blender 使用其 UVSphere 网格所做的事情
(来源:Wiki:Grundkörper)
正如您从上图中所看到的,我将球体分成段和环。 结果是一个正方形列表(可以很容易地分成两个三角形),以及顶部和底部的三角形。 Blender 的 Icosphere(请参阅 Wiki:Ikosaeder (德语) 了解更多详情)更适合 XAML 网格。 但是,我决定从 UVSphere 开始。
球体不是唯一可以通过将圆分割成段来生成的圆形网格。 因此,我决定编写一个抽象基类,该基类也可以用于例如圆盘(3D 空间中的圆)
using System;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace Sphere3D
{
abstract class RoundMesh3D
{
protected int n = 10;
protected int r = 20;
protected Point3DCollection points;
protected Int32Collection triangleIndices;
public virtual int Radius
{
get { return r; }
set { r = value; CalculateGeometry(); }
}
public virtual int Separators
{
get { return n; }
set { n = value; CalculateGeometry(); }
}
public Point3DCollection Points
{
get { return points; }
}
public Int32Collection TriangleIndices
{
get { return triangleIndices; }
}
protected abstract void CalculateGeometry();
}
}
r
代表网格的半径,n
代表我将圆分割成的段数(4*n+4 是我均匀分布在圆上的点数)。
我的第一个测试是圆盘的实现。 这是代码。 它不是很复杂,只是一些三角函数
using System;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Diagnostics;
namespace Sphere3D
{
class DiscGeometry3D : RoundMesh3D
{
protected override void CalculateGeometry()
{
int numberOfSeparators = 4 * n + 4;
points = new Point3DCollection(numberOfSeparators + 1);
triangleIndices = new Int32Collection((numberOfSeparators + 1) * 3);
points.Add(new Point3D(0, 0, 0));
for (int divider = 0; divider < numberOfSeparators; divider++)
{
double alpha = Math.PI / 2 / (n + 1) * divider;
points.Add(new Point3D(r * Math.Cos(alpha),
0, -1 * r * Math.Sin(alpha)));
triangleIndices.Add(0);
triangleIndices.Add(divider + 1);
triangleIndices.Add((divider ==
(numberOfSeparators-1)) ? 1 : (divider + 2));
}
}
public DiscGeometry3D()
{ }
}
}
生成球体的代码有点长。 在球体上分布点很简单。 我发现正确生成三角形更难
using System;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Diagnostics;
namespace Sphere3D
{
class SphereGeometry3D : RoundMesh3D
{
protected override void CalculateGeometry()
{
int e;
double segmentRad = Math.PI / 2 / (n + 1);
int numberOfSeparators = 4 * n + 4;
points = new Point3DCollection();
triangleIndices = new Int32Collection();
for (e = -n; e <= n; e++)
{
double r_e = r * Math.Cos(segmentRad * e);
double y_e = r * Math.Sin(segmentRad * e);
for (int s = 0; s <= (numberOfSeparators - 1); s++)
{
double z_s = r_e * Math.Sin(segmentRad * s) * (-1);
double x_s = r_e * Math.Cos(segmentRad * s);
points.Add(new Point3D(x_s, y_e, z_s));
}
}
points.Add(new Point3D(0, r, 0));
points.Add(new Point3D(0, -1 * r, 0));
for (e = 0; e < 2 * n; e++)
{
for (int i = 0; i < numberOfSeparators; i++)
{
triangleIndices.Add(e * numberOfSeparators + i);
triangleIndices.Add(e * numberOfSeparators + i +
numberOfSeparators);
triangleIndices.Add(e * numberOfSeparators + (i + 1) %
numberOfSeparators + numberOfSeparators);
triangleIndices.Add(e * numberOfSeparators + (i + 1) %
numberOfSeparators + numberOfSeparators);
triangleIndices.Add(e * numberOfSeparators +
(i + 1) % numberOfSeparators);
triangleIndices.Add(e * numberOfSeparators + i);
}
}
for (int i = 0; i < numberOfSeparators; i++)
{
triangleIndices.Add(e * numberOfSeparators + i);
triangleIndices.Add(e * numberOfSeparators + (i + 1) %
numberOfSeparators);
triangleIndices.Add(numberOfSeparators * (2 * n + 1));
}
for (int i = 0; i < numberOfSeparators; i++)
{
triangleIndices.Add(i);
triangleIndices.Add((i + 1) % numberOfSeparators);
triangleIndices.Add(numberOfSeparators * (2 * n + 1) + 1);
}
}
public SphereGeometry3D()
{ }
}
}
对于我的示例,我想在背景中显示两个球体和一张漂亮的图片(请参阅文章顶部的图片)。 因此,我决定从 SphereGeometry3D
创建两个子类
namespace Sphere3D
{
class BigPlanet : SphereGeometry3D
{
BigPlanet()
{
Radius = 30;
Separators = 5;
}
}
class SmallPlanet : SphereGeometry3D
{
SmallPlanet()
{
Radius = 5;
Separators = 5;
}
}
}
最后,我使用 XAML 的数据绑定机制将 MeshGeometry3D
的属性 Positions
和 TriangleIndices
绑定到上面显示的算法
<Window x:Class="Sphere3D.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Sphere3D"
Title="Labyrinth3d" Height="600" Width="600"
>
<Window.Background>
<ImageBrush Stretch="UniformToFill"
ImageSource="Images/Pleiades.jpg"/>
</Window.Background>
<Grid VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" x:Name="Grid1">
<Grid.Resources>
<local:BigPlanet x:Key="SphereGeometrySource1"/>
<local:SmallPlanet x:Key="SphereGeometrySource2"/>
<MeshGeometry3D x:Key="SphereGeometry1"
Positions="{Binding Source={StaticResource
SphereGeometrySource1}, Path=Points}"
TriangleIndices="{Binding Source={StaticResource
SphereGeometrySource1},
Path=TriangleIndices}"/>
<MeshGeometry3D x:Key="SphereGeometry2"
Positions="{Binding Source={StaticResource
SphereGeometrySource2}, Path=Points}"
TriangleIndices="{Binding Source={StaticResource
SphereGeometrySource2},
Path=TriangleIndices}"/>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Viewport3D Grid.Column="1" Grid.Row="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" Name="Viewport1">
<Viewport3D.Camera>
<PerspectiveCamera x:Name="myCamera" Position="100 30 0"
LookDirection="-50 -33 0"
UpDirection="0,1,0" FieldOfView="90"/>
<!--<OrthographicCamera x:Name="myCamera"
Position="200 0 0" LookDirection="-1 0 0"
Width="180" UpDirection="0,1,0"/>-->
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<DirectionalLight Color="#FFFFFF"
Direction="0 -30 0" />
<DirectionalLight Color="#FFFFFF"
Direction="0 +30 0" />
<GeometryModel3D
Geometry="{StaticResource SphereGeometry1}">
<GeometryModel3D.Material>
<MaterialGroup>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<SolidColorBrush Color="Orange"/>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</MaterialGroup>
</GeometryModel3D.Material>
</GeometryModel3D>
<GeometryModel3D
Geometry="{StaticResource SphereGeometry2}">
<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<SolidColorBrush Color="Yellow"/>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
<GeometryModel3D.Transform>
<TranslateTransform3D
x:Name="Sphere2Translation" OffsetZ="50" />
</GeometryModel3D.Transform>
</GeometryModel3D>
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</Grid>
</Window>
如果我为 XAML 实现的 3D 球体对您有帮助,如果您可以在 CodeProject 上为我的文章投票,我将很高兴。 如果您有任何疑问,请随时给我发送电子邮件。