射线投射器

射线投射器#

射线投射传感器支持的射线模式

射线投射器报告所选模式中每条射线遇到的第一个碰撞点。射线不会反弹,也不受材质和不透明度影响。四足任务使用此传感器测量机器人周围的地形高度。

射线投射的实现取决于物理后端。在 PhysX 和 OV PhysX 上,配置的网格会在传感器初始化时加载到 Warp ,且必须保持静止。在 Newton 上,传感器查询实时场景的 BVH,因此能够感知所在环境中的所有碰撞形状、全局地形和动态刚体。Newton 会忽略 mesh_prim_paths ;可使用 isaaclab_newton.sensors.NewtonRaycastSensorCfg 明确指定这一行为,并访问 Newton 特有的选项。

选择射线模式和父坐标系:坐标系及传感器偏移决定其位姿,模式决定射线起点和方向。内置模式包括激光雷达和网格。

@configclass
class RaycasterSensorSceneCfg(InteractiveSceneCfg):
    """Design the scene with sensors on the robot."""

    # ground plane
    ground = AssetBaseCfg(
        prim_path="/World/Ground",
        spawn=sim_utils.UsdFileCfg(
            usd_path=f"{ISAAC_NUCLEUS_DIR}/Environments/Terrains/rough_plane.usd",
            scale=(1, 1, 1),
        ),
    )

    # lights
    dome_light = AssetBaseCfg(
        prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
    )

    # robot
    robot = replace(ANYMAL_C_CFG, prim_path="{ENV_REGEX_NS}/Robot")

    ray_caster = RayCasterCfg(
        prim_path="{ENV_REGEX_NS}/Robot/base",
        update_period=1 / 60,
        offset=RayCasterCfg.OffsetCfg(pos=(0, 0, 0.5)),
        mesh_prim_paths=["/World/Ground"],
        ray_alignment="yaw",
        pattern_cfg=patterns.LidarPatternCfg(
            channels=100, vertical_fov_range=[-90, 90], horizontal_fov_range=[-90, 90], horizontal_res=1.0
        ),
        debug_vis=bool(args_cli.visualizer),
    )

模式中的角度采用度。可启用调试可视化检查模式,测量性能时再关闭。

激光雷达模式可视化

仿真期间,通过 data.ray_hits_w 读取命中位置:

def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
  .
  .
  .
  # Simulate physics
  while sim.is_running():
    .
    .
    .
    # print information from the sensors
      print("-------------------------------")
      print(scene["ray_caster"])
      print("Ray cast hit results: ", scene["ray_caster"].data.ray_hits_w)
-------------------------------
Ray-caster @ '/World/envs/env_.*/Robot/base/lidar_cage':
        view type            : <class 'isaacsim.core.experimental.prims.xform_prim.XformPrim'>
        update period (s)    : 0.016666666666666666
        number of meshes     : 1
        number of sensors    : 1
        number of rays/sensor: 18000
        total number of rays : 18000
Ray cast hit results:  tensor([[[-0.3698,  0.0357,  0.0000],
        [-0.3698,  0.0357,  0.0000],
        [-0.3698,  0.0357,  0.0000],
        ...,
        [    inf,     inf,     inf],
        [    inf,     inf,     inf],
        [    inf,     inf,     inf]]], device='cuda:0')
-------------------------------

命中位置的形状为 (N, B, 3),其中 N 是传感器数量,B 是射线数量。未命中的射线返回 inf。本例中的重复点来自半球激光雷达模式的底部极点,180 条水平方向的采样射线在那里重合。

在下面的示例中尝试不同模式,查看各自的命中位置。

