動作例とリポジトリ

https://github.com/kantamRobo/D3D12MA_DirectXTK12Polygon/tree/master/D3D12MA_DirectXTK12_DXR_Assimp_Basic
動作環境及び使用ツール
CPU AMD Ryzen9 5950x
GPU NVIDIA RTX 4070ti Super 16GB
DRAM DDR4-12800 128GB
ChatGPT5.5 high:デバッグ
GitHub Copilot Agent (Claude Opus 4.6):コード生成
D3D12 Memory Allocator
※実際にプログラムを動かすだけならハードウェアの性能はこんなに必要ありません
NVIDIA RTX GPUがあれば十分です
前置き
こちらのプログラムを動作確認したのは 2026年6月初頭です
このプログラムでは
DirectX Tool Kit for DirectX12
と
D3D12 Memory Allocator
https://gpuopen.com/d3d12-memory-allocator/
Open Asset Import library (Assimp)
https://github.com/assimp/assimp
を使用します
D3D12MAの使用方法については別記事にて記述してあります
AssimpはVisual Studio(note Code)であればNugetからインストールが可能です
レイトレーシングパイプラインの確認
DXRで使用されるレイトレーシングパイプラインは複雑な形をしている
(特にDXR1.0)
覚えようとせずいつでも参照できるようにした方がよい。
https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html
こちらがそのパイプラインになる。パイプラインというか、からくりじゃないか
このパイプラインの特徴としては
・頂点シェーダー、ジオメトリシェーダー、ハルシェーダー、ドメインシェーダーの各ステージは存在しない
・入力アセンブラも存在しない(頂点シェーダーが存在しないため)
・Missシェーダー、ClosetHitシェーダー,RayGenシェーダーの存在
各フレームごとにRayGenを起点に、ClosetHitシェーダーかMissシェーダーが順不同に呼ばれる。
RayGenが起点であることには変わらないが、後者2つのどちらかが呼ばれるかは実質ランダムであることに注意する(DXR1.2では比較的ランダム性が緩和されてはいる)
・ペイロードの存在
このデータがレイによるサンプリング結果を格納、UAVを通じてアプリ側のバックバッファに書き込まれる
Assimpの初期化
vertices = GenerateVertices();
bool Model::LoadModel(const char* path)
{
vertices = GenerateVertices();
// スケール値を設定
float scaleFactor = 10.0f;
for (int i = 0; i < vertices.size(); i++)
{
//乗算 vertices[i].position = vertices[i].position,100.0f;
}
return true;
}
std::vector<DirectX::VertexPositionNormalColorTexture> Model::GenerateVertices()
{
std::vector< DirectX::VertexPositionNormalColorTexture> outvertices;
outvertices.clear();
for (unsigned int i = 0; i < m_scene->mNumMeshes; i++)
{
aiMesh* mesh = m_scene->mMeshes[i];
for (unsigned int j = 0; j < mesh->mNumVertices; j++)
{
DirectX::VertexPositionNormalColorTexture vertex = {};
aiVector3D pos = mesh->mVertices[j];
vertex.position = { pos.x , pos.y , pos.z };
vertex.normal = { mesh->mNormals[j].x, mesh->mNormals[j].y, mesh->mNormals[j].z };
if (mesh->mTextureCoords[0])
{
vertex.textureCoordinate.x = mesh->mTextureCoords[0][j].x;
vertex.textureCoordinate.y = mesh->mTextureCoords[0][j].y;
}
else
{
vertex.textureCoordinate.x = 0.0f;
vertex.textureCoordinate.y = 0.0f;
}
outvertices.push_back(vertex);
}
// インデックスの設定
for (unsigned int j = 0; j < mesh->mNumFaces; j++)
{
aiFace face = mesh->mFaces[j];
for (unsigned int k = 0; k < face.mNumIndices; k++)
{
indices.push_back(face.mIndices[k]);
}
}
}
return outvertices;
}
Assimpで行うのは
3Dファイルを元にaiSceneオブジェクトの生成
そのaiSceneオブジェクトから頂点データの抽出・バッファとして格納する予定の
頂点データにデータをコピー
どうしてAssimp側のデータをそのまま使わないの?
aiVector3Dはコピーする頂点データの中に含まれているDirectXMathを使ったデータのように、D3D→HLSLにデータ用にSIMDを使った最適化を施していないので、実用上
速度の恩恵を得られない
※今回は教育用のコードなので、実際は特に問題はないとは思うが・・・
次に頂点・インデックスバッファの作成を行う
頂点・インデックスバッファの作成
// ===========================================================================
// BuildGeometry
// Uses DirectXTK12 GeometricPrimitive::CreateSphere to get vertex/index data,
// then uploads position+normal into D3D12MA UPLOAD-heap buffers.
// ===========================================================================
void Model:BuildGeometry(DX::DeviceResources* DR)
{
auto device = DR->GetD3DDevice();
m_vertexCount = static_cast<UINT>(vertices.size());
m_indexCount = static_cast<UINT>(indices.size());
// ----- Vertex buffer (D3D12MA UPLOAD heap) -----
{
UINT64 vbSize = sizeof(DirectX::VertexPositionNormalColorTexture) * m_vertexCount;
D3D12MA::CALLOCATION_DESC allocDesc(D3D12_HEAP_TYPE_UPLOAD,
D3D12MA::ALLOCATION_FLAG_NONE);
auto resDesc = CD3DX12_RESOURCE_DESC::Buffer(vbSize);
DX::ThrowIfFailed(m_allocator->CreateResource(
&allocDesc, &resDesc,
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
&m_vertexAllocation, IID_PPV_ARGS(&m_vertexBuffer)));
m_vertexBuffer->SetName(L"SphereVertexBuffer");
void* pData;
CD3DX12_RANGE readRange(0, 0);
DX::ThrowIfFailed(m_vertexBuffer->Map(0, &readRange, &pData));
memcpy(pData, vertices.data(), static_cast<size_t>(vbSize));
m_vertexBuffer->Unmap(0, nullptr);
}
// ----- Index buffer (D3D12MA UPLOAD heap) -----
{
UINT64 ibSize = sizeof(uint16_t) * m_indexCount;
D3D12MA::CALLOCATION_DESC allocDesc(D3D12_HEAP_TYPE_UPLOAD,
D3D12MA::ALLOCATION_FLAG_NONE);
auto resDesc = CD3DX12_RESOURCE_DESC::Buffer(ibSize);
DX::ThrowIfFailed(m_allocator->CreateResource(
&allocDesc, &resDesc,
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
&m_indexAllocation, IID_PPV_ARGS(&m_indexBuffer)));
m_indexBuffer->SetName(L"SphereIndexBuffer");
void* pData;
CD3DX12_RANGE readRange(0, 0);
DX::ThrowIfFailed(m_indexBuffer->Map(0, &readRange, &pData));
memcpy(pData, indices.data(), static_cast<size_t>(ibSize));
m_indexBuffer->Unmap(0, nu)llptr);
}
}
頂点バッファとインデックスバッファはD3D12MAで生成する
なお、今回の頂点・インデックスバッファは従来のレンダリングパイプラインと違って
シェーダーリソースとしてシェーダー側にバインドをする
テクスチャだけがシェーダーリソースというわけではないので、注意。
頂点?
図形を構成するための点。
これを結び合わせると図形ができる
インデックス?
複数の図形からできている3Dモデルは、どうしても図形同士で頂点がダブることがある
そのため、たとえば2つの三角形から成り立つ四角形だと必ずひとつ辺や頂点をつかうことになるのだが、その描画時にそれらの描画命令コストが無駄になるのだ(Direct3Dは描画命令を3Dモデル自体ではなく、基本は1つの三角ポリゴンに対して出すことが多い(というかいっぺんに立方体をどーん!と描くというのは原理上できない。GPUに閉口計算させて瞬時に全部やっているので、そう見えるだけである)
それを防止するためにIDで管理して、ダブった頂点は同じメモリ領域を使いまわす。
それを行うのがインデックスとなる
