DirectX 9プラットフォームでのパーティクルシステム開発パートI

この投稿では、独自の、非常に生産的な(私のコンピューターでは、リアルタイムで1,000,000個のパーティクルが静かに描画およびアニメーション化される)パーティクルシステムの開発方法について説明します。 C ++で記述し、DirectX 9をプラットフォームとして使用します。



2番目の部分はここから入手できます



視覚化フレームの1つの例(クリック可能):









そもそも、なぜC ++とDirectX9なのかを説明する価値はありますが、XNAやGDI全般ではありません。 決定する前に、HTML + JS(概念を開発したとき)、C#とGDI、C ++とGDI、C#とXNAなど、多くのオプションを試しました。 これらのオプションはすべて、必要なパフォーマンス(50,000個を超えるパーティクルのリアルタイムレンダリング)を達成することができなかったため、より深刻なオプションを検討し始めました。 最初に頭に浮かんだのはDirectDrawでしたが、誰も長い間開発していなかったため、Direct3Dを選択しました。 OpenGLを使用することもできますが、D3Dは何らかの形で私に近いです。



0.コンセプトと要件



システムはパーティクルを描画してアニメーション化します。 いくつかの式に従ってアニメーションを作成します(例として、私は普遍重力の法則を使用しました)。 外部からシステムと対話し、リアルタイムでデータを送信できます。



要件。


パーティクルシステムは、リアルタイムレンダリングに使用できるように十分に生産的であり、設定の柔軟性が必要であり、スプライト、さまざまなエフェクト、およびポストエフェクトを使用できる必要があります。



順番に行きましょう:

1. パフォーマンス。 おそらく、C \ C ++よりも高速なものを見つけるのは難しく、Direct3Dはコンピューターゲームの開発に広く使用されています。 確かに十分な機能があります。

2. リアルタイムレンダリング。 実際には、Direct3D(OpenGL)がこれに使用されます。 選択した候補者が適切です。

3. カスタマイズの柔軟性。 DirectXには、シェーダーなどの素晴らしい機能があります。 それら以外は何も書き換えずに実装できます。

4. スプライト。 DirectXでは、これらは非常に使いやすいです。 ぴったり。

5. エフェクト、ポストエフェクト。 これを実装するには、シェーダーが役立ちます。



次に、十分なパフォーマンスを達成するために考慮しなければならない微妙な点について説明します。 なぜなら パーティクルの数は非常に多いので、レンダリング関数の呼び出しが大量に発生してパフォーマンスが低下しないように、パーティクルを1つのピースにレンダリングするために送信することをお勧めします。 まあ、メモリも世話をする必要があるので、最良のオプションはポイントの配列の形式でデータを保存することです。



理論上のすべての質問を解決しました。実装に移ります。



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に変換するために使用され、カメラは私たちが見ているものと見ている場所を記述します。



しかし、ここで1つの単純化、直交射影行列を紹介します。 実際、パーティクルはフラットポイントであり、遠近感の特別な計算は必要ありません。カメラなしで実行できます。 正射影では、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(頂点バッファー)に粒子データを保存するために使用され、空間内の粒子の座標を含みます。 この構造には特別な形式があり、実際、グラフィックプロセッサはそこから何をどこに描画するかに関する情報を取得します。

パーティクルパーティクルを表し、座標と速度が含まれます。

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に伝えます。 これにより、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フラグを指定しました 。これにより、GPUは古いバッファーからパーティクルを描画し続けることができます。 同時に、彼らは私たちに新しいものを返します。 この小さなトリックはダウンタイムを短縮します。



これで、記事の最初の部分は終わりました。 次のパートでは、パーティクル、頂点、ピクセルシェーダーのテクスチャリング、エフェクト、ポストエフェクトについて説明します。 また、2番目のパートの最後には、デモとその完全なソースコードへのリンクが表示されます。



All Articles