DX11でパーティクル

公開日:2018-10-27
最終更新:2018-10-27

やりたかったこと

リスポーンと更新もシェーダでやる
ジオメトリシェーダ使って1頂点1四角形

知ったこと

・頂点バッファは2つ必要
・ドローコールは2回必要(更新と描画)
・描画と更新でジオメトリシェーダを切り替える

改善点

ジオメトリシェーダ内の初期化と更新をif文で分けているのでif文なくしたい

参考本URL

http://maverickproj.web.fc2.com/d3d11_11.html
https://sites.google.com/site/monshonosuana/directxno-hanashi-1/directx-108
ここなければ作れなかった。ありがとうございました。

コード

頂点レイアウト

constexpr
D3D11_INPUT_ELEMENT_DESC vertex_desc[] = {
    { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT,    0,    4 * 4 * 0    , D3D11_INPUT_PER_VERTEX_DATA   , 0 },
    { "NORMAL"  , 0, DXGI_FORMAT_R32G32B32A32_FLOAT,    0,    4 * 4 * 1    , D3D11_INPUT_PER_VERTEX_DATA   , 0 },
    { "COLOR"   , 0, DXGI_FORMAT_R32G32B32A32_FLOAT,    0,    4 * 4 * 2    , D3D11_INPUT_PER_VERTEX_DATA   , 0 },
    { "TANGENT" , 0, DXGI_FORMAT_R32G32B32A32_FLOAT,    0,    4 * 4 * 3    , D3D11_INPUT_PER_VERTEX_DATA   , 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT,    0,    4 * 4 * 4    , D3D11_INPUT_PER_VERTEX_DATA   , 0 },
    { "TEXCOORD", 1, DXGI_FORMAT_R32G32B32A32_FLOAT,    0,    4 * 4 * 5    , D3D11_INPUT_PER_VERTEX_DATA   , 0 },
    { "TEXCOORD", 2, DXGI_FORMAT_R32G32B32A32_FLOAT,    0,    4 * 4 * 6    , D3D11_INPUT_PER_VERTEX_DATA   , 0 }, // 
    { "TEXCOORD", 3, DXGI_FORMAT_R32G32B32A32_FLOAT,    0,    4 * 4 * 7    , D3D11_INPUT_PER_VERTEX_DATA   , 0 },
};

これに合わせて頂点に持たせたい情報の構造体作成

// パーティクル1つの情報
struct ParticleOne
{
    vec4                        position                    ; // 現在位置
    vec4                        normal                      ;
    vec4                        color                       ;
    vec4                        move_vec                    ;
    vec4                        uv0                         ;
    union _uv1{
        vec4                    data_vec4                   ;
        struct {
            float32             rand_seed                   ; // 今回使わない
            UINT                index                       ; // 今回使わない
            float32             time                        ;
            float32             life_limit                  ;
        }data;
    }                           uv1                         ;
    vec4                        emit_pos                    ; // 発生位置。初期位置。
    vec4                        uv3                         ; // 今回使わない
};

vec4はfloat4つの構造体
unionいれると見づらくなる

頂点バッファ作成

D3D11_BUFFER_DESC       desc                = { 0 }                                                 ;
desc.ByteWidth                              = (UINT)(sizeof(VertexLayout) * vertex_list.size())     ;
desc.Usage                                  = D3D11_USAGE_DEFAULT                                   ;
desc.BindFlags                              = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT   ;
desc.CPUAccessFlags                         = 0                                                     ;
desc.MiscFlags                              = 0                                                     ;
desc.StructureByteStride                    = 0                                                     ;

D3D11_SUBRESOURCE_DATA  sub_resource_data   = { 0 }                                                 ;
sub_resource_data.pSysMem                   = vertex_list.data()                                    ;
sub_resource_data.SysMemPitch               = 0                                                     ;
sub_resource_data.SysMemSlicePitch          = 0                                                     ;

// 1つめ
hr = device->CreateBuffer(&desc, &sub_resource_data, &vertex_buffer_[0]);
if (FAILED(hr)) {
    return hr;
}
// 2つめ
hr = device->CreateBuffer(&desc, &sub_resource_data, &vertex_buffer_[1]);
if (FAILED(hr)) {
    return hr;
}

D3D11_BIND_STREAM_OUTPUTを指定することで書き込めるようになる

ジオメトリシェーダ作成

ID3D11Device *device;
device_context->GetDevice( &device );

D3D11_SO_DECLARATION_ENTRY desc[] = { 
    { 0, "SV_POSITION"  ,  0, 0, 4, 0 },
    { 0, "NORMAL"       ,  0, 0, 4, 0 },
    { 0, "COLOR"        ,  0, 0, 4, 0 },
    { 0, "TANGENT"      ,  0, 0, 4, 0 },
    { 0, "TEXCOORD"     ,  0, 0, 4, 0 },
    { 0, "TEXCOORD"     ,  1, 0, 4, 0 },
    { 0, "TEXCOORD"     ,  2, 0, 4, 0 },
    { 0, "TEXCOORD"     ,  3, 0, 4, 0 },
};

