初めに
この記事はPhysX4.1を四苦八苦しながら導入&使い方を試した記事です。他の人の為にもこの記事を書くことにしました。ビルド環境はVS2019(x64)のC++17です。
PhysXとは
物理演算を実行してくれるエンジンです。導入手順などはこの記事を見ながらしたので、詳細は記事を見て下さい。
使い方
軽い使い方は恐らく上記の記事の書かれた方がPDFとしてBoothで売られているので、それで解決するかとは思います。自分もそれを参考にしているので、著作権的にも実際のコードは載せません。
ですがこの記事の意味がなくなりそうなので手順だけ書いています。といっても、リファレンスを見れば済む気がしますが(笑)
初期化
- physx::PXFoundationクラスの作成(バージョンはPX_PHYSICS_VERSIONを指定してください)
- physx::PxPvdクラスことPVD(PhysX Visual Debugger)の作成(PVDのダウンロードはこのサイトから1)
- physx::PxPvdTransportクラスの作成
- physx::PxPvdとの接続(3で作成したクラスを使います)
- physx::PxPhysicsクラスの作成
- 拡張ライブラリを初期化(PxInitExtensions)
- physx::PxDefaultCpuDispatcherクラスの作成(物理演算に使うCPUのスレッド数を指定します)
- physx::PxSceneDescの設定
- physx::PxSceneの作成(重力・7のクラス・physx::PxDefaultSimulationFilterShader)
- physx::PxPvdSceneClientの作成
- physx::PxPvdSceneClient::setScenePvdFlag関数の設定(eTRANSMIT_CONSTRAINTS, eTRANSMIT_CONTACTS, eTRANSMIT_SCENEqUERIES のフラグをtrueに設定して下さい)
- physx::PxTolerancesScaleの設定(恐らくシーン上の単位を管理(メートルは1、センチメートルは100))
- physx::PxCookingクラスの作成
以下はGPUで物理演算をさせる時に設定する項目です。
- 8の時にphysx::PxCudaContextManagerDescをphysx::PxSceneDesc.cudaContextManagerに設定
- physx::PxSceneDesc.flags |= physx::PxSceneFlag::eENABLE_GPU_DYNAMICSに設定
- physx::PxSceneDesc.broadPhaseType = physx::PxBroadPhaseType::eGPU;に設定
更新
- physx::PxScene::simulate関数を呼ぶ(この更新頻度が高いと精度は上がりますが、負荷も上がります)
- physx::PxScene::fetchResults関数を呼ぶ(1の関数で物理シミュレーションを開始し、この関数で結果の完了を待ちます2)
ジオメトリ
作成出来るジオメトリの種類です。詳細はリファレンスを見てほしいのですが、軽く説明をします。
球
クラスはphysx::PxSphereGeometryです。
カプセル
クラスはphysx::PxCapsuleGeometryです。
ボックス
プレーン
凸メッシュ
そもそも凸メッシュの説明をこのサイトの言い方で言うと
凸メッシュとは凹みや穴を持たない多面体です. 頂点座標を指定することで自由な形を作成することができます.
になります。このオブジェクトを作成しようとして、色々と穴にハマったので作り方の一例を紹介します。
namespace px = physx;
static const px::PxVec3 convexVerts[]{ px::PxVec3(0,1,0),px::PxVec3(1,0,0),px::PxVec3(-1,0,0),px::PxVec3(0,0,1), px::PxVec3(0,0,-1) };
physx::PxConvexMeshDesc desc;
mesh.setToDefault(); // 初期化
desc.points.count = 5; // 頂点の個数
desc.points.stride = sizeof(px::PxVec3); // 頂点のバイト単位のオフセット
desc.points.data = convexVerts; // 頂点配列の先頭のポインター
desc.flags = px::PxConvexFlag::eCOMPUTE_CONVEX;
assert(desc.isValid());
px::PxDefaultMemoryOutputStream buf;
px::PxConvexMeshCookingResult::Enum result;
assert(cooking->cookConvexMesh(desc, buf, &result));
assert(result == px::PxConvexMeshCookingResult::Enum::eSUCCESS);
px::PxDefaultMemoryInputData input(buf.getData(), buf.getSize());
px::PxConvexMesh* convexMesh{ physics->createConvexMesh(input) };
assert(convexMesh);
px::PxConvexMeshGeometry mesh{};
mesh.convexMesh = convexMesh;
px::PxShape* mesh_shape{ physics->createShape(mesh, physics->createMaterial(0.5f, 0.5f, 0.5f)) };
px::PxRigidDynamic* rigid_static
{ px::PxCreateDynamic(*physics, physx::PxTransform(physx::PxIdentity), *mesh_shape, 10.f) };
scene->addActor(*rigid_static);
リファレンスをみているとaConvexActor
ってなに? となったので、直ぐに出来なかったり...。
デモにあるはずの関数がなかったりして、色々と苦労しました。
三角形メッシュ
三角形メッシュと分かりにくい言い方ですが、.Fbxや.Objなどのファイルを読み込んで物理演算をさせる時に使うのがこのメッシュです。
これも同じく苦労したので、作り方を紹介します。というより作り方が凸メッシュと非常に似ているのですが(笑)
namespace px = physx;
physx::PxTriangleMeshDesc desc;
desc.setToDefault(); // 初期化
desc.points.data = /*頂点配列の先頭へのポインター*/;
desc.points.count = /*頂点の個数*/;
desc.points.stride = /*頂点のバイト単位のオフセット*/;
desc.triangles.data = /*三角形配列の先頭へのポインター*/;
desc.triangles.count = /*三角形の個数*/;
desc.triangles.stride = /*三角形のバイト単位のオフセット*/;
desc.flags = px::PxMeshFlags();
assert(desc.isValid());
px::PxDefaultMemoryOutputStream write_buffer{};
bool status{ cooking->cookTriangleMesh(desc, write_buffer) };
assert(status);
px::PxDefaultMemoryInputData read_buffer(write_buffer.getData(), write_buffer.getSize());
px::PxTriangleMesh* triangle_mesh{ physics->createTriangleMesh(read_buffer) };
assert(triangle_mesh);
px::PxTriangleMeshGeometry mesh{};
mesh.triangleMesh = triangle_mesh;
px::PxShape* mesh_shape{ physics->createShape(mesh, physics->createMaterial(0.5f, 0.5f, 0.5f)) };
px::PxRigidStatic* rigid_static{ px::PxCreateStatic(*physics, physx::PxTransform(physx::PxIdentity), *mesh_shape) };
scene->addActor(*rigid_static);
ハイトフィールド
そもそもハイトフィールドの説明をこのサイトの言い方で言うと
ハイト・フィールドは非常に多くの小さな3角形によって凹凸のある面を表現するものである。 凹凸の形状は濃淡画像(またはパレット・インデックス)を高さに変換することによって生成される。 Paint Shopなどの画像ソフトを使って、地形図を等高線で色分けして実際の地形を表現するといった使い方もできる。
アクターと物理演算
PhysXで扱うオブジェクトの事をカッコつけて「アクター」と呼んでいます。
動的・静的なアクター
この欄はUnityなどを触っている方なら、分かり易いと思います。
- Dynamic Actor: 物理演算を行うアクターです。通常はこのアクターを使用することになると思います。
- Static Actor: イメージ的には物理演算を行わないアクターです。正確には物理演算を行いますが、一切動かないアクターになるので、通常は壁や地面になると思います。
- Kinematic Actor 物理演算を行うアクターです。ただし、Dynamic Actorに対して一方的に影響を与え、自身については影響を受けません。
追加
ジオメトリはphysx::PxCreateDynamic
などで作成しただけでは自動で物理演算してくれません。そのためにはphysx::PxScene
に追加しなければなりません。逆に言えば、あらかじめ作成しておいて後でシーンに追加するといったことも可能です。
情報のセット
Dynamic Actor
に対し
- 速度:
setLinearVelocity
- 加速度:
setAngularVelocity
- 位置+角度:
setKinematicTarget
(変更する前にsetRigidBodyFlag
でKinematic Actorに変更する必要があります)
``setGlobalPose``について
この関数は強制的に座標を変更する関数で、コメントにも書いてある通り、使用については**非推奨**です。これは、静的アクターの場合はパフォーマンスが低下する場合があり、動的アクターの場合は挙動がおかしくなる(他のアクターに重なった時やジョイント部分を引き離した時)からです。情報の取得
- 速度:
getLinearVelocity
- 加速度:
getAngularVelocity
- 位置+角度
getkinematicTarget
かgetGlobalPose
無効状態
Dynamic Actorを無効化させることで、処理負荷を大幅に軽減させることが出来ます。無効化状態だと完全に静止状態になり、他のActorとの衝突があるまで処理が無効化されます。
- 無効化:
putToSleep
- 有効化:
wakeUp
削除
ジオメトリを削除するにはphysx::PxScene
で削除することで自動で削除出来ます。PhysXのメモリー管理は参照カウントを使っているためです。ですが、参照カウントにはあまりいい思い出は無いので、出来る限り手動で削除したいとは思いますが...。
終了処理
かならず、初期化で作成した全てのクラスは削除する(releaseを呼ぶ)ようにしてください。特にphysx::PxPhysics
は削除した際に、シーンに存在する全てのジオメトリを削除するのですが、トラブルを避ける為にもPhysX側に任せるのではなく、事前にジオメトリの削除を自分で実行した方が良いと思います。
最後に
物理演算エンジンは入れたいけど難易度が高そうと思っている方がいるかもしれませんが、導入さえしてしまえば使うのは意外と難しくありません。他に難しくしている要素は日本語の記事がほとんどない事でしょうか?その問題もGoogle先生にかかれば割とどうにかなるので、試しに導入してみるのもありだと思います。