🎮

C++でPhysXを使ってみる

2023/06/16に公開

こんにちは。初めまして"しろくま"と申します。Zenn初投稿となります🎉

はじめに

今回は神戸電子専門学校内で開催したセミナーの内容を再編、解説したものを公開したいと思います。
今回は"使ってみる"ということで、実際にC++プロジェクト内でシミュレーションをするところまでと、ちょっとしたWrapperを紹介したいと思います。

物理エンジン

「あっ、ゲームに物理エンジンを導入したいな」って思ったことありますよね。けれど昨今ゲームエンジンでの開発が主流の中、ゲームエンジン自体に組み込まれていることが標準であり、コンポーネントを追加するだけで挙動を再現できたりします。しかしゲームエンジンを開発する人やDirectXAPIを直接叩いてゲーム開発をしている人(弊学)もまだまだいます。なので今回は無料でオープンソースでもあるPhysXをC++プロジェクトに導入して動作させてみたいと思います。

PhysXとは?

  • NVIDIAが開発した物理演算ライブラリ
  • 昔からあり名立たるゲームやエンジンに組み込まれており実績豊富
  • 4.x系(+3.4)からBSD3Licenseでオープンソース化

他のオープンソースなC++の物理演算ライブラリは以下のようなものがあります。

それぞれ長所短所あるので、自分の環境に合ったライブラリを選定してください。
ほかにもUnityを使ってるならOSSではないですがHavokなども有名ではないでしょうか。

ビルド環境

Windows 10
Visutal Studio 2022 (VC17)
Python 3.9.11 (Required 3.5 higher)
CMake 3.23.1

ビルド手順

https://github.com/NVIDIA-Omniverse/PhysX

GithubのReleaseからダウンロードします。
今回はBlastやFlowは使いませんがそのままSource codeをダウンロードします。
現在のバージョンは5.1.3でした。

展開するとphysxフォルダがあるので、その中のgenerate_projects.batを実行します。
しばらく経つとどのプリセットを使うか聞かれるので、自分の環境にあったプリセットの番号を入力してEnterを押します。Windows 10/64bit/Visual Studio 2022なら2を選択すれば良いはずです。またしばらく経つと処理が終了するので、これでプロジェクトの生成は完了です。

compiler/コンパイラの種類/
e.g. compiler/vc17win64/

にプロジェクトが生成されているのでその中のPhysXSDK.slnを起動します。

ソリューションエクスプローラーのルートにあるソリューションを右クリックしビルドをクリックするとビルドが始まります。

debugrelease両方ビルドしておきましょう。

ビルドが終了すると

physx/bin/ビルドの種類/
e.g. physx/bin/win/win.x86_64.vc143.mt/

に配置されています。

ここからはビルド済みライブラリファイルの配置ですが、今回は自分で統一しているやり方を紹介します。

  1. プロジェクトとは別の場所にライブラリフォルダを作成
    今回は{CPP_LIB}/PhysX/PhysX-5.1.3/の下に配置していきます。
  2. 不要なファイルの削除
    今回のビルド方法でビルドされた中にあるSnippet~と始まるファイルは全てサンプルのビルドです。
    今回は不要なので全て削除します。
  3. *.dllなどの動的ライブラリや関連するファイルはbinに配置
  4. *.lib,,*.pdbなどの静的ライブラリや関連するファイルはlibに配置
  5. physx/includeにある*.hなどのインクルードファイルはファイル構成を変えずそのままincludeに配置

