(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Разработка системы частиц на платформе DirectX 9. Часть I

Источник: habrahabr
A1ex

Данный пост будет о том, как разработать свою собственную, и достаточно производительную (на моем компьютере спокойно отрисовывается и анимируется 1 000 000 частиц в реальном времени), систему частиц. Писать будем на языке C++, в качестве платформы будет использован DirectX 9. 

Пример одного из кадров визуализации (кликабельно):

Для начала стоит сказать почему именно C++ и DirectX9, а не, скажем, XNA, или вообще GDI. Перед тем как определиться, я рассмотрел\попробовал много вариантов: HTML+JS (когда разрабатывал концепцию), С# и GDI, C++ и GDI, С# и XNA. Все из перечисленных вариантов не позволили достичь необходимой производительности (реал-тайм рендеринг более 50000 частиц), поэтому я стал рассматривать более серьезные варианты. Первое что пришло в голову было DirectDraw, но его давно никто не разрабатывает, поэтому выбор пал на Direct3D. Можно было использовать и OpenGL, но D3D мне как-то ближе.

0. Концепция и требования

Система будет рисовать и анимировать частицы. Анимацию будем производить по некой формуле (в качестве примера я использовал Закон всемирного тяготения). С системой можно взаимодействовать из вне, передавая какие-то данные в реальном времени.

Требования.

Нам хочется, чтобы наша система частиц была достаточно производительной, чтобы её можно было использовать для отрисовки в реальном времени, должна быть гибкой в настройке, позволяла применять спрайты, различные эффекты и пост-эффекты.

Пойдем по-порядку:
1.  Производительность.  Пожалуй, чего-то быстрее чем C\C++ будет сложно найти, да и Direct3D широко применяется при разработке компьютерных игр. Нам его возможностей точно хватит.
2.  Отрисовка в реальном времени.  Собственно Direct3D (OpenGL) для этого и используют. Выбранный кандидат подходит.
3.  Гибкость в настройке.  В DirectX есть такая замечательная вещь, как шейдеры. Можно реализовать что угодно, не переписывая больше ничего, кроме них.
4.  Спрайты.  В DirectX ими достаточно легко пользоваться. Подходит.
5.  Эффекты, пост-эффекты.  Для реализации этого нам помогут шейдеры.

Теперь о тонкостях, которые необходимо учесть для того, чтобы достичь достаточной производительности. Т.к. количество частиц очень велико, то на отрисовку их подавать лучше одним куском, дабы не терять в производительности из-за огромного количества вызовов функций отрисовки. Ну и о памяти тоже нужно позаботиться, поэтому оптимальным вариантом будет хранение данных в виде массива точек.

Все теоретические вопросы мы решили, теперь перейдем к реализации.

1. Инициализация Direct3D и создание камеры

Для работы нам понадобится собственно среда разработки\компилятор и DirectX SDK

Первое, что нужно сделать это создать объект Direct3D9, после устройство для вывода и окно, где и будет все отображаться.

Скрытый текст
// Создание и регистрация класса окна
WNDCLASSEX wc = {sizeof(WNDCLASSEX), CS_VREDRAW/CS_HREDRAW/CS_OWNDC, 
    WndProc, 0, 0, hInstance, NULL, NULL, (HBRUSH)(COLOR_WINDOW+1), 
    NULL, L"RenderToTextureClass", NULL}; 
RegisterClassEx(&wc);

// Создание окна
HWND hMainWnd = CreateWindowW(L"RenderToTextureClass", 
    L"Render to texture", 
    WS_POPUP, 0, 0, Width, Height, 
    NULL, NULL, hInstance, NULL);

// Создание объекта Direct3D
LPDIRECT3D9 d3d = Direct3DCreate9(D3D_SDK_VERSION);

// Создание и устанока параметров устройства
D3DPRESENT_PARAMETERS PresentParams;
memset(&PresentParams, 0, sizeof(D3DPRESENT_PARAMETERS));
PresentParams.Windowed = TRUE; // Наше приложение не полноэкранное

// Указываем как будет осуществляться переключение буферов в цепочке переключений.
// Для большинства случаев можно указать значение D3DSWAPEFFECT_DISCARD.
PresentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;

LPDIRECT3DDEVICE9 device = NULL;
// Создаем устройство
d3d->CreateDevice(D3DADAPTER_DEFAULT, // Используем адаптер по умолчанию
 	D3DDEVTYPE_HAL, 	      // Используем аппаратное ускорение
 	hMainWnd,                     // Рисовать будем в этом окне
    D3DCREATE_HARDWARE_VERTEXPROCESSING, // Будем использовать аппаратную обработку вершин
 	&PresentParams,               // Параметры, которые мы заполнили выше
    &device);                     // Указатель на переменную, в которую будет добавлен объект,
                                   // представляюищий устройство.

device->SetRenderState(D3DRS_LIGHTING,FALSE);  // Мы не будем использовать освещение
device->SetRenderState(D3DRS_ZENABLE, FALSE); // И буфер глубины тоже

В коде выше мы создаем обычное окно, в котором будет происходить отрисовка. Далее объект Direct3D. И наконец объект устройства, который мы и будем использовать для рисования.

Пару слов о аппаратном ускорении. Многие вычисления можно производить с помощью процессора, эмулируя видеокарту, но т.к. обычный процессор не очень подходит для этих целей (в нем, в лучшем случае, 4 ядра, а в видеокарте используется десятки, а то и сотни), то это скажется на быстродействии. В некоторых случаях очень сильно. Поэтому по возможности лучше использовать аппаратное ускорение.

Еще следует не забывать об установки матриц проекции и камеры. Если коротко, то матрица проекции используется для преобразование 3D данных в 2D, а камера описывает то, что мы видим и куда смотрим.

Но здесь я внесу одно упрощение, ортогональную матрицу проекции, т.к. фактически наши частицы плоские точки, и им не нужны особые расчеты перспективы, мы можем обойтись и без камеры. В ортогональной проекции Z фактически не учитывается, и объекты не изменяют размер в зависимости от положения в пространстве.

// Инициализация матриц
    D3DXMATRIX matrixView;
    D3DXMATRIX matrixProjection;

// Матрица вида
D3DXMatrixLookAtLH(
    &matrixView,
    &D3DXVECTOR3(0,0,0),
    &D3DXVECTOR3(0,0,1),
    &D3DXVECTOR3(0,1,0));

// Матрица проекции
D3DXMatrixOrthoOffCenterLH(&matrixProjection, 0, Width, Height, 0, 0, 255);

// Установка матриц в качестве текущих
device->SetTransform(D3DTS_VIEW,&matrixView);
device->SetTransform(D3DTS_PROJECTION,&matrixProjection);

2. Создания частиц и буфера для них

Все приготовления мы выполнили, осталось лишь создать частицы и приступить к рисованию.

struct VertexData
{
    float x,y,z;
};

struct Particle
{
    float x, y, vx, vy;
};
std::deque<Particle> particles;

VertexData используется для хранения данных о частице в GPU (вершинный буфер), и содержит координаты нашей частицы в пространстве. Эта структура имеет особый формат, и фактически графический процессор будет брать из нее сведения что и где рисовать.
Particle будет представлять нашу частицу, и содержит координаты и скорость. 
В particles же будут хранится сведения о всех наших частицах. Этой информацией мы будем пользоваться для расчетов движения частиц.

Скрытый текст
//Заполняем внутренний массив сведениями о частицах
srand(clock());
Particle tmp;
for( int i = 0; i<particleCount; ++i )
{
    tmp.x  = rand()%Width;
    tmp.y  = rand()%Height;
    
    particles.push_back( tmp );
}

LPDIRECT3DVERTEXBUFFER9 pVertexObject = NULL;
LPDIRECT3DVERTEXDECLARATION9 vertexDecl = NULL;

size_t count = particles.size();
VertexData *vertexData = new VertexData[count];

for(size_t i=0; i<count; ++i)
{
vertexData[i].x = particles[i].x;
vertexData[i].y = particles[i].y;
vertexData[i].z = 0.f;
vertexData[i].u = 0;
vertexData[i].v = 0;
}

void *pVertexBuffer = NULL; 
// Создаем вершинный буфер
device->CreateVertexBuffer(
    count*sizeof(VertexData), // Необходимое количество байт
    D3DUSAGE_WRITEONLY,       // Говорим GPU, что мы не будем читать данные из буфера
    D3DFVF_XYZ,		  // Буфер будет хранить координаты XYZ
    D3DPOOL_DEFAULT,          // Размещение в пуле по умолчанию
    &pVertexObject,           // Указатель на объект, куда будем помещен буфер
    NULL);			  // Зарезервированный параметр. Всегда NULL

// Блокируем буфер, чтобы записать туда данные о вершинах
pVertexObject->Lock(0, count*sizeof(VertexData), &pVertexBuffer, 0);

// Копируем данные в буфер
memcpy(pVertexBuffer, vertexData, count*sizeof(VertexData));
pVertexObject->Unlock();

delete[] vertexData;
vertexData = nullptr;

// Создаем описание данных в буфере
// Наш буфер хранит 3 float, начиная с 0-го байта, представляющие позицию элемента
D3DVERTEXELEMENT9 decl[] =
{
    { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
    D3DDECL_END()
};

// Создаем объект с описанием вершин
device->CreateVertexDeclaration(decl, &vertexDecl);

Прокомментирую некоторые моменты. 
При создание буфера, мы передаем параметр D3DUSAGE_WRITEONLY, говоря GPU, что мы не будем читать данные из буфера. Это позволит графическому процессору произвести необходимые оптимизации, и увеличит скорость рендеринга.
VertexDeclaration обычно используют для работы с шейдерами. Если шейдеры не требуются, то можно обойтись без создания этого объекта.

3. Отрисовка частиц

Теперь частицы необходимо нарисовать. Делается это очень просто:

// Очищаем экранный буфер
device->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );

// Устанавливаем источник вершин
device->SetStreamSource(0, pVertexObject, 0, sizeof(VertexData));
// Указываем, что хранится в буфере
device->SetVertexDeclaration(vertexDecl);

device->BeginScene();

// Рисуем
device->DrawPrimitive(D3DPRIMITIVETYPE::D3DPT_POINTLIST, // Данные в буфере - точки
    0,						 // начинаем с 0-го байта
    particles.size()); // Количество объектов столько, сколько у нас частиц

device->EndScene();

Одно важно замечание: BeginScene() необходимо вызывать каждый раз перед началом рисования, аEndScene() после окончания.

Анимация

Ну и конечно никак без анимации, иначе какая же это система частиц. В качестве примера я использовал Закон всемирного тяготения.

Скрытый текст
// Получаем координаты курсора
POINT pos;
GetCursorPos(&pos);
RECT rc;
GetClientRect(hMainWnd, &rc); 
ScreenToClient(hMainWnd, &pos);

const int mx = pos.x;
const int my = pos.y;
const auto size = particles.size();

float force;
float distSquare;

VertexData *pVertexBuffer;
// Блокируем весь буфер, для изменения
pVertexObject->Lock(0, 0, (void**)&pVertexBuffer, D3DLOCK_DISCARD);

for(int i = 0; i < size; ++i )
{
    auto &x = particles[i].x;
    auto &y = particles[i].y;

    distSquare= pow( x - mx, 2 ) + pow( y - my, 2 );
    if( dist < 20 )
    {
        force = 0;
    }
    else
    {
        force = G / distSquare;
    }

    const float xForce = (mx - x) * force;
    const float yForce = (my - y) * force;

    particles[i].vx *= Resistance;
    particles[i].vy *= Resistance;

    particles[i].vx += xForce;
    particles[i].vy += yForce;

    x+= particles[i].vx;
    y+= particles[i].vy;

    if( x > Width )
        x -= Width;
    else if( x < 0 )
        x += Width;

    if( y > Height )
        y -= Height;
    else if( y < 0 )
        y += Height;

    pVertexBuffer[i].x = particles[i].x;
    pVertexBuffer[i].y = particles[i].y;
}
pVertexObject->Unlock();

При блокировке буфера, я указал флаг D3DLOCK_DISCARD, он позволяет продолжить графическому процессору отрисовку частиц из старого буфера. Нам при этом возвращают новый. Эта маленькая хитрость уменьшает простои оборудования.

На этом первая часть статьи подошла к концу. В следующей части будет описано текстурирование частиц, вершинные и пиксельные шейдеры, а так же эффекты и пост-эффекты. Так же в конце 2-ой части вы увидите ссылки на демонстрацию и её полный исходный код.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 07.11.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
ESET NOD32 Антивирус на 1 год для 3ПК или продление на 20 месяцев
The BAT! Home Upgrade- 1 компьютер
CAD Import .NET Professional пользовательская
ESET NOD32 Secure Enterprise newsale for 26 user, лицензия на 1 год
SAP CRYSTAL Reports 2013 WIN INTL NUL
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование в AutoCAD
СУБД Oracle "с нуля"
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Delphi - проблемы и решения
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
Обсуждения в форумах
Топ рейтинг слотов 2021 года (6)
Среди огромного выбора азартных слотов...
 
Пишу программы на заказ профессионально (3292)
Пишу программы на заказ на языках Pascal (численные методы, списки, деревья, прерывания) под...
 
Отличается ли ДрифтКазино от беттинга? (43)
Друзья, давно заметил, что на Дрифте уже несколько месяцев во всю рекламируется и предлагается...
 
Программы Delphi на заказ (246)
Пишу программы в среде Delphi на заказ http://bddelphi.ucoz.ru/
 
Нормальные казино в 2021 году (14)
После долгих поисков все таки смог найти хорошие игровухи, хотя и ушла куча времени, пока я...
 
 
 



    
rambler's top100 Rambler's Top100