源项目地址:https://github.com/Microsoft/...
以下是把示例转换为简要说明,同时给出实际运行效果及关键代码剖析:

ParticlesDemo粒子群3D动画转圈示例

clipboard.png

  • 文件组织

Particle.cs粒子类
ParticleSystem.cs粒子系统类
ParticleSystemManager.cs粒子系统管理类
WPF中MainWindow.xaml及.cs文件等

  • 实现功能

每秒出现各种颜色的3D粒子顺时针转圈运动
粒子随时间而扩散、消失
显示帧数及总粒子束

  • 主要代码

粒子类

public class Particle
{
    public double Decay;//消散系数
    public double Life; //存在时长
    public Point3D Position;//3D位置
    public double Size;//尺寸
    public double StartLife;//开始时长
    public double StartSize;//开始尺寸
    public Vector3D Velocity;//3D位移数
}

粒子3D模型初始化

_particleModel = new GeometryModel3D {Geometry = new MeshGeometry3D()};

var e = new Ellipse
{
    Width = 32.0,
    Height = 32.0
};
var b = new RadialGradientBrush();
b.GradientStops.Add(new GradientStop(Color.FromArgb(0xFF, color.R, color.G, color.B), 0.15));
b.GradientStops.Add(new GradientStop(Colors.White, 0.3));
b.GradientStops.Add(new GradientStop(Color.FromArgb(0x00, color.R, color.G, color.B), 1.0));
e.Fill = b;
e.Measure(new Size(32, 32));
e.Arrange(new Rect(0, 0, 32, 32));

粒子材质

Brush brush = null;

#if USE_VISUALBRUSH
            brush = new VisualBrush(e);
#else
            var renderTarget = new RenderTargetBitmap(32, 32, 96, 96, PixelFormats.Pbgra32);
            renderTarget.Render(e);
            renderTarget.Freeze();
            brush = new ImageBrush(renderTarget);
#endif

            var material = new DiffuseMaterial(brush);

            _particleModel.Material = material;

            _rand = new Random(brush.GetHashCode());
  • 后台逻辑

在加载窗口初始化
由DispatcherTimer计时器驱动,绑定事件:每1/60s执行一帧动作

 var frameTimer = new DispatcherTimer();
frameTimer.Tick += OnFrame;
frameTimer.Interval = TimeSpan.FromSeconds(1.0/60.0);
frameTimer.Start();

同时三维模型集Model3DGroup添加5种不同颜色例子系统。

_spawnPoint = new Point3D(0.0, 0.0, 0.0);
_lastTick = Environment.TickCount;

_pm = new ParticleSystemManager();

WorldModels.Children.Add(_pm.CreateParticleSystem(1000, Colors.Gray));
WorldModels.Children.Add(_pm.CreateParticleSystem(1000, Colors.Red));
WorldModels.Children.Add(_pm.CreateParticleSystem(1000, Colors.Silver));
WorldModels.Children.Add(_pm.CreateParticleSystem(1000, Colors.Orange));
WorldModels.Children.Add(_pm.CreateParticleSystem(1000, Colors.Yellow));

每帧的动作方法:
记录每帧间的毫秒时长,作为粒子移动位置、大小变化、消散时长的重要参考。

private void OnFrame(object sender, EventArgs e)
{
    // Calculate frame time;
    _currentTick = Environment.TickCount;
    _elapsed = (_currentTick - _lastTick)/1000.0;
    _totalElapsed += _elapsed;
    _lastTick = _currentTick;

每秒显示帧数及总粒子数
每帧_frameCount时长自增,在累计毫秒到1s时,重新计数。

_frameCount++;
_frameCountTime += _elapsed;
if (_frameCountTime >= 1.0)
{
    _frameCountTime -= 1.0;
    _frameRate = _frameCount;
    _frameCount = 0;
    FrameRateLabel.Content = "FPS: " + _frameRate + "  Particles: " + _pm.ActiveParticleCount;
}

同时,帧间根据消散时间参数来更新微粒系统中的所有粒子

_pm.Update((float) _elapsed);

具体方法如下:
在ParticleSystem.cs中
新建一个死亡列表,捕获消散时间到0的粒子,在总列表中再移除标志的死亡例子。ps:foreach中若删除其中元素会报异常。是否通过for循环中删除死亡粒子可提供性能?

public void Update(double elapsed)
{
    var deadList = new List<Particle>();

    // Update all particles
    foreach (var p in _particleList)
    {
        p.Position += p.Velocity*elapsed;
        p.Life -= p.Decay*elapsed;
        p.Size = p.StartSize*(p.Life/p.StartLife);
        if (p.Life <= 0.0)
            deadList.Add(p);
    }

    foreach (var p in deadList)
        _particleList.Remove(p);

    UpdateGeometry();
}

更新粒子的几何图形数据:
重新设定三维三角基元的位置信息,如位置、三角形索引的集合、纹理坐标集合。其中根据之前设置的动态尺寸p.Size = p.StartSize*p.Life/p.StartLife)参数来改变例子大小。
若不指定TriangleIndices三角形索引的集合,则出现以下效果

clipboard.png