UINT stride[] = { sizeof( VertexLayout ) };

// 頂点バッファ更新用のジオメトリシェーダ
HRESULT hr = device->CreateGeometryShaderWithStreamOutput(
    &g_gs_particle_update_vertex, sizeof(g_gs_particle_update_vertex) , 
    desc                , (UINT)array_length(desc)   ,
    stride              , (UINT)array_length(stride) ,
    D3D11_SO_NO_RASTERIZED_STREAM,
    NULL, 
    &geometry_shader_update_vertex_
);
if (FAILED(hr)) {
    return hr;
}
// 描画用のジオメトリシェーダ
hr = device->CreateGeometryShader(
    &g_gs_particle_test, sizeof(g_gs_particle_test), 
    NULL, 
    &geometry_shader_
);
if (FAILED(hr)) {
    return hr;
}
return hr;

ジオメトリシェーダ(更新用)

// ジオメトリシェーダーの入力パラメータ
struct VS_OUT
{
    float4    pos             : SV_POSITION;
    float4    normal          : NORMAL0   ;
    float4    color           : COLOR0    ;
    float4    tangent         : TANGENT   ;
    float4    uv0             : TEXCOORD0 ;
    float4    uv1             : TEXCOORD1 ;
    float4    joint0          : TEXCOORD2 ;
    float4    weight0         : TEXCOORD3 ;
    float     depth           : DEPTH0    ;
};
typedef VS_OUT GS_IN;

// ジオメトリシェーダーの出力パラメータ
struct GS_OUT
{
    float4    pos             : SV_POSITION;
    float4    normal          : NORMAL0   ;
    float4    color           : COLOR0    ;
    float4    tangent         : TANGENT   ;
    float4    uv0             : TEXCOORD0 ;
    float4    uv1             : TEXCOORD1 ;
    float4    joint0          : TEXCOORD2 ;
    float4    weight0         : TEXCOORD3 ;
    float     depth           : DEPTH0    ;
};

[maxvertexcount(1)]
void main(
    point GS_IN input[1], 
    inout PointStream< GS_OUT > output
)
{
    // 1頂点から三角形2つだして四角い画像を表示する    
    GS_OUT element      = (GS_OUT)0        ;
    element.normal      = input[0].normal  ;
    element.tangent     = input[0].tangent ;
    element.uv0         = input[0].uv0     ;
    element.uv1         = input[0].uv1     ;
    element.color       = input[0].color   ;
    element.joint0      = input[0].joint0  ;
    element.weight0     = input[0].weight0 ;
    element.depth       = input[0].depth   ;
    element.pos         = input[0].pos     ;

    float time          = input[0].uv1[2]   ;
    float time_limit    = input[0].uv1[3]   ;

    //ここのif文なくしたい
    if( time > time_limit )
    {
        float rand     = input[0].uv1[0]        ;
        time_limit     = 10.0f + rand * 50.0f   ;
        element.uv1[3] = time_limit             ;
        element.uv1[2] = 0.0f                   ;
    }
    else
    {
        element.uv1[2] = time + 1.0f;
    }

    output.Append(element);
}

if文あるとどちらも実行されてしまうので消したいけどどうしよう

ジオメトリシェーダ(描画用)

// ジオメトリシェーダーの入力パラメータ
struct VS_OUT
{
    float4    pos             : SV_POSITION;
    float4    normal          : NORMAL0   ;
    float4    color           : COLOR0    ;
    float4    tangent         : TANGENT   ;
    float4    uv0             : TEXCOORD0 ;
    float4    uv1             : TEXCOORD1 ;
    float4    joint0          : TEXCOORD2 ;
    float4    weight0         : TEXCOORD3 ;
    float     depth           : DEPTH0    ;
};
typedef VS_OUT GS_IN;

// ジオメトリシェーダーの出力パラメータ
struct GS_OUT
{
    float4    pos             : SV_POSITION;
    float4    normal          : NORMAL0   ;
    float4    color           : COLOR0    ;
    float4    tangent         : TANGENT   ;
    float4    uv0             : TEXCOORD0 ;
    float4    uv1             : TEXCOORD1 ;
    float4    joint0          : TEXCOORD2 ;
    float4    weight0         : TEXCOORD3 ;
    float     depth           : DEPTH0    ;
};


