パーティクルの仕組みを実装してみる。
いろいろ調べていくと既存のツールではやれることがいろいろありすぎて、それと同じものを作ろうと考えるとなかなか進まない。
すでに1つのメッシュに対してオブジェクトを追加すると描画する仕組み自体はあるので、その仕組みにパーティクル要素を追加して肉付けしていくことにした。
パーティクル用メッシュ
色々なメッシュ形状が必要になると思うけど、とりあえず矩形を用意。
パーティクル用のメッシュの頂点データは、x,y,z,u,vのみにした。
後々、円盤とか、円柱とか用意する予定。
パーティクル用シェーダ
とりあえずパーティクル単位でシェーダを作るようにしてみる。
VS
Out.Pos = mul( Cam.TransPV, mul( World[ In.IID ], float4( In.Pos, 1.0f ))) ;
Out.UV = In.UV ;
頂点にはWorld、View、Projectionマトリックスを掛けて、UVはそのままPSに渡す。
PS
Out.Col = float4( 1, 1, 1, 1 ) ;
固定で白を出力。
パーティクル用パラメータ
既存のツールではかなり多くのパラメータが設定できる。汎用ツールなので殆ど使われないものも含んで細かくなってしまっているのだろう。
自分の実装としては、パラメータ化はせず生成と更新の関数を渡せるようにする。
たくさん作っていくうちに傾向、パターン化ができるようになれば毎回関数を作るのではなく、ライブラリ化したものを渡せるようになるはず。
生成関数
毎フレームオブジェクトを生成して100個作ったらやめるようにする。
更新関数
毎フレーム1ずつYを増やしていって、配置された位置から100行ったところで消滅するようにする。
実行結果
パーティクル自体は、オブジェクトが0になったタイミングで消滅するようにした。
 |
| この時点での実行結果 |
結果を見ると、とりあえず動いているように見えるが、カメラを動かすと背面カリングが効いて消えてしまう。使い方によるが、今回のパターンではビルボードの動きをしてほしい。
以前、草をGSとHSで生やした際、ビルボードについて調べたときにViewの逆行列を利用すれば良いというのを見つけたが結局出来ず、直接計算して頂点を操作した。GSではそれで問題なかったけど、今回の場合はそうは行かないので再度チャレンジしてみる。
VS
Out.Pos = mul( Cam.TransPV, mul( World[ In.IID ], mul( Cam.InverseV, float4( In.Pos, 1.0f )))) ;
Out.UV = In.UV ;
カメラの定数バッファにViewの逆行列(Cam.InverseV)を含めた。その際、移動部分_41~_43を0にする必要がある。用途によっては回転に対しての打ち消しも必要になる。
試行錯誤した結果、上記でうまく行った。
草のときには、カメラの上下には追従してしまうと、草が全部寝てしまうのでY軸を打ち消す様に_22は1、_21、_23は0にしてたことになる。
ただ、パーティクルとして使う場合は不要なので移動のみ打ち消す。
PS
Out.Col = float4( 1, 1, 1, 0.5 ) ;
パーティクルといえばアルファブレンドで加算合成だろということで、アルファ値を0.5に修正。
 |