private void UpdateGeometry()
{
    var positions = new Point3DCollection();
    var indices = new Int32Collection();
    var texcoords = new PointCollection();

    for (var i = 0; i < _particleList.Count; ++i)
    {
        var positionIndex = i*4;
        var indexIndex = i*6;
        var p = _particleList[i];

        var p1 = new Point3D(p.Position.X, p.Position.Y, p.Position.Z);
        var p2 = new Point3D(p.Position.X, p.Position.Y + p.Size, p.Position.Z);
        var p3 = new Point3D(p.Position.X + p.Size, p.Position.Y + p.Size, p.Position.Z);
        var p4 = new Point3D(p.Position.X + p.Size, p.Position.Y, p.Position.Z);

        positions.Add(p1);
        positions.Add(p2);
        positions.Add(p3);
        positions.Add(p4);

        var t1 = new Point(0.0, 0.0);
        var t2 = new Point(0.0, 1.0);
        var t3 = new Point(1.0, 1.0);
        var t4 = new Point(1.0, 0.0);

        texcoords.Add(t1);
        texcoords.Add(t2);
        texcoords.Add(t3);
        texcoords.Add(t4);

        indices.Add(positionIndex);
        indices.Add(positionIndex + 2);
        indices.Add(positionIndex + 1);
        indices.Add(positionIndex);
        indices.Add(positionIndex + 3);
        indices.Add(positionIndex + 2);
    }

    ((MeshGeometry3D) _particleModel.Geometry).Positions = positions;
    ((MeshGeometry3D) _particleModel.Geometry).TriangleIndices = indices;
    ((MeshGeometry3D) _particleModel.Geometry).TextureCoordinates = texcoords;
}

回到帧方法中,每帧新建12个拥有初始3D位置、漫射角度系数、颜色、尺寸、存在时长参数 的粒子。
利用每帧间隔时长参数返回指定角度的正弦、余弦轨迹偏移度,设置粒子初始位置_spawnPoint,来形成转圈的轨迹。

_pm.Update((float) _elapsed);
_pm.SpawnParticle(_spawnPoint, 10.0, Colors.Red, _rand.NextDouble(), 5*_rand.NextDouble());
_pm.SpawnParticle(_spawnPoint, 10.0, Colors.Orange, _rand.NextDouble(), 2.5*_rand.NextDouble());
_pm.SpawnParticle(_spawnPoint, 10.0, Colors.Silver, _rand.NextDouble(), 2.5*_rand.NextDouble());
_pm.SpawnParticle(_spawnPoint, 10.0, Colors.Gray, _rand.NextDouble(), 2.5*_rand.NextDouble());
_pm.SpawnParticle(_spawnPoint, 10.0, Colors.Red, _rand.NextDouble(), 2.5*_rand.NextDouble());
_pm.SpawnParticle(_spawnPoint, 10.0, Colors.Orange, _rand.NextDouble(), 2.5*_rand.NextDouble());
_pm.SpawnParticle(_spawnPoint, 10.0, Colors.Silver, _rand.NextDouble(), 2.5*_rand.NextDouble());
_pm.SpawnParticle(_spawnPoint, 10.0, Colors.Gray, _rand.NextDouble(), 2.5*_rand.NextDouble());
_pm.SpawnParticle(_spawnPoint, 10.0, Colors.Red, _rand.NextDouble(), 2.5*_rand.NextDouble());
_pm.SpawnParticle(_spawnPoint, 10.0, Colors.Yellow, _rand.NextDouble(), 2.5*_rand.NextDouble());
_pm.SpawnParticle(_spawnPoint, 10.0, Colors.Silver, _rand.NextDouble(), 2.5*_rand.NextDouble());
_pm.SpawnParticle(_spawnPoint, 10.0, Colors.Yellow, _rand.NextDouble(), 2.5*_rand.NextDouble());

var c = Math.Cos(_totalElapsed);
var s = Math.Sin(_totalElapsed);

_spawnPoint = new Point3D(s*32.0, c*32.0, 0.0);

ParticleSystem中新建粒子方法
其中在初始位置点再随机变动粒子位置,形成漫射粒子效果

public void SpawnParticle(Point3D position, double speed, double size, double life)
{
    if (_particleList.Count > MaxParticleCount)
        return;
    var p = new Particle
    {
        Position = position,
        StartLife = life,
        Life = life,
        StartSize = size*2,
        Size = size*10
    };

    var x = 1.0f - (float) _rand.NextDouble()*2.0f;
    var z = 1.0f - (float) _rand.NextDouble()*2.0f;

    var v = new Vector3D(x, z, 0.0);
    v.Normalize();
    v *= ((float) _rand.NextDouble() + 0.25f)*(float) speed;

    p.Velocity = new Vector3D(v.X, v.Y, v.Z);

    p.Decay = 1.0f; // 0.5 + rand.NextDouble();
    //if (p.Decay > 1.0)
    //    p.Decay = 1.0;

    _particleList.Add(p);
}

以上。。
另:根据源代码修改参数,获得效果如下:

clipboard.png


李志玮
22 声望34 粉丝

求索~~