{CPP_LIB}/PhysX/PhysX-5.1.3/bin/x64/Debug/*.dll
{CPP_LIB}/PhysX/PhysX-5.1.3/bin/x64/Release/*.dll
{CPP_LIB}/PhysX/PhysX-5.1.3/include/*.h
{CPP_LIB}/PhysX/PhysX-5.1.3/lib/x64/Debug/*.lib,*.pdb
{CPP_LIB}/PhysX/PhysX-5.1.3/lib/x64/Release/*.lib,*.pdb

最終的なファイル構成はこうなっています。
{CPP_LIB}は環境変数を割り当てているだけで絶対/相対パスでも大丈夫です。

最後にクリーンアップですが、不要な方はスキップしてもらって構いません。今後ビルドすることがないなら削除も可能です。

  • Cドライブ直下に依存関係が入ったpackman-repoが生成されているので削除
  • ユーザー環境変数PM_PACKAGES_ROOTが追加されているので削除

以上でビルド手順は完了です。

プロジェクトに導入

任意のプロジェクト(ソリューション)を開いてプロジェクトのプロパティを開きます。
C/C++全般追加のインクルードディレクトリ
{CPP_LIB}/PhysX/PhysX-5.1.3/includeを追加
リンカー全般追加のライブラリディレクトリ
{CPP_LIB}/PhysX/PhysX-5.1.3/lib/$(Platform)/$(Configuration)を追加

以上でプロジェクトに導入は完了です。

コーディング序章

正常に導入できているかどうか確認してみます。Pch.hやmain.cppなどで以下のコードを追加してビルドしてみてください。

#include "PxPhysicsAPI.h"
#pragma comment(lib, "PhysX_64.lib")
#pragma comment(lib, "PhysXCommon_64.lib")
#pragma comment(lib, "PhysXCooking_64.lib")
#pragma comment(lib, "PhysXExtensions_static_64.lib")
#pragma comment(lib, "PhysXFoundation_64.lib")
#pragma comment(lib, "PhysXPvdSDK_static_64.lib")
#pragma comment(lib, "PhysXTask_static_64.lib")
#pragma comment(lib, "SceneQuery_static_64.lib")
#pragma comment(lib, "SimulationController_static_64.lib")

こちらを書いた状態でビルドが通っていれば成功です!

FAQ

  • E0020 識別子"Identifier"が定義されていません。
    インクルードが正しくできていない可能性があります。追加のインクルードディレクトリのパス、ファイル名が正しくない可能性があります。
  • LNK2038 '_ITERATOR_DEBUG_LEVEL'の不一致が検出されました。値'0'が2の値'main.obj'と一致しません。
    ライブラリの種類(Debug/Release)がプロジェクトの設定との不一致が考えられます。
  • LNK2038 'RuntimeLibrary'の不一致が検出されました。値'MTd_StaticDebug'がMDd_DynamicDebugの値'main.obj'と一致しません。
    ライブラリの種類(MultiThread/MultiThreadDebug)がプロジェクトの設定との不一致が考えられます。
  • LNK2019 未解決の外部シンボル"SymbolName"(Call)で参照されました。
    リンカーが正しくできていない可能性があります。追加のライブラリディレクトリのパス、ファイル名が正しくない可能性があります。

PhysX Visual Debugger(PVD)の紹介

さっそくコーディングを行っていきたいところですが、プロジェクトによっては描画ライブラリをまだ導入していないプロジェクトもあるかもしれません。よって物理演算後の挙動が視覚的に確認できないのかといえばそうではありません。NVIDIAは外部の可視化ツールも用意してくれています。

https://developer.nvidia.com/physx-visual-debugger

ダウンロードにはNVIDIA Developer Account(無料)が必要です。
また、リンク先を飛んでもダウンロード可能リストの中に埋もれているのでブラウザ内検索でPhysX Visual Debuggerと検索して一番上に出てくる項目をダウンロードしてインストールしてください。今現在の最新バージョンは3.2021.01.29532135でした。

初期化

実際にコーディングしていきます。まずメンバ変数です。

// PhysX内で利用するアロケーター
physx::PxDefaultAllocator m_defaultAllocator;
// エラー時用のコールバックでエラー内容が入ってる
physx::PxDefaultErrorCallback m_defaultErrorCallback;
// 上位レベルのSDK(PxPhysicsなど)をインスタンス化する際に必要
physx::PxFoundation* m_pFoundation = nullptr;
// 実際に物理演算を行う
physx::PxPhysics* m_pPhysics = nullptr;
// シミュレーションをどう処理するかの設定でマルチスレッドの設定もできる
physx::PxDefaultCpuDispatcher* m_pDispatcher = nullptr;
// シミュレーションする空間の単位でActorの追加などもここで行う
physx::PxScene* m_pScene = nullptr; 
// PVDと通信する際に必要
physx::PxPvd* m_pPvd = nullptr;

以上が最低限必要なメンバ変数になります。
次にインスタンス化などを行っていきます。

// Foundationのインスタンス化
if (m_pFoundation = PxCreateFoundation(PX_PHYSICS_VERSION, m_defaultAllocator, m_defaultErrorCallback); !m_pFoundation) {
    return false;
}
// PVDと接続する設定
if (m_pPvd = physx::PxCreatePvd(*m_pFoundation); m_pPvd) {
    // PVD側のデフォルトポートは5425
    physx::PxPvdTransport* transport = physx::PxDefaultPvdSocketTransportCreate("127.0.0.1", 5425, 10);
    m_pPvd->connect(*transport, physx::PxPvdInstrumentationFlag::eALL);
}
// Physicsのインスタンス化
if (m_pPhysics = PxCreatePhysics(PX_PHYSICS_VERSION, *m_pFoundation, physx::PxTolerancesScale(), true, m_pPvd); !m_pPhysics) {
    return false;
}
// 拡張機能用
if (!PxInitExtensions(*m_pPhysics, m_pPvd)) {
    return false;
}
// 処理に使うスレッドを指定する
m_pDispatcher = physx::PxDefaultCpuDispatcherCreate(8);
// 空間の設定
physx::PxSceneDesc scene_desc(m_pPhysics->getTolerancesScale());
scene_desc.gravity = physx::PxVec3(0, -9, 0);
scene_desc.filterShader = physx::PxDefaultSimulationFilterShader;
scene_desc.cpuDispatcher = m_pDispatcher;
// 空間のインスタンス化
m_pScene = m_pPhysics->createScene(scene_desc);
// PVDの表示設定
physx::PxPvdSceneClient* pvd_client;
if (pvd_client = m_pScene->getScenePvdClient(); pvd_client) {
    pvd_client->setScenePvdFlag(physx::PxPvdSceneFlag::eTRANSMIT_CONSTRAINTS, true);
    pvd_client->setScenePvdFlag(physx::PxPvdSceneFlag::eTRANSMIT_CONTACTS, true);
    pvd_client->setScenePvdFlag(physx::PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES, true);
}

また、デストラクタかどこかに解放する関数も書いておきましょう。忘れるとそのままMemoryLeakになります。

PxCloseExtensions();
m_pScene->release();
m_pDispatcher->release();
m_pPhysics->release();
if (m_pPvd) {
    m_pPvd->disconnect();
    physx::PxPvdTransport* transport = m_pPvd->getTransport();
    m_pPvd->release();
    transport->release();
}
m_pFoundation->release();

解放する順番はインスタンス化の順番とは逆の順番にしてください。

次はループ処理の部分に書きます

// シミュレーション速度を指定する
m_pScene->simulate(1.f / 60.f);
// PhysXの処理が終わるまで待つ
m_pScene->fetchResults(true);

実行

構文が合っているなら、このままビルドは成功するとは思いますが実行は失敗すると思います。エラー文がポップアップで出てきた場合はそのエラー文を読んでもらうと理由が一発で分かると思います。前回のビルド時に用意したbin/*.dllの方を利用していません。実行するには*.dll全てをプロジェクトディレクトリかexeと同ディレクトリに配置する必要があります。

ビルドも成功し実行もできたら一度閉じて、PVDを起動してからもう一度実行してみてください。何か変化があると思います。

このようにグリッド状の平面が生成され、右上のフレームカウントが増えていったら初期化等は成功しています。

StaticRigidBodyの追加

続いてRigidBody(剛体)と呼ばれるものを追加します。これは初期時に設定した形状が不変でありながら衝突判定を行い物理挙動を行うものを指します。要は固いものの物理挙動を行うのに必要なものです。また、形状を設定するのにはShapeを生成しAttachする必要があります。

// 絶対に動くことのない(静的)剛体を作成
physx::PxRigidStatic* rigid_static
    = m_pPhysics->createRigidStatic(physx::PxTransform(physx::PxIdentity));
// 形状(Box)を作成
physx::PxShape* box_shape
    = m_pPhysics->createShape(
	// Boxの大きさ
        physx::PxBoxGeometry(1.f, 1.f, 1.f),
	// 摩擦係数と反発係数の設定
	*m_pPhysics->createMaterial(0.5f, 0.5f, 0.5f)
    );
// 形状のローカル座標を設定
box_shape->setLocalPose(physx::PxTransform(physx::PxIdentity));
// 形状を紐づけ
rigid_static->attachShape(*box_shape);
// 剛体を空間に追加
m_pScene->addActor(*rigid_static);

これで実行してみてください。

このようにPVD上で赤色のBoxが生成されたことが確認できると思います。

PxActorについて解説

ここで新しい単語PxActorが出てきましたが、これはPxRigidStaticPxRigidDynamicの基底クラスなので共通で代入することのできる型です。基底クラスにはis<Type>()というdynamic_castのような関数があり、Typeと派生クラスが一致していたらダウンキャストすることもできます。私が制作で利用していた際には、剛体としてPxActorを持ち必要に応じてキャストしていましたが、それぞれメンバ変数として分けて持つかどうかは処理コストや設計と相談してください。この解説ではPxActorとしていますが、コーディングの方では分けて説明します。(ややこしくてすいません)

剛体を生成する流れとして、空の剛体に形状を設定していく流れになります。なので一つの剛体に形状を複数設定(Attach)することもできます。
また、Shapeにも設定がいくつかあります。主に利用するパラメータは

実際の形状(Px{形状}Geometry(大きさとか半径とか形状による))
+
材質(PxMaterial(摩擦係数&反発係数))
+
質量など

です。これを

attachShape(Shape, ローカル座標(PxTransform(座標と回転)))

のようにして様々なパラメータを持ったShapeを違う座標に複数設定し一つのActorとして保持することができます。

DynamicRigidBodyの追加

これだけだと寂しいので次は動的に動かせる剛体を作成してみたいと思います。
と言ってもStaticと書かれている部分をDynamicに変更するだけです。

// 動かすことのできる(動的)剛体を作成
physx::PxRigidDynamic* rigid_dynamic
    = m_pPhysics->createRigidDynamic(physx::PxTransform(physx::PxIdentity));
// 形状(Box)を作成
physx::PxShape* box_shape
    = m_pPhysics->createShape(
        // Boxの大きさ
        physx::PxBoxGeometry(1.f, 1.f, 1.f),
	// 摩擦係数と反発係数の設定
	*m_pPhysics->createMaterial(0.5f, 0.5f, 0.5f)
    );
// 形状を紐づけ
box_shape->setLocalPose(physx::PxTransform(physx::PxIdentity));
rigid_dynamic->attachShape(*box_shape);

// 剛体を空間に追加
m_pScene->addActor(*rigid_dynamic);

実行すると画像のように緑色のBoxが落下していく様子が確認できると思います。

これだけでは素っ気ないので力を与えてみます。

rigid_dynamic->addForce(physx::PxVec3(0, 10, 0), physx::PxForceMode::eIMPULSE);

これで実行した瞬間に上に跳ね上がったと思います。

https://ekulabo.com/force-mode

ForceModeについては、Unityですが動作は同じになるのでこちらが参考になると思います。

Math

PhysX内部では数学計算用にPxVec3PxQuatなどのPhysX独自のクラスを利用しています。これを利用する側がそのまま利用するのは不便なので変換する関数を用意する必要があります。私の制作では共通でDirectXTKにあるDirectX::SimpleMathを利用していたのでこれに相互変換する関数を紹介します。

/* DirectX Simple Math to PhysX math */

