Список работ

OpenTK: вывод частиц

Содержание

Введение

Создается C#-приложение, в котором средствами OpenTK генерируются частицы (рис. 1).

Частицы OpenTK

Рис. 1. Все частицы рождаются в одной точке

Тип проекта - Console Application.
Приводимый код основан на примере, имеющемся в поставке OpenTK.
Частицы - это вершины OpenGL, для хранения свойств которых используется VBO - Vertex Buffer Object - объект, содержащий свойства вершин.

VertexC4ubV3f[] VBO = new VertexC4ubV3f[MaxParticleCount];

Свойства Direction и Age (направление и возраст) частиц хранит объект ParticleAttributes

ParticleAttribut[] ParticleAttributes = new ParticleAttribut[MaxParticleCount];

VertexC4ubV3f и ParticleAttribut - это заданные в приводимой ниже программе структуры.
В программе для частиц заданы свойства Direction и Age (направление движения и возраст). Реально, однако на состояние сцены влияет только значение свойства Direction.
Все частицы рождаются в одной точке, имеют случайно заданный цвет и летят в случайно сгенерированных направлениях.

Реализация

using System;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input; // Для Key.Escape

namespace Particles
{
    // VBO - Vertex Buffer Object. Средство для работы с массивами, содержащими свойства вершин
    class T09_VBO_Dynamic : GameWindow
    {
        uint VBOHandle;
        static int MaxParticleCount = 500;
        int VisibleParticleCount;
        // Создаем VBO
        VertexC4ubV3f[] VBO = new VertexC4ubV3f[MaxParticleCount];
        // Структура свойств частиц (Direction и Age)
        ParticleAttribut[] ParticleAttributes = new ParticleAttribut[MaxParticleCount];
        // Структура, употребляемая при выводе вершин
        struct VertexC4ubV3f
        {
            public byte R, G, B, A;
            public Vector3 Position;
            public static int SizeInBytes = 16;
        }
        // Структура, употребляемая для обновления сцены
        struct ParticleAttribut
        {
            public Vector3 Direction;
            public uint Age;
            // Далее можно задать Rotation, Radius и прочие свойства частиц
        }
        //
        // Создает окно 800x600 с указанным заголовком
        public T09_VBO_Dynamic() : base(400, 300)
        {
            // Вертикальная синхронизация - синхронизация кадровой частоты с частотой вертикальной развёртки монитора
            // При этом максимальное значение FPS с вертикальной синхронизацией приравнивается к частоте обновления монитора
            this.VSync = VSyncMode.Off;
        }
        // Загрузка ресурсов
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            // Проверка версии OpenGL
            Version version = new Version(GL.GetString(StringName.Version).Substring(0, 3));
            Version target = new Version(1, 5);
            if (version < target)
            {
                throw new NotSupportedException(String.Format("OpenGL {0} требуется (имеется только {1})", target, version));
            }
            // Цвет заливки окна вывода
            GL.ClearColor(.1f, 0f, .1f, 0f);
            GL.Enable(EnableCap.DepthTest); // Активизируем тест глубины
            GL.PointSize(5f); // Размер частиц
            GL.Enable(EnableCap.PointSmooth); // Точка округлой формы
            GL.Hint(HintTarget.PointSmoothHint, HintMode.Nicest); // Наилучшее сглаживание
            // Статус VBO. Работаем с массивами цвета и вершин
            GL.EnableClientState(ArrayCap.ColorArray);
            GL.EnableClientState(ArrayCap.VertexArray);
            GL.GenBuffers(1, out VBOHandle); // Номер обработчика VBO
            // Можно выполнить привязку здесь, поскольку имеется только 1 VBO
            GL.BindBuffer(BufferTarget.ArrayBuffer, VBOHandle);
            GL.ColorPointer(4, ColorPointerType.UnsignedByte, VertexC4ubV3f.SizeInBytes, (IntPtr)0);
            GL.VertexPointer(3, VertexPointerType.Float, VertexC4ubV3f.SizeInBytes, (IntPtr)(4 * sizeof(byte)));
            Random rnd = new Random();
            Vector3 temp = Vector3.Zero;
            // Генерация частиц
            for (uint i = 0; i < MaxParticleCount; i++)
            {
                // Цвет частицы i
                VBO[i].R = (byte)rnd.Next(0, 256);
                VBO[i].G = (byte)rnd.Next(0, 256);
                VBO[i].B = (byte)rnd.Next(0, 256);
                VBO[i].A = (byte)rnd.Next(0, 256); // Не используется
                // Координаты частицы i
                VBO[i].Position = Vector3.Zero; // Все частицы появляются в начале координат
                // Генерация вектора направления в диапазоне [-0.25f...+0.25f]
                temp.X = (float)((rnd.NextDouble() - 0.5) * 0.5f);
                temp.Y = (float)((rnd.NextDouble() - 0.5) * 0.5f);
                temp.Z = (float)((rnd.NextDouble() - 0.5) * 0.5f);
                ParticleAttributes[i].Direction = temp; // Вектор направления
                ParticleAttributes[i].Age = 0; // Начальный возраст частицы
            }
            // Число родившихся частиц
            VisibleParticleCount = 0;
        }
        protected override void OnUnload(EventArgs e)
        {
            GL.DeleteBuffers(1, ref VBOHandle);
        }
        // Вызывается при изменении размеров окна вывода
        // Подходящее место для матриц проецирования и видовой матрицы
        protected override void OnResize(EventArgs e)
        {
            GL.Viewport(0, 0, Width, Height);
            GL.MatrixMode(MatrixMode.Projection);
            Matrix4 p = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Width / (float)Height, 0.1f, 50.0f);
            GL.LoadMatrix(ref p);
            GL.MatrixMode(MatrixMode.Modelview);
            Matrix4 mv = Matrix4.LookAt(Vector3.UnitZ, Vector3.Zero, Vector3.UnitY);
            GL.LoadMatrix(ref mv);
        }
        // Вызывается при обновлении изображения
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            var keyboard = OpenTK.Input.Keyboard.GetState();
            if (keyboard[Key.Escape]) Exit();
            // Обновляем частицы. При использовании Physics SDK частота обновления существенно выше числа кадров в секунду
            // Поэтому будут лишние циклы копирования в VBO
            if (VisibleParticleCount < MaxParticleCount) VisibleParticleCount++;
            Vector3 temp;
            for (int i = MaxParticleCount - VisibleParticleCount; i < MaxParticleCount; i++)
            {
                if (ParticleAttributes[i].Age >= MaxParticleCount)
                {
                    // Приводим непоявившиеся частицы в начальное состояние
                    ParticleAttributes[i].Age = 0;
                    VBO[i].Position = Vector3.Zero;
                }
                else
                {
                    // Эти частицы уже в сцене
                    // Увеличиваем возраст частицы
                    ParticleAttributes[i].Age += (uint)Math.Max(ParticleAttributes[i].Direction.LengthFast * 10, 1);
                    // Получаем вектор temp, умножая вектор направления на время события
                    Vector3.Multiply(ref ParticleAttributes[i].Direction, (float)e.Time, out temp);
                    // Изменяем координаты частицы, складывая прежние координаты с вектором temp
                    Vector3.Add(ref VBO[i].Position, ref temp, out VBO[i].Position);
                }
            }
        }
        // Вызывается при выводе очередного кадра
        // Параметр "e" содержит информацию о времени
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            this.Title = VisibleParticleCount + " - число частиц. FPS: " + string.Format("{0:F}", 1.0 / e.Time);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.PushMatrix();
            // Перемещаем частицы по оси Z
            GL.Translate(0f, 0f, -5f);
            // Сообщаем OpenGL, что можно пренебречь прежним VBO и предоставить память для нового VBO-буфера
            // Без этого GL будет ждать завершения вывода старого VBO
            GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(VertexC4ubV3f.SizeInBytes * MaxParticleCount), IntPtr.Zero, BufferUsageHint.StreamDraw);
            // Заполняем вновь выделенный буфер
            GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(VertexC4ubV3f.SizeInBytes * MaxParticleCount), VBO, BufferUsageHint.StreamDraw);
            // Выводим только уже родившиеся частицы
            GL.DrawArrays(PrimitiveType.Points, MaxParticleCount - VisibleParticleCount, VisibleParticleCount);
            GL.PopMatrix();
            SwapBuffers();
        }
        // Точка входа в приложение
        static void Main()
        {
            using (T09_VBO_Dynamic example = new T09_VBO_Dynamic())
            {
                example.Title = "Пример вывода частиц";
                example.Run(30.0, 0.0);
            }
        }
    }
}

Список работ

Рейтинг@Mail.ru