| ビルボード版 |
ただ、アルファブレンドがおかしい。白いオブジェクトが連なってるのでずっと白のハズなのに途切れてる部分が見える。
これは深度バッファを利用して物体の前後関係は維持しつつ、パーティクル自体は深度バッファに書き込まないようにしないといけないけど、いつも通り書き込んでしまっているために同じZのオブジェクトが描かれなくなっているせいで起こっている。
機能を拡張して、シェーダを作るときに色々パラメータ指定をできるようにする必要がある。
ラスタライザ
FillMode
たまにワイヤーフレームで確認したいときがあって、ライブラリを直接変えて実行していたけど、指定できるようにする。デフォルトはD3D12_FILL_MODE_SOLID。
CullMode
これもメッシュが表示されないとき、両面描画に変えたりして確認していたけど、パーティクルやエフェクトの描画の場合種類によって両面描画する必要があったりするので、指定できるようにする。デフォルトはD3D12_CULL_MODE_BACK。
アルファブレンド
元々アルファブレンドOn/Off自体はあったんだけど、今回いろんなブレンド方法を追加しておこう。
アルファブレンドなし
D3D12_BLEND_DESC & oBD = oGPSD.BlendState ;
oBD.AlphaToCoverageEnable = FALSE ;
oBD.IndependentBlendEnable = FALSE ;
for( tNum i = 0 ; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT ; i++ ) {
auto & oRT = oBD.RenderTarget[i] ;
oRT.BlendEnable = FALSE ;
oRT.LogicOpEnable = FALSE ;
oRT.SrcBlend = D3D12_BLEND_ONE ;
oRT.DestBlend = D3D12_BLEND_ZERO ;
oRT.BlendOp = D3D12_BLEND_OP_ADD ;
oRT.SrcBlendAlpha = D3D12_BLEND_ONE ;
oRT.DestBlendAlpha = D3D12_BLEND_ZERO ;
oRT.BlendOpAlpha = D3D12_BLEND_OP_ADD ;
oRT.LogicOp = D3D12_LOGIC_OP_NOOP ;
oRT.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL ;
}
アルファブレンドあり
switch( eABT ) {
case tAlphaBlendType::Normal : // 通常ブレンド
oRT.BlendEnable = TRUE ;
oRT.SrcBlend = D3D12_BLEND_SRC_ALPHA ;
oRT.DestBlend = D3D12_BLEND_INV_SRC_ALPHA ;
break ;
case tAlphaBlendType::Add : // 加算合成
oRT.BlendEnable = TRUE ;
oRT.SrcBlend = D3D12_BLEND_ONE ;
oRT.DestBlend = D3D12_BLEND_ONE ;
break ;
case tAlphaBlendType::AddAlpha : // 加算半透明合成
oRT.BlendEnable = TRUE ;
oRT.SrcBlend = D3D12_BLEND_SRC_ALPHA ;
oRT.DestBlend = D3D12_BLEND_ONE ;
break ;
case tAlphaBlendType::Sub : // 減算合成
oRT.BlendEnable = TRUE ;
oRT.SrcBlend = D3D12_BLEND_ZERO ;
oRT.DestBlend = D3D12_BLEND_INV_SRC_COLOR ;
break ;
case tAlphaBlendType::Mul : // 乗算合成
oRT.BlendEnable = TRUE ;
oRT.SrcBlend = D3D12_BLEND_ZERO ;
oRT.DestBlend = D3D12_BLEND_SRC_COLOR ;
break ;
case tAlphaBlendType::Mul2 : // 乗算合成2
oRT.BlendEnable = TRUE ;
oRT.SrcBlend = D3D12_BLEND_DEST_COLOR ;
oRT.DestBlend = D3D12_BLEND_SRC_COLOR ;
break ;
case tAlphaBlendType::Screen : // スクリーン合成
oRT.BlendEnable = TRUE ;
oRT.SrcBlend = D3D12_BLEND_INV_DEST_COLOR ;
oRT.DestBlend = D3D12_BLEND_ONE ;
break ;
case tAlphaBlendType::Reverse : // リバース
oRT.BlendEnable = TRUE ;
oRT.SrcBlend = D3D12_BLEND_INV_DEST_COLOR ;
oRT.DestBlend = D3D12_BLEND_INV_SRC_COLOR ;
break ;
}
深度バッファ
DepthWriteMask
深度バッファの書き込みをしない場合は、この値をD3D12_DEPTH_WRITE_MASK_ZEROにする。デフォルトはD3D12_DEPTH_WRITE_MASK_ALL。
シェーダコンパイル
パラメータを指定することで正しい描画になった。
調子に乗ってボタンを押しまくると、若干カクつく。
試しにパーティクル追加の時間を測ってみると、なんと追加に16~17msもかかっている。
シェーダの生成、コンパイル、PSO作成はかなり重い処理ということか。
パーティクルのシェーダはパーティクル追加とは切り離して、事前に行う必要がありそう。
とりあえず完成
PSをちょっと修正して、生成関数で各方向にランダム値を設定。更新関数で指定方向に進むように変更してみた。
 |
| それっぽい |
一応それっぽいものが出来た。
後はシェーダとパーティクルの紐づけを変更しつつ、描画時に各種リソースを設定する部分ができればいろんなことができるようになる。