physx::PxMat44 ToPxMat44(const DirectX::SimpleMath::Matrix& mat) {
    return physx::PxMat44(
        physx::PxVec4(mat._11, mat._12, mat._13, mat._14),
        physx::PxVec4(mat._21, mat._22, mat._23, mat._24),
        physx::PxVec4(mat._31, mat._32, mat._33, mat._34),
        physx::PxVec4(mat._41, mat._42, mat._43, mat._44)
    );
}

physx::PxVec3 ToPxVec3(const DirectX::SimpleMath::Vector3& vec3) {
    return physx::PxVec3(vec3.x, vec3.y, vec3.z);
}

physx::PxVec4 ToPxVec4(const DirectX::SimpleMath::Vector4& vec4) {
    return physx::PxVec4(vec4.x, vec4.y, vec4.z, vec4.w);
}

physx::PxQuat ToPxQuat(const DirectX::SimpleMath::Quaternion& quat) {
    return physx::PxQuat(quat.x, quat.y, quat.z, quat.w);
}

physx::PxTransform ToPxTransform(const DirectX::SimpleMath::Vector3& position, const DirectX::SimpleMath::Vector3& rotation) {
    return physx::PxTransform(ToPxVec3(position), ToPxQuat(DirectX::SimpleMath::Quaternion::CreateFromYawPitchRoll(rotation * (DirectX::XM_PI / 180.0f))));
}
physx::PxTransform ToPxTransform(const DirectX::SimpleMath::Vector3& position, const DirectX::SimpleMath::Quaternion& quaternion) {
    return physx::PxTransform(ToPxVec3(position), ToPxQuat(quaternion));
}