raycaster_sensor.py 的代码
  1# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
  2# All rights reserved.
  3#
  4# SPDX-License-Identifier: BSD-3-Clause
  5
  6"""Cast a lidar-style ray pattern against rough terrain."""
  7
  8import argparse
  9from typing import TYPE_CHECKING
 10
 11import torch
 12
 13import isaaclab.sim as sim_utils
 14from isaaclab.app import add_launcher_args, launch_simulation
 15from isaaclab.assets import AssetBaseCfg
 16from isaaclab.scene import InteractiveSceneCfg
 17from isaaclab.sensors.ray_caster import RayCasterCfg, patterns
 18from isaaclab.utils import configclass, instantiate, replace
 19from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
 20
 21from isaaclab_assets.robots.anymal import ANYMAL_C_CFG
 22
 23if TYPE_CHECKING:
 24    from isaaclab.scene import InteractiveScene
 25
 26parser = argparse.ArgumentParser(description="Example on using the raycaster sensor.")
 27parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to spawn.")
 28parser.add_argument("--log_interval", type=int, default=100, help="Steps between compact sensor summaries.")
 29parser.add_argument("--max_steps", type=int, default=-1, help="Stop after this many steps; negative runs forever.")
 30parser.add_argument(
 31    "--physics",
 32    default="isaacsim_physx",
 33    choices=["isaacsim_physx"],
 34    help="Physics backend.",
 35)
 36add_launcher_args(parser)
 37parser.set_defaults(visualizer=["kit"])
 38args_cli = parser.parse_args()
 39if args_cli.log_interval < 1:
 40    parser.error("--log_interval must be at least 1.")
 41if args_cli.max_steps == 0 or args_cli.max_steps < -1:
 42    parser.error("--max_steps must be positive or -1.")
 43
 44
 45@configclass
 46class RaycasterSensorSceneCfg(InteractiveSceneCfg):
 47    """Design the scene with sensors on the robot."""
 48
 49    # ground plane
 50    ground = AssetBaseCfg(
 51        prim_path="/World/Ground",
 52        spawn=sim_utils.UsdFileCfg(
 53            usd_path=f"{ISAAC_NUCLEUS_DIR}/Environments/Terrains/rough_plane.usd",
 54            scale=(1, 1, 1),
 55        ),
 56    )
 57
 58    # lights
 59    dome_light = AssetBaseCfg(
 60        prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
 61    )
 62
 63    # robot
 64    robot = replace(ANYMAL_C_CFG, prim_path="{ENV_REGEX_NS}/Robot")
 65
 66    ray_caster = RayCasterCfg(
 67        prim_path="{ENV_REGEX_NS}/Robot/base",
 68        update_period=1 / 60,
 69        offset=RayCasterCfg.OffsetCfg(pos=(0, 0, 0.5)),
 70        mesh_prim_paths=["/World/Ground"],
 71        ray_alignment="yaw",
 72        pattern_cfg=patterns.LidarPatternCfg(
 73            channels=100, vertical_fov_range=[-90, 90], horizontal_fov_range=[-90, 90], horizontal_res=1.0
 74        ),
 75        debug_vis=bool(args_cli.visualizer),
 76    )
 77
 78
 79def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") -> None:
 80    """Run the simulator."""
 81    # Define simulation stepping
 82    sim_dt = sim.get_physics_dt()
 83    count = 0
 84
 85    while sim.is_running() and (args_cli.max_steps < 0 or count < args_cli.max_steps):
 86        if count % 500 == 0:
 87            # reset the scene entities
 88            # root state
 89            # we offset the root state by the origin since the states are written in simulation world frame
 90            # if this is not done, then the robots will be spawned at the (0, 0, 0) of the simulation world
 91            root_pose = scene["robot"].data.default_root_pose.torch.clone()
 92            root_pose[:, :3] += scene.env_origins
 93            scene["robot"].write_root_pose_to_sim_index(root_pose=root_pose)
 94            root_vel = scene["robot"].data.default_root_vel.torch.clone()
 95            scene["robot"].write_root_velocity_to_sim_index(root_velocity=root_vel)
 96            # set joint positions with some noise
 97            joint_pos, joint_vel = (
 98                scene["robot"].data.default_joint_pos.torch.clone(),
 99                scene["robot"].data.default_joint_vel.torch.clone(),
100            )
101            joint_pos += torch.rand_like(joint_pos) * 0.1
102            scene["robot"].write_joint_position_to_sim_index(position=joint_pos)
103            scene["robot"].write_joint_velocity_to_sim_index(velocity=joint_vel)
104            # clear internal buffers
105            scene.reset()
106            print("[INFO]: Resetting robot state...")
107        targets = scene["robot"].data.default_joint_pos.torch
108        scene["robot"].set_joint_position_target_index(target=targets)
109        scene.write_data_to_sim()
110        sim.step()
111        count += 1
112        scene.update(sim_dt)
113
114        if count % args_cli.log_interval == 0:
115            hits = scene["ray_caster"].data.ray_hits_w.torch
116            valid = torch.isfinite(hits).all(dim=-1)
117            print(f"[INFO] step={count} ray hit rate={valid.float().mean().item():.1%}")
118
119
120def main() -> None:
121    """Run the ray-caster example."""
122    sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device)
123    with launch_simulation(sim_cfg, args_cli):
124        sim = sim_utils.SimulationContext(sim_cfg)
125        sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0])
126        scene_cfg = RaycasterSensorSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
127        scene: InteractiveScene = instantiate(scene_cfg)
128        sim.reset()
129        print("[INFO]: Setup complete...")
130        run_simulator(sim, scene)
131
132
133if __name__ == "__main__":
134    main()