65.9K
CodeProject 正在变化。 阅读更多。
Home

在 XAML 中生成球体网格

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.57/5 (35投票s)

2006 年 6 月 11 日

2分钟阅读

viewsIcon

152837

downloadIcon

5159

本文展示了一种在 C# 和 XAML 中创建 3D 球体的方法。

Sample Image - XamlUVSphere.jpg

引言

几天前,我突然想尝试 WPF 的新 3D 功能。在那之前,我只用 XAML 看过 2D 图形和动画。 当然,我的起点是 MSDN。 您可以在 MSDN 找到关于 XAML 的 3D 图形的一个很好的介绍。 还有另一篇 CodeProject 上的文章:XAML 中的 3D,它能让你很好地入门 XAML 中的 3D。 在我的文章中,我不会涉及相机、网格、灯光等基础知识。

当我在 MSDN 中读到 WPF “目前不支持预定义的 3D 图元,如球体和立方体形式”时,我很惊讶。 它为您提供了 MeshGeometry3D 类,允许您将任何几何体构建为三角形列表。 因此,我决定我在 WPF 中的第一个 3D 小型项目将是一个生成表示球体的网格的算法。

不幸的是,我不是编写 3D 图形代码的专家。 因此,我决定实现一个相当简单的算法,该算法从三角形网格生成一个球体:我做的很像开源 3D 建模器 Blender 使用其 UVSphere 网格所做的事情

Blender 3D 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 的属性 PositionsTriangleIndices 绑定到上面显示的算法

<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 上为我的文章投票,我将很高兴。 如果您有任何疑问,请随时给我发送电子邮件。

© . All rights reserved.