/* PhysX math to DirectX Simple Math */

DirectX::SimpleMath::Matrix ToMatrix(const physx::PxMat44& px_mat44) {
    return DirectX::SimpleMath::Matrix(
        px_mat44.column0.x, px_mat44.column0.y, px_mat44.column0.z, px_mat44.column0.w,
        px_mat44.column1.x, px_mat44.column1.y, px_mat44.column1.z, px_mat44.column1.w,
        px_mat44.column2.x, px_mat44.column2.y, px_mat44.column2.z, px_mat44.column2.w,
        px_mat44.column3.x, px_mat44.column3.y, px_mat44.column3.z, px_mat44.column3.w
    );
}

DirectX::SimpleMath::Vector3 ToVector3(const physx::PxVec3& px_vec3) {
    return DirectX::SimpleMath::Vector3(px_vec3.x, px_vec3.y, px_vec3.z);
}

DirectX::SimpleMath::Vector4 ToVector4(const physx::PxVec4& px_vec4) {
    return DirectX::SimpleMath::Vector4(px_vec4.x, px_vec4.y, px_vec4.z, px_vec4.w);
}

DirectX::SimpleMath::Quaternion ToQuaternion(const physx::PxQuat& px_quat) {
    return DirectX::SimpleMath::Quaternion(px_quat.x, px_quat.y, px_quat.z, px_quat.w);
}

