ストリーム出力を使用する頂点シェーダーではなく、計算シェーダーを使用してボーン変形をメッシュ頂点に適用することを検討しています。コンピューティング シェーダーの実行が頂点シェーダーよりもはるかに遅いことがわかりましたが、書き留める前に、何か間違ったことをしていないことを確認したいと思います。
100,000 個の頂点と 300 個のボーンの 1,000 フレームのアニメーション データのテスト データを使用すると、頂点シェーダーは約 0.22 ミリ秒で実行され、計算シェーダーは 4 倍の 0.85 ミリ秒かかります。タイミングは、(CPU タイマーではなく) D3D API タイマー クエリを介して行われます。
変形構造体.hlsl
struct Vertex {
float3 position : POSITION;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD;
float3 tangent : TANGENT;
float4 color : COLOR;
};
struct BoneWeights {
uint index;
float weight;
};
StructuredBuffer<matrix> g_bone_array : register(t0);
Buffer<uint> g_bone_offsets : register(t1);
Buffer<uint> g_bone_counts : register(t2);
StructuredBuffer<BoneWeights> g_bone_weights : register(t3);
bone_deform_cs.hlsl
#include "deform_structs.hlsl"
StructuredBuffer<Vertex> g_input_vertex : register(t4);
RWStructuredBuffer<Vertex> g_output_vertex : register(u0);
[numthreads(64,1,1)]
void BoneDeformCS(uint id : SV_DispatchThreadID) {
Vertex vert = g_input_vertex[id.x];
uint offset = g_bone_offsets[id.x];
uint count = g_bone_counts[id.x];
matrix bone_matrix = 0;
for (uint i = offset; i < (offset + count); ++i) {
BoneWeights weight_info = g_bone_weights[i];
bone_matrix += weight_info.weight * g_bone_array[weight_info.index];
}
vert.position = mul(float4(vert.position,1), bone_matrix).xyz;
vert.normal = normalize(mul(vert.normal, (float3x3)bone_matrix));
vert.tangent = normalize(mul(vert.tangent, (float3x3)bone_matrix));
g_output_vertex[id.x] = vert;
}
bone_deform_vs.hlsl
#include "deform_structs.hlsl"
void BoneDeformVS(uint id : SV_VertexID, Vertex vsin, out Vertex vsout) {
uint offset = g_bone_offsets[id];
uint count = g_bone_counts[id];
matrix bone_matrix = 0;
for (uint i = offset; i < (offset + count); ++i) {
BoneWeights bone_info = g_bone_weights[i];
bone_matrix += bone_info.weight * g_bone_array[bone_info.index];
}
vsout.position = mul(float4(vsin.position,1), bone_matrix).xyz;
vsout.normal = normalize(mul(vsin.normal, (float3x3)bone_matrix));
vsout.tangent = normalize(mul(vsin.tangent, (float3x3)bone_matrix));
vsout.texcoord = vsin.texcoord;
vsout.color = vsin.color;
}
実行後のバッファの内容を比較すると、それらは同一であり、期待される値が含まれています。
計算シェーダーを間違って実行し、スレッドを生成しすぎているのではないでしょうか? 渡す番号がDispatch
間違っていますか?これは 1 次元のデータ行であるため、 を使用するのが理にかなっています[numthreads(64,1,1)]
。32 ~ 1024 のさまざまな値を試しました。64 は、AMD GPU を効率的に使用するために必要な最小値であるため、スイート スポットのようです。ともかく。を呼び出すとDispatch
、実行するように要求し(vertex_count / 64) + (vertex_count % 64 != 0) ? 1 : 0
ます。100,000 個の頂点の場合、呼び出しは になりDispatch(1563,1,1)
ます。
ID3D11ShaderResourceView * srvs[] = {bone_array_srv, bone_offset_srv,
bone_count_srv, bone_weights_srv,
cs_vertices_srv};
ID3D11UnorderedAccessView * uavs[] = {cs_output_uav};
UINT srv_count = sizeof(srvs) / sizeof(srvs[0]);
UINT uav_count = sizeof(uavs) / sizeof(uavs[0]);
UINT thread_group_count = vertex_count / 64 + (vertex_count % 64 != 0) ? 1 : 0;
context->CSSetShader(cs, nullptr, 0);
context->CSSetShaderResources(0, srv_count, srvs);
context->CSSetUnorderedAccessViews(0, uav_count, uavs);
context->Dispatch(thread_group_count, 1, 1);
これが頂点シェーダーの実行方法です。
ID3D11ShaderResourceView * srvs[] = {bone_array_srv, bone_offset_srv,
bone_count_srv, bone_weights_srv};
UINT srv_count = sizeof(srvs) / sizeof(srvs[0]);
UINT stride = 0;
UINT offset = 0;
context->GSSetShader(streamout_gs, nullptr, 0);
context->VSSetShader(vs, nullptr, 0);
context->VSSetShaderResources(0, srv_count, srvs);
context->SOSetTargets(1, &vs_output_buf, &offset);
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
context->IASetInputLayout(vs_input_layout);
context->IASetVertexBuffers(0, 1, &vs_vertices, &stride, &offset);
context->Draw(vertex_count, 0);
それとも、シェーダー リソース ビューからの読み取りと順序付けされていないアクセス ビューへの書き込みは、頂点バッファーからの読み取りとストリーム出力バッファーへの書き込みよりもはるかに遅いというだけでしょうか?