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

この投稿は、DirectX 9でのパーティクルシステムの開発に関する記事の2番目と最後の部分です。最初の部分を読んでいない場合は、それをよく理解することを勧めします。



記事のこの部分では、スプライト、頂点シェーダー、ピクセルシェーダー、エフェクト、ポストエフェクトの操作について説明します。 特に、ポストエフェクトの実装-テクスチャへのレンダリングの受け入れ。





0.基本情報





スプライト


スプライトは、画面上を動き回り、オブジェクトまたはオブジェクトの一部を描写するテクスチャです。 システム内のパーティクルは単なるドットであるため、さまざまなテクスチャをそれらに適用すると、任意のオブジェクト(雲など)を視覚化できます。 スプライトは単純なテクスチャであるため、基本的な理解が必要です。



ピクセルの代わりに、私たちが慣れているように、テクセルがあります。 Direct3Dは、水平U軸と垂直V軸によって形成されるテクスチャに座標系を使用します。





頂点シェーダー


頂点シェーダーは、特別なHLSL言語(またはアセンブラー)で作成されたプログラムで、頂点変換とライティングを処理します。 頂点シェーダーでは、頂点の位置を取得して、完全に別の場所に移動できます。 この記事では、頂点シェーダーもテクスチャー座標の生成に使用されます。



ピクセルシェーダー


頂点シェーダーと同様に、代わりに画像のラスタライズに従事します。 このようなシェーダーでは、テクスチャ、色、その他の多くに関するデータが送信され、これに基づいて、シェーダーはピクセルの色を返す必要があります。 テクスチャに使用します。



エフェクトとポストエフェクト


エフェクトには、ピクセルシェーダーや頂点シェーダー、および1つ以上のレンダーパスが含まれます。 それらを使用して、たとえば、ぼかしまたはグロー効果を実装できます。

ポストエフェクトは、すでに荒れ果てたシーンに適用されるという点で通常のエフェクトとは異なります。



1.粒子にテクスチャを付ける



テクスチャをパーティクルに適用する前に、バッファ内の頂点を表すために使用したタイプを次のように変更する必要があります。

struct VertexData { float x,y,z; float u,v; //    };
      
      





値uおよびvは、作成時にゼロに初期化する必要があります。



また、バッファを作成するときにフラグとバッファの説明を変更する必要があります。

 device->CreateVertexBuffer(count*sizeof(VertexData), D3DUSAGE_WRITEONLY, D3DFVF_XYZ | D3DFVF_TEX0, D3DPOOL_DEFAULT, &pVertexObject, NULL); // ... D3DVERTEXELEMENT9 decl[] = { { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, { 0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, D3DDECL_END() };
      
      





テクスチャの座標を保存することを示すフラグD3DFVF_TEX0を追加します。 また、頂点の説明に行を追加します。



そして今、テクスチャをロードし、レンダリング状態を変更します:

 float pointSize = 5; //       device->SetRenderState(D3DRS_POINTSIZE_MAX, *((DWORD*)&pointSize)); device->SetRenderState(D3DRS_POINTSIZE, *((DWORD*)&pointSize)); device->SetRenderState(D3DRS_LIGHTING,FALSE); device->SetRenderState(D3DRS_POINTSPRITEENABLE, TRUE ); //     device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); device->SetRenderState(D3DRS_ZENABLE, FALSE);
      
      







すべての状態については説明しませんが、それらの情報はMSDNで参照できます。 私は効果のためにそれらのいくつかが必要になるとしか言えません。