[maxvertexcount(6)]
void main(
    point GS_IN input[1], 
    inout TriangleStream< GS_OUT > output
)
{
    // 1頂点から三角形2つだして四角い画像を表示する    
    GS_OUT element     = (GS_OUT)0           ;
    element.normal     = input[0].normal     ;
    element.tangent    = input[0].tangent    ;
    element.uv0        = input[0].uv0        ;
    element.uv1        = input[0].uv1        ;
    element.color      = input[0].color      ;
    element.joint0     = input[0].joint0     ;
    element.weight0    = input[0].weight0    ;
    element.depth      = input[0].depth      ;

    float4 size = float4( 0.1f, 0.1f, 0.0f, 1.0f );

    // 1つめ
    element.pos     = input[0].pos + float4(-1.0f, 1.0f, 0.0f, 0.0f ) * size;
    element.uv0     = float4(0.0f, 0.0f, 0.0f, 0.0f);
    output.Append(element);
    element.pos     = input[0].pos + float4( 1.0f, 1.0f, 0.0f, 0.0f ) * size;
    element.uv0     = float4(1.0f, 0.0f, 0.0f, 0.0f);
    output.Append(element);
    element.pos     = input[0].pos + float4(-1.0f,-1.0f, 0.0f, 0.0f ) * size;
    element.uv0     = float4(0.0f, 1.0f, 0.0f, 0.0f);
    output.Append(element);
    // 2つめ
    element.pos     = input[0].pos + float4( 1.0f, 1.0f, 0.0f, 0.0f ) * size;
    element.uv0     = float4(1.0f, 0.0f, 0.0f, 0.0f);
    output.Append(element);
    element.pos     = input[0].pos + float4( 1.0f,-1.0f, 0.0f, 0.0f ) * size;
    element.uv0     = float4(1.0f, 1.0f, 0.0f, 0.0f);
    output.Append(element);
    element.pos     = input[0].pos + float4(-1.0f,-1.0f, 0.0f, 0.0f ) * size;
    element.uv0     = float4(0.0f, 1.0f, 0.0f, 0.0f);
    output.Append(element);
}

コメントにも書いてるけど1頂点を6個に増やして四角形に

頂点更新

    // 読み込みと書き込み先のバッファを入れ替える
    auto temp         = vertex_buffer_[0]   ;
    vertex_buffer_[0] = vertex_buffer_[1]   ;
    vertex_buffer_[1] = temp                ;

    // 読み込み頂点バッファ設定
    ID3D11Buffer* read_buffers[1] = { vertex_buffer_[0] }                   ;
    UINT          stride[1]       = { sizeof(VertexLayout) }                ;
    UINT          offset[1]       = { 0 }                                   ;
    device_context.IASetVertexBuffers( 0, 1, read_buffers, stride, offset ) ;

    // 書き込み頂点バッファ設定
    ID3D11Buffer* write_buffers[] = { vertex_buffer_[1] }  ;
    device_context.SOSetTargets( 1, write_buffers, offset );

    // 描画はしないのでピクセルシェーダはOFF
    device_context.VSSetShader( vertex_shader_                , NULL, 0 ) ;
    device_context.GSSetShader( geometry_shader_update_vertex_, NULL, 0 ) ;// 更新用
    device_context.PSSetShader( NULL                          , NULL, 0 ) ;

    if( is_first )
    {
        device_context.Draw( (UINT)vertex_list_.size(), 0 );
        is_first = false;
    }         
    else
    {
        // 2回目移行は処理する頂点数が不明なのでDrawAuto()で描画する
        device_context.DrawAuto();
    }

    // 更新後はターゲット出力バッファを無効に
    ID3D11Buffer* null_buffers[] = { NULL };
    device_context.SOSetTargets( 1, null_buffers, NULL );

この関数を呼ぶたびに2つある頂点バッファの読み込みと書き込み先が逆転する

描画

    // update関数で書き込んだバッファを描画する
    ID3D11Buffer* read_buffers[1] = { vertex_buffer_[1] }                   ;
    UINT          stride[1]       = { sizeof(VertexLayout) }                ;
    UINT          offset[1]       = { 0 }                                   ;
    device_context.IASetVertexBuffers( 0, 1, read_buffers, stride, offset ) ;

    // シェーダ設定
    device_context.VSSetShader( vertex_shader_  , NULL, 0 ) ;
    device_context.GSSetShader( geometry_shader_, NULL, 0 ) ; // 描画用
    device_context.PSSetShader( pixel_shader_   , NULL, 0 ) ;

    // 描画
    device_context.DrawAuto();

結果


あとがき

記事書いてる最中にコードの間違いに結構気付かされた

記事が少しでもいいなと思ったらクラップを送ってみよう!
18
+1
@esYgFPm2miKw4VJBの技術ブログ

よく一緒に読まれている記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる

技術ブログをはじめよう

Qrunch(クランチ)は、ITエンジニアリングに携わる全ての人のための技術ブログプラットフォームです。

技術ブログを開設する

Qrunchでアウトプットをはじめよう

Qrunch(クランチ)は、ITエンジニアリングに携わる全ての人のための技術ブログプラットフォームです。

Markdownで書ける

ログ機能でアウトプットを加速

デザインのカスタマイズが可能

技術ブログ開設

ここから先はアカウント(ブログ)開設が必要です

英数字4文字以上
.qrunch.io
英数字6文字以上
ログインする