260401

继我们上次搓好了小车车,我们接下来可以来搓一个较为简单的世界,我们可以在 src/kibot_one_sim/worlds/kibot_one.world.sdf 中写入如下内容:

<?xml version="1.0"?>
<sdf version="1.9">
  <world name="kibot_one_world">
    <gravity>0 0 -9.8</gravity>

    <physics name="physics" type="dart">
      <max_step_size>0.004</max_step_size>
      <real_time_factor>1.0</real_time_factor>
      <real_time_update_rate>250</real_time_update_rate>
    </physics>

    <scene>
      <ambient>0.6 0.6 0.6 1</ambient>
      <background>0.8 0.8 0.8 1</background>
    </scene>

    <light name="sun" type="directional">
      <cast_shadows>true</cast_shadows>
      <pose>0 0 10 0 0 0</pose>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <constant>0.9</constant>
        <linear>0.01</linear>
        <quadratic>0.001</quadratic>
      </attenuation>
      <direction>-0.5 0.1 -0.9</direction>
    </light>

    <model name="ground_plane">
      <static>true</static>
      <link name="link">
        <collision name="collision">
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
        </collision>
        <visual name="visual">
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <material>
            <ambient>0.8 0.8 0.8 1</ambient>
            <diffuse>0.8 0.8 0.8 1</diffuse>
            <specular>0.1 0.1 0.1 1</specular>
          </material>
        </visual>
      </link>
    </model>

    <include>
      <uri>model://kibot_one_base</uri>
      <pose>0 0 0 0 0 0</pose>
    </include>
  </world>
</sdf>

上面的 xml文件的元数据的一些标识就不用说了,我们重点说一下有实际功能的部分。

我们先分析其结构,我们可以将它结构概括为这样的:

sdf
└── world
    ├── gravity      # 世界重力
    ├── physics      # 物理仿真参数
    ├── scene        # 画面环境参数
    ├── light        # 光源
    ├── ground_plane # 地面
    └── include      # 把小车模型放进来

这里我们先看我们的 world标签,该标签将作为该世界的外包裹标签。

这里我们定义了这个世界的名称,叫做 kibot_one_world。

然后是重力标签gravity:

<gravity>0 0 -9.8</gravity>

这里重力标签控制了三个值,分别是 x, y, z 方向上的重力, 这里我们填写 x, y 上都是 0,而 z 上则是 -9.8。

如果我们修改重力,如修改为 1 的话,则在开始模拟后小车会自己飘起来:

然后是 physics 标签:

<physics name="physics" type="dart">
  <max_step_size>0.004</max_step_size>
  <real_time_factor>1.0</real_time_factor>
</physics>

它决定了我们的仿真应该怎么跑。

我们这里 type 决定了使用什么物理引擎,我们这里使用的是 dart。

除了 dart 以外,我们常见的物理引擎还有 bullet, ode 等。

dart较新,而且对于在复杂关节约束和树状/闭环运动学链上的模拟会明显优于 ode。

然后是 max_step_size ,这决定了我们的物理引擎多久进行一次物理计算,如果这个值太大了,计算间隔太长,我们的模型在进行仿真模拟的时候,可能会出现“闪现”或者“穿墙”的情况,而有可能不会计算碰撞。如果计算间隔太短又会对 CPU 造成较大的负担,从而导致物理引擎的模拟过程发生卡顿。

而下面的 real_time_factor则是控制物理引擎运算速度相对于现实的倍数,我们这里是 1.0,就是正常流逝,如果我们需要的话,我们可以调到更高,如 2.0就是 2 倍,这样的话就可以让我们的物理引擎以远超现实的速度进行计算,这使得在我们进行强化学习的时候可以以相对于现实几十倍甚至更高的速度来进行训练。

往下就是我们的场景 scene 标签了:

    <scene>
      <ambient>0.6 0.6 0.6 1</ambient>
      <background>0.8 0.8 0.8 1</background>
    </scene>

这里的 ambient 是环境光强度和颜色,这样不至于没有光照到的地方显得全黑。

而下面的 background 则是 Gazebo 的窗口颜色,没什么影响的地方。

继续往下,就是 light 了:

    <light name="sun" type="directional">
      <cast_shadows>true</cast_shadows>
      <pose>0 0 10 0 0 0</pose>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <constant>0.9</constant>
        <linear>0.01</linear>
        <quadratic>0.001</quadratic>
      </attenuation>
      <direction>-0.5 0.1 -0.9</direction>
    </light>

这里我们定义了一个名为 sun ,类型为directional的方向光,directional 的环境光是同一个方向打过去的平行光,而非 point 这样类似灯泡的点光源,也不像 spot 这样的聚光灯。

这下面的 cast_shadows 则表示物体需不需要有阴影,我们通常是需要有阴影的,因此为 true,不过如果想要画面更简单的话也可以设置为 false。

继续往下还有 pose,这里 pose 中的六个字段和我们上面的也相同,也都是 x, y, z, roll, pitch, yaw。

再往下,diffuse 和 specular则都是控制反射,前者是漫反射,后者是镜面反射,前者决定了物体的基础轮廓和固有颜色,而后者则决定了一些物体的高光。

然后是 attenuation,这个是光线衰减参数,直觉上来说,随着距离变远,光会逐渐变弱,而这个就决定了这个光是否会变弱,以及变弱的多快。

我们的是方向光,虽然这段代码会被物理引擎忽略,但是写上总是没什么问题的。

继续往下,就是对于我们的方向光来说至关重要的 direction 方向了。

这里值得注意的是,我们的 pose 就已经带有方向属性了,但是我们通常不会直接通过调整 roll, pitch 和 yaw 来调整光源的方向。

pose 中的方向和 direction 的角度通常是叠加关系,即 direction 的方向是相对于 pose 的,不过值得注意的是, pose中角度使用的是欧拉角,而direction中使用的则是向量。

这个其实就很像我们实际仿真中“手电筒”和“光源”的关系。

我们的手电筒放在一个架子上的时候,如在支架上倾斜 30 度,此时我们就可以直接调整 pose 中的方向来调整手电筒的朝向使其被放在架子上,而如果我们想要调整其中光源的方向的话,如果此时我们还调整 pose 中的方向,那我们得到的就是一个“掰弯的手电筒”,这其实是不符合我们物理预期的,而通过 direction直接调整光源方向则可以避免这个问题。

这就是我们的 pose 中的方向和 direction 不同的地方。

以上就是我们主要场景的相关配置。

再往下,我们可以看到我们的 world 的 sdf 里也有 model,这是因为 world 是指场景,而场景里是可以有很多物体的,因此也会有 model。

这里我们创建了一个名为 ground_plane 的模型,即纯地面。

这里我们新增了一个配置项,名为 static ,即代表该物体在场景中是否为静态的,通常地面、墙壁等我们都期望它们是静态的,不会到处乱飞。

同时,我们定义了一个平面 plane 的碰撞箱和材质。

最下面就是我们通过 include引入了我们的小车模型。

值得注意的是,我们可以发现这里我们也写了一个 pose:

    <include>
      <uri>model://kibot_one_base</uri>
      <pose>0 0 0 0 0 0</pose>
    </include>

要知道,我们的小车 sdf 文件中的 model 下面我们也写了一个 pose。

这里就引出来了一个区分的点——它们两者都是指模型中心原点在出生时相对于世界的坐标,但是include 中的优先级更高,会直接覆盖掉model 下的pose。

就这样,我们就写出来了一个 world 文件。

以上。