Geometryの種類

前回はBoxとPlaneしか利用しませんでしたが、他にもデフォルトで用意されているGeometryはいくつかあります。

  • Sphere(球体)
    • float radius
      physx::PxSphereGeometry(radius);
      
  • Capsule(カプセル)
    • float radius
    • float half_height
      physx::PxCapsuleGeometry(radius, half_height);
      
  • Box(箱)
    • Vector3 half_extent
      physx::PxBoxGeometry(half_extent);
      
  • Plane(平面)
    • None
      physx::PxPlaneGeometry();
      
  • Convex(凸メッシュ)
    • physx::PxConvexMesh* mesh
    • Vector3 scale
      physx::PxConvexMeshGeometry(mesh, physx::PxMeshScale(scale));
      
  • TriangleMesh(三角メッシュ)
    • physx::PxTriangleMesh* mesh
    • Vector3 scale
      physx::PxTriangleMeshGeometry(mesh, physx::PxMeshScale(scale));
      
  • HeightField(平面の凹凸あり版)
    • physx::PxHeightField* height_field
      physx::PxHeightFieldGeometry(height_field);
      

またPxRigidActorにAttachしたShapeを抽出することもできます。

std::vector<physx::PxShape*> GetShapes(physx::PxRigidActor* actor) {
    std::vector<physx::PxShape*> shapes;
    physx::PxU32 shape_count = actor->getNbShapes();
    physx::PxShape** ppshapes = new physx::PxShape*[shape_count];
    shapes.reserve(shape_count);

    actor->getShapes(ppshapes, shape_count);
    for (physx::PxU32 i = 0; i < shape_count; ++i) {
        shapes.push_back(ppshapes[i]);
    }
    delete[] ppshapes;
    return shapes;
}

Convex&TriangleMeshの作成

Convex

