类MujocoEnv
里面通过_initialize_sim
函数初始化了MjSim
类型的self.sim
成员
# MujocoEnv._initialize_sim
def _initialize_sim(self, xml_string=None):
"""
Creates a MjSim object and stores it in self.sim. If @xml_string is specified, the MjSim object will be created
from the specified xml_string. Else, it will pull from self.model to instantiate the simulation
Args:
xml_string (str): If specified, creates MjSim object from this filepath
"""
xml = xml_string if xml_string else self.model.get_xml()
# Create the simulation instance
self.sim = MjSim.from_xml_string(xml) # self.sim = MjSim()
# run a single step to make sure changes have propagated through sim state
self.sim.forward()
class MjSim:
def __init__(self, model):
"""
Args:
model: should be an MjModel instance created via a factory function
such as mujoco.MjModel.from_xml_string(xml)
"""
self.model = MjModel(model)
self.data = MjData(self.model)
# offscreen render context object
self._render_context_offscreen = None
MjSim
有三个重要的成员,model
是MjModel
类型,其中的_model
成员其实就是mujoco.MjModel.from_xml_path
得到的mujoco模型对象,data
是MjData
类型,其中的_data
成员其实就是mujoco.MjData
得到的mujoco数据对象,_render_context_offscreen
是MjRenderContextOffscreen
类型,负责渲染,并返回摄像头的数据
MjSim里面的render
函数就是调用了MjRenderContextOffscreen
的父类MjRenderContext
的render函数,然后获取摄像头的数据
# MjSim.render
def render(self,width=None,height=None,...):
camera_id = self.model.camera_name2id(camera_name)
with _MjSim_render_lock:
self._render_context_offscreen.render(width=width, height=height,...)
return self._render_context_offscreen.read_pixels(width, height,...)
至于MjRenderContext
的render
函数,就是调用了mujoco原生的api,包括MjrRect
、mjv_updateScene
和mjr_render
,可以看到MjRenderContext
里面也有model
和data
两个成员,因为它在构造的时候需要传入MjSim
对象,这两个成员其实就是MjSim
的model
和data
# MjRenderContext.render
def render(self, width, height, camera_id=None, segmentation=False):
viewport = mujoco.MjrRect(0, 0, width, height)
mujoco.mjv_updateScene(
self.model._model, self.data._data, self.vopt, self.pert, self.cam, mujoco.mjtCatBit.mjCAT_ALL, self.scn
)
mujoco.mjr_render(viewport, self.scn, self.con)
在MujocoEnv里面是通过viewer
成员来调用render
函数的,这个viewer
成员是个OpenCVRenderer
类型的对象,在构造的时候拿到了MujocoEnv的sim
成员,然后调用sim
的render
函数,拿到图像数据并显示出来
# OpenCVRenderer.render
def render(self):
im = self.sim.render(self.camera_name, self.height, self.width)[..., ::-1]
im = np.flip(im, axis=0)
cv2.imshow("offscreen render", im)
key = cv2.waitKey(1)
if self.keypress_callback:
self.keypress_callback(key)
这样关系就清楚了
viewer.render() --> sim.render() --> _render_context_offscreen.render&read_pixels
那为什么不直接sim.render
,还要多出来一个viewer
呢