 IDirect3DTexture9 *particleTexture = NULL, D3DXCreateTextureFromFile(device, L"particle.png", &particleTexture); //  device->SetTexture(0, particleTexture); // 
      
      





テクスチャをロードして、パーティクルを表すファイルからインストールします。



これで、アプリケーションを起動すると、単純なドットの代わりにテクスチャ付きの粒子が表示されますが、さらに進んで、結果の画像に単純な効果を追加します。



可視化結果:





2.効果





エフェクトの開発には、NVIDIAのFx Composerという素晴らしいプログラムがあります。 シェーダー、第4バージョンのシェーダー、DIrect3D(9、10)およびOpenGLのデバッグをサポートしました。 強くお勧めしますが、この記事ではこの開発環境は考慮しません。



最初に、効果の基本構造を検討します。

非表示のテキスト
 float4x4 WorldViewProj; //  .  4x4 //   texture Base < string UIName = "Base Texture"; string ResourceType = "2D"; >; //,     sampler2D BaseTexture = sampler_state { Texture = <Base>; AddressU = Wrap; AddressV = Wrap; }; //,       struct VS_INPUT { float4 Position : POSITION0; float2 Tex : TEXCOORD0; }; //    struct VS_OUTPUT { float4 Position : POSITION0; float2 Tex : TEXCOORD0; }; //   VS_OUTPUT mainVS(VS_INPUT Input) { VS_OUTPUT Output; Output.Position = mul( Input.Position, WorldViewProj ); Output.Tex = Input.Tex; return( Output ); } //   float4 mainPS(float2 tex: TEXCOORD0) : COLOR { return tex2D(BaseTexture, tex); } //  "" technique technique0 { //   pass p0 { CullMode = None; //    //  VertexShader = compile vs_2_0 mainVS(); //   PixelShader = compile ps_2_0 mainPS(); //   } }
      
      









コードからわかるように、各シェーダーは値を取得して返します。 頂点シェーダーは、頂点の座標と、処理されたピクセルのピクセルカラーを返す必要があります。

効果はいくつかのテクニックに分けられます。 各手法は、独自の方法でエフェクトを適用したり、異なるエフェクトを適用したりすることもできます。

各手法には、1つ以上の視覚化パスが含まれています。



今度は単純な効果を作成します。たとえば、粒子を赤で色付けします。

非表示のテキスト
 float4x4 WorldViewProj; //  .  4x4 //   () texture Base < string UIName = "Base Texture"; string ResourceType = "2D"; >; //,     sampler2D BaseTexture = sampler_state { Texture = <Base>; AddressU = Wrap; AddressV = Wrap; }; //,       struct VS_INPUT { float4 Position : POSITION0; float2 Tex : TEXCOORD0; }; //    struct VS_OUTPUT { float4 Position : POSITION0; float2 Tex : TEXCOORD0; }; //   VS_OUTPUT mainVS(VS_INPUT Input) { VS_OUTPUT Output; Output.Position = mul( Input.Position, WorldViewProj ); //       Output.Tex = Input.Tex; //       return( Output ); } //   float4 mainPS(float2 tex: TEXCOORD0) : COLOR { return tex2D(BaseTexture, tex) * float4(1.0, 0, 0, 1.0); //      } //  "" technique technique0 { //   pass p0 { CullMode = None; //    //  VertexShader = compile vs_2_0 mainVS(); //   PixelShader = compile ps_2_0 mainPS(); //   } }
      
      









この効果のコードは、以前に調査した基本構造とほとんど異なりません。 Multiply Blendメソッドを使用して、赤とのブレンドのみを追加しました。 取得したものは次のとおりです。





悪くはありませんが、ブレンドモードを別のモードに変更し、1つの色ではなくテクスチャ全体でブレンドを行うことができます。

粒子とテクスチャの視覚化を正しくミックスするには、Render Target(視覚化ターゲット)と呼ばれる手法を使用する必要があります。 このテクニックの本質はシンプルで、シーンをテクスチャにレンダリングしてから、既にラスタライズされた画像にエフェクトを適用します。



これは、これを実装する完全なエフェクトコードです。

非表示のテキスト
 float4x4 WorldViewProj; texture Base < string UIName = "Base Texture"; string ResourceType = "2D"; >; sampler2D BaseTexture = sampler_state { Texture = <Base>; AddressU = Wrap; AddressV = Wrap; }; texture Overlay < string UIName = "Overlay Texture"; string ResourceType = "2D"; >; sampler2D OverlayTexture = sampler_state { Texture = <Overlay>; AddressU = Wrap; AddressV = Wrap; }; // ,      texture PreRender : RENDERCOLORTARGET < string Format = "X8R8G8B8" ; >; //     sampler2D PreRenderSampler = sampler_state { Texture = <PreRender>; }; struct VS_INPUT { float4 Position : POSITION0; float2 Tex : TEXCOORD0; }; struct VS_OUTPUT { float4 Position : POSITION0; float2 Tex : TEXCOORD0; }; VS_OUTPUT cap_mainVS(VS_INPUT Input) { VS_OUTPUT Output; Output.Position = mul( Input.Position, WorldViewProj ); Output.Tex = Input.Tex; return( Output ); } float4 cap_mainPS(float2 tex: TEXCOORD0) : COLOR { return tex2D(BaseTexture, tex); } /////////////////////////////////////////////////////// struct Overlay_VS_INPUT { float4 Position : POSITION0; float2 Texture1 : TEXCOORD0; }; struct Overlay_VS_OUTPUT { float4 Position : POSITION0; float2 Texture1 : TEXCOORD0; float2 Texture2 : TEXCOORD1; }; vector blend(vector bottom, vector top) { //Linear light float r = (top.r < 0.5)? (bottom.r + 2*top.r - 1) : (bottom.r + top.r); float g = (top.g < 0.5)? (bottom.g + 2*top.g - 1) : (bottom.g + top.g); float b = (top.b < 0.5)? (bottom.b + 2*top.b - 1) : (bottom.b + top.b); return vector(r,g,b,bottom.a); } Overlay_VS_OUTPUT over_mainVS(Overlay_VS_INPUT Input) { Overlay_VS_OUTPUT Output; Output.Position = mul( Input.Position, WorldViewProj ); Output.Texture1 = Input.Texture1; Output.Texture2 = Output.Position.xy*float2(0.5,0.5) + float2(0.5,0.5); //   ,    return( Output ); } float4 over_mainPS(float2 tex :TEXCOORD0, float2 pos :TEXCOORD1) : COLOR { return blend(tex2D(OverlayTexture, pos), tex2D(PreRenderSampler, tex)); } technique technique0 { pass p0 { CullMode = None; VertexShader = compile vs_2_0 cap_mainVS(); PixelShader = compile ps_2_0 cap_mainPS(); } pass p1 { CullMode = None; VertexShader = compile vs_2_0 over_mainVS(); PixelShader = compile ps_2_0 over_mainPS(); } }
      
      







お気づきのように、視覚化の別の段階が現れました。 最初の段階では、粒子をそのまま視覚化します。 さらに、テクスチャで視覚化をレンダリングする必要があります。 そして、すでに視覚化の2番目のパスで、線形ライトブレンディングを使用して画像に別のテクスチャを適用します。



プログラムでエフェクトを使用する



エフェクトを作成しました。エフェクトの使用を追加してコードを変更します。

エフェクトコードを作成してコンパイルし、追加のテクスチャをロードし、レンダリングするテクスチャを作成する必要があります。

非表示のテキスト
 ID3DXBuffer* errorBuffer = 0; D3DXCreateEffectFromFile( //     device, L"effect.fx", NULL, NULL, D3DXSHADER_USE_LEGACY_D3DX9_31_DLL, //   DirectX 9 NULL, &effect, &errorBuffer ); if( errorBuffer ) // ,    { MessageBoxA(hMainWnd, (char*)errorBuffer->GetBufferPointer(), 0, 0); errorBuffer->Release(); terminate(); } //  ,     WorldViewProj //       D3DXMATRIX W, V, P, Result; D3DXMatrixIdentity(&Result); device->GetTransform(D3DTS_WORLD, &W); device->GetTransform(D3DTS_VIEW, &V); device->GetTransform(D3DTS_PROJECTION, &P); D3DXMatrixMultiply(&Result, &W, &V); D3DXMatrixMultiply(&Result, &Result, &P); effect->SetMatrix(effect->GetParameterByName(0, "WorldViewProj"), &Result); //     effect->SetTechnique( effect->GetTechnique(0) ); IDirect3DTexture9 *renderTexture = NULL, *overlayTexture = NULL; //        IDirect3DSurface9* orig =NULL , *renderTarget = NULL; D3DXCreateTextureFromFile(device, L"overlay.png", &overlayTexture); //  ,      D3DXCreateTexture(device, Width, Height, 0, D3DUSAGE_RENDERTARGET, D3DFMT_X8B8G8R8, D3DPOOL_DEFAULT, &renderTexture); //  ,     renderTexture->GetSurfaceLevel(0, &renderTarget); //    device->GetRenderTarget(0, &orig); //    auto hr = effect->SetTexture( effect->GetParameterByName(NULL, "Overlay"), overlayTexture); hr |= effect->SetTexture( effect->GetParameterByName(NULL, "Base"), particleTexture); hr |= effect->SetTexture( effect->GetParameterByName(NULL, "PreRender"), renderTexture); if(hr != 0) { MessageBox(hMainWnd, L"Unable to set effect textures.", L"", MB_ICONHAND); }
      
      









ご覧のとおり、使用する前にエフェクトをコンパイルし、テクニックを選択し、使用するすべてのデータをインストールする必要があります。

テクスチャを視覚化するには、テクスチャ自体、元のシーンのサイズ、およびその表面を作成する必要があります。 表面はレンダリングに使用されます。



現在は、エフェクトを使用してテクスチャを描画するだけです。 これは次のように行われます。



非表示のテキスト
 UINT passes = 0; //       effect->Begin(&passes, 0); for(UINT i=0; i<passes; ++i) { effect->BeginPass(i); if(i == 0) { //    device->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 ); //  ,    ,     device->SetRenderTarget(0, renderTarget); //  ,      device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0); //   DrawParticles(); } else if(i == 1) { //    device->SetRenderTarget(0, orig); //  ,      (RenderTexture) DrawRect(); } effect->EndPass(); } effect->End(); //     device->Present(NULL, NULL, NULL, NULL);
      
      









DrawRect()を使用したコードでは、この関数はRenderTextureテクスチャが重ねられた長方形を描画します。 これはこの手法の特徴であり、テクスチャにレンダリングした後、さらに処理するために何らかの方法で画面に表示する必要があります。 画面スペース全体を占めるように描画する長方形は、これに役立ちます。 記事をさらに膨らませないように、頂点の初期化コードと四角形の視覚化は行いません。 必要なアクションはすべて、パーティクルの初期化中に実行したアクションに似ているとしか言えません。 問題がある場合は、このコードがサンプルコードでどのように実装されているかを確認できます。



エフェクトは次のように使用されます。最初にBegin()メソッドを呼び出し、エフェクトのレンダリングパスの数を取得します。 次に、各パスの前にBeginPass(i)を呼び出し、 EndPass()の後に呼び出します。 最後に、視覚化が完了した後、 End()メソッドを呼び出します。



取得したものは次のとおりです。





この記事の終わりに、ご清聴ありがとうございました。 コメントでご質問にお答えできることを嬉しく思います。

プロジェクトの完全なソースコードはGitHubで入手できます 。 注意、コンパイルされた例を実行するには、 VisualC ++ Redistributable 2012をインストールする必要があります



UPD

D3D9が絶望的に​​時代遅れになっていると思う人や、GPUですべての計算を実行したいだけの人のために、すでにD3D10でのみ別の例があります。 いつものように、例とコンパイルされたデモはGitHubで入手できます 。 GPUの計算が添付されています:)



All Articles