template<class VArray>
inline physx::PxConvexMesh* ToPxConvexMesh(physx::PxPhysics* physics, const VArray& vertices) {
    physx::PxTolerancesScale tolerances_scale;
    physx::PxCookingParams cooking_params(tolerances_scale);
    cooking_params.convexMeshCookingType = physx::PxConvexMeshCookingType::Enum::eQUICKHULL;
    cooking_params.gaussMapLimit = 256;

    physx::PxConvexMeshDesc px_mesh_desc;
    px_mesh_desc.points.count  = static_cast<physx::PxU32>(vertices.size());
    px_mesh_desc.points.stride = sizeof(VArray::value_type);
    px_mesh_desc.points.data   = &vertices[0];
    px_mesh_desc.flags         = physx::PxConvexFlag::eCOMPUTE_CONVEX;

    physx::PxConvexMesh* convex_mesh = nullptr;
    physx::PxDefaultMemoryOutputStream write_buffer;
    if (!PxCookConvexMesh(cooking_params, px_mesh_desc, write_buffer)) {
        assert(0 && "PxCookConvexMesh failed.");
    }
    physx::PxDefaultMemoryInputData read_buffer(write_buffer.getData(), write_buffer.getSize());
    convex_mesh = physics->createConvexMesh(read_buffer);
    return convex_mesh;
}

TriangleMesh

template<class VArray, class TArray>
inline physx::PxTriangleMesh* ToPxTriangleMesh(physx::PxPhysics* physics, const VArray& vertices, const TArray& triangles) {
    physx::PxTolerancesScale tolerances_scale;
    physx::PxCookingParams cooking_params(tolerances_scale);
    cooking_params.convexMeshCookingType = physx::PxConvexMeshCookingType::Enum::eQUICKHULL;
    cooking_params.gaussMapLimit = 256;

    physx::PxTriangleMeshDesc px_mesh_desc;
    px_mesh_desc.setToDefault();
    px_mesh_desc.points.count     = static_cast<physx::PxU32>(vertices.size());
    px_mesh_desc.points.stride    = sizeof(VArray::value_type);
    px_mesh_desc.points.data      = &vertices[0];
    px_mesh_desc.triangles.count  = static_cast<physx::PxU32>(triangles.size());
    px_mesh_desc.triangles.stride = sizeof(TArray::value_type);
    px_mesh_desc.triangles.data   = &triangles[0];
    px_mesh_desc.flags            = physx::PxMeshFlag::e16_BIT_INDICES;
    
    /*
    physx::PxSDFDesc sdf_desc;
    if (spacing > 0.f) {
        sdf_desc.spacing = static_cast<physx::PxReal>(spacing);
        sdf_desc.subgridSize = static_cast<physx::PxU32>(subgrid_size);
        sdf_desc.bitsPerSubgridPixel = bits_per_sdf_subgrid_pixel;
        sdf_desc.numThreadsForSdfConstruction = 16;
        px_mesh_desc.sdfDesc = &sdf_desc;
    }
    */

    physx::PxTriangleMesh* triangle_mesh = nullptr;
    physx::PxDefaultMemoryOutputStream write_buffer;
    if (!PxCookTriangleMesh(cooking_params, px_mesh_desc, write_buffer)) {
        assert(0 && "PxCookTriangleMesh failed.");
    }
    physx::PxDefaultMemoryInputData read_buffer(write_buffer.getData(), write_buffer.getSize());
    triangle_mesh = physics->createTriangleMesh(read_buffer);
    return triangle_mesh;
}

頂点リストをVArray(points)として、頂点インデックスリストをTArray(triangles)として渡すようにしています。また、templateなのでstd::arrayかstd::vectorを渡せるようにしています。

RigidDynamicBody用の関数

RigidDynamicBodyに専用の関数がいくつか用意されています。
今回はSleep関連の関数を紹介します。

Sleepとは

