Physics Scene and Simulation Configuration#
Every stage that ovphysx simulates needs a physics scene: a PhysicsScene
prim that defines gravity and the PhysX solver configuration. This page explains
how to author the scene and configure global simulation behavior (solver,
iteration counts, GPU buffers, sleeping, stabilization) for scenes that ovphysx
loads and steps.
This is the foundation the other Simulation Setup pages build on (Colliders, Rigid Bodies, Joints, Articulations, Deformables, Particles).
The code examples on this page are fragments, not complete files. Each USDA
example shows the prims to author inside the stage’s defaultPrim hierarchy;
where a later example repeats the physicsScene prim, it replaces the earlier
definition instead of adding a second scene. Each Python example after
Setting Up a USD Stage and a Physics Scene
extends that section’s script and reuses its stage and scene variables. For a
complete, CI-tested file, refer to the simple_physics_scene.usda reference used
by Hello World.
How ovphysx Simulates a Scene#
ovphysx consumes pre-authored USD owned by the application through ovstage.
Before you step a simulation, confirm the following:
An ovphysx instance exists, created with
ovphysx.PhysX()in Python orovphysx_create_instance()in C.The application has populated its scene into ovstage, and that scene contains at least one
PhysicsSceneprim.The bundled codeless schemas are registered if the stage uses any
Physx*orOmniPhysics*schema or API, as described in Physics Schemas.
A step then proceeds as follows:
Seal the populated ordinal with
advance_write_floor().Call
attach_ovstage()with the sealed ordinal. Attach parses that ordinal and creates PhysX objects for the prims that carry physics schemas.Colliders that need cooking (convex hull, convex decomposition, SDF, triangle mesh) are cooked. Results are cached on disk so recomputation is usually avoided — refer to the cooked-collider cache (UJITSO) in the Developer Guide.
Call
step(dt)to advance the simulation bydt.step()is asynchronous: it enqueues the step and returns anop_indeximmediately. Subsequent in-stream ovphysx calls (such as tensorread()/write()) automatically wait for it, but to consume results outside the ovphysx stream you must synchronize —wait_op()on the returned index (orwait_all()).step_sync(dt)is a convenience that steps and waits in one call (the typical choice for RL and control loops). Refer to the Execution Model.Read results back explicitly — either with the ovstage output read API (the session read,
ovphysx_read/PhysX.read) or the deprecated tensor bindings. Unlike Omni PhysX in Kit, ovphysx does not write simulation output back to the attached USD stage; the application owns writing state back to ovstage.
The pipeline is working when attach_ovstage() succeeds, step(dt) reports no
error through wait_op() or wait_all(), and a read of the simulated state
changes between steps. The clearest observable check is a dynamic body released
above a static ground: its position along the gravity axis decreases over
successive steps and then stops changing after the body rests on the ground.
The rest of this page is about authoring the PhysicsScene prim and the
attributes that configure this pipeline.
Setting Up a USD Stage and a Physics Scene#
To simulate physics, a stage must have at least one PhysicsScene prim. The
scene is authored with the core UsdPhysics schema (part of stock usd-core).
PhysX-specific solver settings live on the codeless PhysxSceneAPI schema (refer to
Physics Schemas).
Before you author the scene, install usd-core and confirm that
ovphysx.codeless_schema_paths() resolves, because the PhysX schemas must be
registered before the first stage is created. The scene is authored correctly
when the stage opens without a schema warning, UsdPhysics.Scene.Get() returns a
valid prim at the authored path, and the gravity attributes read back the values
you set.
USDA#
The following example defines a minimal stage and scene in a .usda file:
#usda 1.0
(
defaultPrim = "World"
metersPerUnit = 1
upAxis = "Z"
kilogramsPerUnit = 1
)
def Xform "World"
{
def PhysicsScene "physicsScene"
{
vector3f physics:gravityDirection = (0, 0, -1)
float physics:gravityMagnitude = 9.81
}
}
Python#
The following script builds the same stage and scene, and sets the units and up axis described in Units and Up Axis:
import ovphysx
from pxr import Plug, Usd, UsdGeom, UsdPhysics, Gf
# Register the bundled codeless PhysX schemas first: USD builds its schema
# registry on first access and never rebuilds it, so this must precede the
# first stage. Refer to Physics Schemas.
Plug.Registry().RegisterPlugins([str(p) for p in ovphysx.codeless_schema_paths()])
stage = Usd.Stage.CreateNew("scene.usda")
# Units and up axis.
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z)
UsdGeom.SetStageMetersPerUnit(stage, 1.0)
UsdPhysics.SetStageKilogramsPerUnit(stage, 1.0)
world = UsdGeom.Xform.Define(stage, "/World")
scene = UsdPhysics.Scene.Define(stage, "/World/physicsScene")
scene.CreateGravityDirectionAttr().Set(Gf.Vec3f(0.0, 0.0, -1.0))
scene.CreateGravityMagnitudeAttr().Set(9.81)
For a complete, CI-tested minimal scene (physics scene + static ground + dynamic
bodies), refer to the simple_physics_scene.usda reference used by
Hello World.
Units and Up Axis#
USD supports scaling of units through layer metadata. For correct simulation results, the units and up axis of your stage must match your content.
metersPerUnitsets the length unit (1= meters). Keep all sizes, positions, and velocities in that unit.upAxisis"Y"or"Z". Pointphysics:gravityDirectiondown the same axis (up axisZ-> gravity(0, 0, -1)).kilogramsPerUnit(set throughUsdPhysics.SetStageKilogramsPerUnit) sets the mass unit.If no gravity is authored, ovphysx scales default and auto-computed values by the layer unit metadata. For example, at
metersPerUnit = 0.01(centimeter scale) the default gravity magnitude is981.0 cm/s^2.
Angles are always in degrees. Every rotation-related physics parameter is in degrees, including drive stiffness (units of torque/degree for an angular drive).
Keep units consistent across all layers. Automatic reconciliation of unit scales between layers is not supported; author every layer and asset at the same scale.
Multiple Physics Scenes#
A stage can contain multiple PhysicsScene prims. PhysicsRigidBodyAPI and
PhysicsCollisionAPI have a physics:simulationOwner relationship that assigns
a body or collider to a specific scene. If it is not set, the first
PhysicsScene found during traversal is used. Objects in separate physics
scenes do not collide with each other.
def PhysicsScene "physicsScene0"
{
}
def PhysicsScene "physicsScene1"
{
}
def Cube "cube0" (
prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsCollisionAPI"]
)
{
rel physics:simulationOwner = </World/physicsScene0>
}
ovphysx steps all scenes together. Multiple
PhysicsSceneprims in one stage become separate PhysX scenes, but they all advance together on eachstep(); per-scene stepping is not surfaced. For isolated stages or truly independent parallel sims, use separate subprocesses (refer to the threading and single-attached-stage notes in the Developer Guide).
Physics Solver#
The solver iteratively resolves constraints (joints, contacts) to reach a
physically plausible state without overlaps or disconnected joints. Two
strategies are available, selected on PhysxSceneAPI:
TGS (Temporal Gauss-Seidel, the default): converges faster; can overshoot (objects gaining excess velocity) in complex collision scenarios. TGS benefits from
physxScene:enableExternalForcesEveryIterationand needs only very few (around 1) velocity iterations.PGS (Projected Gauss-Seidel): converges more slowly but is less prone to overshooting.
Iteration counts trade accuracy against cost. Position iterations keep bodies from overlapping; velocity iterations prevent bodies from picking up artificial velocity on interaction. Both are clamped per-scene with min/max attributes.
USDA#
The following example extends the physicsScene prim authored earlier with
PhysxSceneAPI and explicit solver settings:
def PhysicsScene "physicsScene" (
prepend apiSchemas = ["PhysxSceneAPI"]
)
{
vector3f physics:gravityDirection = (0, 0, -1)
float physics:gravityMagnitude = 9.81
uniform token physxScene:solverType = "TGS"
uniform uint physxScene:minPositionIterationCount = 4
uniform uint physxScene:maxPositionIterationCount = 16
uniform uint physxScene:minVelocityIterationCount = 1
uniform uint physxScene:maxVelocityIterationCount = 1
}
Python for the Codeless PhysX Schema#
PhysX-specific attributes live on codeless schemas that have no typed Python class. Apply them by identifier and author the attributes generically. The following example extends the script in Setting Up a USD Stage and a Physics Scene, whose registration call already ran before the stage was created:
from pxr import Sdf
scene_prim = scene.GetPrim()
scene_prim.ApplyAPI("PhysxSceneAPI")
scene_prim.CreateAttribute("physxScene:solverType", Sdf.ValueTypeNames.Token).Set("TGS")
scene_prim.CreateAttribute("physxScene:maxPositionIterationCount", Sdf.ValueTypeNames.UInt).Set(16)
Refer to Physics Schemas for the full codeless-schema registration story, and Performance for guidance on clamping iteration counts in bulk/RL scenes.
PhysX GPU Memory Buffers#
When simulating on the GPU, PhysX allocates internal buffers at initialization.
If a buffer overflows, the runtime reports an error and you must increase its
capacity on PhysxSceneAPI. The most common one to tune is the found/lost pairs
capacity:
def PhysicsScene "physicsScene" (
prepend apiSchemas = ["PhysxSceneAPI"]
)
{
uint physxScene:gpuFoundLostPairsCapacity = 10240
}
CPU and GPU Simulation#
GPU dynamics are enabled per scene in USD with physxScene:enableGPUDynamics
(and a GPU broadphase through physxScene:broadphaseType = "GPU"):
def PhysicsScene "physicsScene" (
prepend apiSchemas = ["PhysxSceneAPI"]
)
{
bool physxScene:enableGPUDynamics = true
uniform token physxScene:broadphaseType = "GPU"
}
ovphysx also exposes process- and instance-level controls that complement the per-scene USD settings:
ovphysx.PhysX.set_cpu_mode(True)forces a process-wide CPU-only mode (must be called before any instance is created; it cannot be reverted in that process).PhysX(active_cuda_gpus="0,1")selects explicit CUDA ordinals and their supported scene-distribution mode. Any non-empty value takes precedence overPhysXConfig.scene_multi_gpu_mode; one ordinal disables distribution.With
active_cuda_gpusempty,PhysXConfig(scene_multi_gpu_mode=...)controls distribution of multiplePhysicsSceneprims across GPUs.
GPU features (deformables, particles, SDF meshes, DirectGPU tensor workloads) require GPU dynamics. Refer to the GPU/CPU and determinism sections of the Developer Guide.
Sleeping#
Sleeping lets PhysX save computation when dynamic objects are at rest. An object that stops moving for a short time goes to sleep until it is woken — by the application (for example, applying a force or changing a joint drive target) or by another object colliding with it.
Each dynamic object type exposes sleep-related properties: for rigid bodies,
physxRigidBody:sleepThreshold sets the mass-normalized kinetic-energy threshold
below which the body can sleep (set it to 0 to keep a body always awake).
From the runtime, rigid-body tensor bindings (deprecated) expose explicit
wake_up() / sleep() controls.
Sleeping and effective iteration counts (GPU). In a GPU simulation it is efficient to apply the same iteration count to every awake object — the maximum over all awake objects in the scene. If a high-iteration object goes to sleep, the effective iteration count for the remaining awake objects can drop, which can change their behavior (notably for deformables). Keep this in mind when mixing objects with very different iteration settings.
Stabilization#
Stabilization improves the settling behavior of rigid bodies in large-pile or high-interaction scenes by applying extra damping to slow-moving objects. When a body’s mass-normalized kinetic energy falls below its stabilization threshold, damping is applied so it settles faster.
Stabilization reduces momentum and can cause unphysical effects, so it is not recommended for robotics, industrial applications, or any simulation that needs precise interactions.
Enable it at the scene level, then set the threshold per rigid body:
def PhysicsScene "physicsScene" (
prepend apiSchemas = ["PhysxSceneAPI"]
)
{
bool physxScene:enableStabilization = true
}
def Cube "box" (
prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysxRigidBodyAPI", "PhysicsCollisionAPI"]
)
{
float physxRigidBody:stabilizationThreshold = 0.0001
}
Reading Simulation Output#
ovphysx does not write results back to the attached stage. To observe simulated state, read it explicitly:
ovstage output read — the session read (
ovphysx_read/PhysX.read) of positions, orientations, velocities, and joint state.Tensor bindings (deprecated) — bulk, DLPack-friendly read/write of poses, velocities, joint state, and more.