RigidDynamicBodyはSceneに生成した瞬間からUpdateを呼ばれるたびにシミュレート(処理)されます。Sceneに同時に存在しているRigidDynamicBodyが少量ならそのままでも問題ないですが、やはり増えてくると処理負荷が増えてきます。そこでSleepを有効化することでシミュレートされなくなります。PhysXでは一定時間動かなかった場合、外部から何等かの作用がない限り自動的にSleepになります。前回、無限落下防止用に地面用のPlaneを設定したのもこれが理由です。

  • Sleep中かどうかの真偽値
    dynamic_actor->isSleeping();
    
  • 強制的にSleepにする
    dynamic_actor->putToSleep();
    
  • 強制的にSleepを解除する
    dynamic_actor->wakeUp();
    

他にもRigidDynamicBodyの継承元であるRigidBodyには質量を設定する関数もあります。

GPUAcceleration(CUDA)

まずメンバ変数を定義します。

physx::PxCudaContextManager* m_pCudaCtxMgr = nullptr;

次に初期化関数です。この関数はphysx::PxSceneDescに代入する必要があるので、createScene()より前に呼び出す必要があります。

// 空間の設定
physx::PxSceneDesc scene_desc(m_pPhysics->getTolerancesScale());
scene_desc.gravity = physx::PxVec3(0, -9, 0);
scene_desc.filterShader = physx::PxDefaultSimulationFilterShader;
scene_desc.cpuDispatcher = m_pDispatcher;

// 追加
physx::PxCudaContextManagerDesc cuda_ctx_mgr_desc;
cuda_ctx_mgr_desc.interopMode = physx::PxCudaInteropMode::D3D11_INTEROP;
cuda_ctx_mgr_desc.graphicsDevice = dev;

if (m_pCudaCtxMgr = PxCreateCudaContextManager(*m_pFoundation, cuda_ctx_mgr_desc, PxGetProfilerCallback()); m_pCudaCtxMgr) {
    if (!m_pCudaCtxMgr->contextIsValid()) {
        m_pCudaCtxMgr->release();
    }
    else {
        scene_desc.cudaContextManager = m_pCudaCtxMgr;
        scene_desc.broadPhaseType = physx::PxBroadPhaseType::eGPU;
        scene_desc.flags |= physx::PxSceneFlag::eENABLE_PCM;
        scene_desc.flags |= physx::PxSceneFlag::eENABLE_GPU_DYNAMICS;
        scene_desc.flags |= physx::PxSceneFlag::eENABLE_STABILIZATION;
        scene_desc.gpuMaxNumPartitions = 8;
    }
}
// 追加終わり

// 空間のインスタンス化
m_pScene = m_pPhysics->createScene(scene_desc);

これでGPUAcceleration(CUDA)が有効になりました。シミュレート中にタスクマネージャーのGPUグラフを確認するとCUDAを利用していることが確認できると思います。

さいごに

2023/06/16 Snippetサンプルについて追記
2023/06/18 C++20以上を利用時のコンパイルエラーについて追記
2023/06/22 一部文章を修正
2023/06/27 誤字脱字を修正
2023/06/28 一部加筆
2023/06/30 誤字を修正
2023/07/03 構文ミスを修正
2023/08/31 コードの一部を修正
2023/10/03 複数に分割していた記事を統合

この記事を書いている最中にPhysXのReleaseのバージョンが上がっていました。

https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/docs/MigrationTo52.html

変更点を見る限り紹介した関数等は5.2.1でも動作すると思います。

https://github.com/shirokuma1101/game-libraries/tree/main/Inc/ExternalDependencies/PhysX

また、私が制作したライブラリにもPhysXのHelperやManagerがあります。まだ不完全な上に5.x系に対応できていない部分もありますが、設計等を考える上での参考になればなと思います。

JointやVehicleなどPhysX内でまだ触れられていない部分やBlastやFlowなどのライブラリも今後記事にしたいです。(ジカンガタリナイヨ)

関連リンク

5.1.3公式ドキュメント

https://nvidia-omniverse.github.io/PhysX/physx/5.1.3/index.html

形状について

https://nvidia-omniverse.github.io/PhysX/physx/5.1.3/docs/Geometry.html

Sleepについて

https://nvidia-omniverse.github.io/PhysX/physx/5.1.3/docs/RigidBodyDynamics.html#sleeping

5.2.1公式ドキュメント

https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/index.html

神戸電子専門学校ゲーム技術研究部

Discussion