Mujoco制作模拟视频

录制的原理就是:按照帧率,不断地采集一幅幅模拟的图像,然后通过ffmepg制作成视频


This code sample simulates the passive dynamics of a given model, renders it offscreen, reads the color and depth pixel values, and saves them into a raw data file that can then be converted into a movie file with tools such as ffmpeg. The rendering is simplified compared to simulate.cc because there is no user interaction, visualization options or timing; instead we simply render with the default settings as fast as possible. The dimensions and number of multi-samples for the offscreen buffer are specified in the MuJoCo model, while the simulation duration, frames-per-second to be rendered (usually much less than the physics simulation rate), and output file name are specified as command-line arguments. For example, a 5 second animation at 60 frames per second is created with:

render humanoid.xml 5 60 rgb.out

The default humanoid.xml model specifies offscreen rendering with 800x800 resolution. With this information in hand, we can compress the (large) raw date file into a playable movie file:

ffmpeg -f rawvideo -pixel_format rgb24 -video_size 800x800
  -framerate 60 -i rgb.out -vf "vflip" video.mp4

但是我试验失败好几次,发现ffmpeg里面的有尺寸的要求,我们将
模型也做个尺寸设置:

  <visual>
    <map force="0.1" zfar="30"/>
    <rgba haze="0.15 0.25 0.35 1"/>
    <quality shadowsize="4096"/>
    <global offwidth="800" offheight="800"/>
  </visual>

这样就成功制作出视频了


将record.cc编译到自己项目的问题

目前来说,我没办法直接将录制功能嵌入到项目,感觉录制功能
很吃资源,而且录制时用OPENGL来做,测试发现会跟mujoco的UI
有点冲突,导致mujoco的UI不显示。

可能会遇到的问题

使用Mujoco自带的record.cc来改就可以
但是要注意,用g++编译,如果使用gcc编译,
可能会报cstdio找不到这样的错误。
另外,文件名不能过长,不然std::fopen会打不开
record.cc有用到mujoco的array_safe.h,这个是源码提供,可以直接复制
到自己的项目里面

例子:测试可以通过

Mujoco录制模拟视频


进一步的改进和封装

官方原始代码,用起来有点麻烦,需要将其代码摘抄出来,嵌入到我们自己的项目中。
这样子直接插入一大段录制功能的代码,容易让我们自己的项目代码不好看。
我做了代码提取和封装,这样只需在项目中简单调用几个接口就可以了

.h头文件

/**
 * 
 * created by Feisy,2022-02-23
 */
#ifndef RECODER_H_
#define RECODER_H_

#include "mujoco.h"
#include <string>

// select EGL, OSMESA or GLFW
#if defined(MJ_EGL)
#include <EGL/egl.h>
#elif defined(MJ_OSMESA)
#include <GL/osmesa.h>
OSMesaContext ctx;
unsigned char buffer[10000000];
#else
#include <GLFW/glfw3.h>
#endif

#include "array_safety.h"
namespace mju = ::mujoco::sample_util;

class Recoder
{
public:
    Recoder();
    virtual ~Recoder();

public:
    int Init(mjModel* m);
    int Setting(std::string &save_file,int fps,int duration);

    /**
     * cur_time is d->time or other type time you used,also yi can be real elapsed time
     */
    int Run(mjModel* m,mjData* d ,mjvCamera &cam,mjvOption &opt,double cur_time);
    void Release();


private:
    void init_opengl();

private:
    bool m_bInit;
private:
    int m_fps;
    int m_duration;
    std::string m_file;

private:
    double m_frametime;
    int m_framecount;

private:
    int m_W;
    int m_H;
    std::FILE *m_fp;
    unsigned char *m_rgb;
    float *m_depth;
    mjrRect m_viewport;
    mjvScene m_scn;
    mjrContext m_con;
};

#endif

.cpp文件

#include "Recoder.h"

Recoder::Recoder()
{
  m_bInit = false;
}
Recoder::~Recoder()
{
}

int Recoder::Init(mjModel *m)
{
  init_opengl();// should call first

  mjv_defaultScene(&m_scn);
  mjr_defaultContext(&m_con);

  mjv_makeScene(m, &m_scn, 2000);
  mjr_makeContext(m, &m_con, 200);

  // set rendering to offscreen buffer
  mjr_setBuffer(mjFB_OFFSCREEN, &m_con);
  if (m_con.currentBuffer != mjFB_OFFSCREEN)
  {
    std::printf("Warning: offscreen rendering not supported, using default/window framebuffer\n");
  }

  // get size of active renderbuffer
  m_viewport = mjr_maxViewport(&m_con);
  m_W = m_viewport.width;
  m_H = m_viewport.height;

  // allocate rgb and depth buffers
  m_rgb = (unsigned char *)std::malloc(3 * m_W * m_H);
  m_depth = (float *)std::malloc(sizeof(float) * m_W * m_H);
  if (!m_rgb || !m_depth)
  {
    mju_error("Could not allocate buffers");
  }
  m_frametime = 0.0;
  m_framecount = 0;
  m_bInit = true;
  printf("recoder init finish\n");
  return 0;
}

int Recoder::Setting(std::string &save_file, int fps, int duration)
{
  m_fps = fps;
  m_duration = duration;
  m_file = std::string(save_file.data());

  // create output rgb file
  m_fp = std::fopen(m_file.data(), "wb");
  if (!m_fp)
  {
    mju_error("Could not open rgbfile for writing");
  }
  printf("recoder setting finish\n");
  return 0;
}

int Recoder::Run(mjModel *m, mjData *d, mjvCamera &cam, mjvOption &opt, double cur_time)
{
  if (cur_time > m_duration)
  {
    return 1;
  }

  // render new frame if it is time (or first frame)
  if ((cur_time - m_frametime) > 1 / m_fps || m_frametime == 0)
  {
    // update abstract scene
    mjv_updateScene(m, d, &opt, NULL, &cam, mjCAT_ALL, &m_scn);

    // render scene in offscreen buffer
    mjr_render(m_viewport, &m_scn, &m_con);

    // add time stamp in upper-left corner
    char stamp[50];
    mju::sprintf_arr(stamp, "Time = %.3f", cur_time);
    mjr_overlay(mjFONT_NORMAL, mjGRID_TOPLEFT, m_viewport, stamp, NULL, &m_con);

    // read rgb and depth buffers
    mjr_readPixels(m_rgb, m_depth, m_viewport, &m_con);

    // insert subsampled depth image in lower-left corner of rgb image
    const int NS = 3; // depth image sub-sampling
    for (int r = 0; r < m_H; r += NS)
      for (int c = 0; c < m_W; c += NS)
      {
        int adr = (r / NS) * m_W + c / NS;
        m_rgb[3 * adr] = m_rgb[3 * adr + 1] = m_rgb[3 * adr + 2] = (unsigned char)((1.0f - m_depth[r * m_W + c]) * 255.0f);
      }

    // write rgb image to file
    std::fwrite(m_rgb, 3, m_W * m_H, m_fp);

    // print every 10 frames: '.' if ok, 'x' if OpenGL error
    if (((m_framecount++) % 10) == 0)
    {
      if (mjr_getError())
      {
        std::printf("x");
      }
      else
      {
        std::printf(".");
      }
    }

    // save simulation time
    m_frametime = cur_time;
  }
  return 0;
}

void Recoder::Release()
{
  if (false == m_bInit)
  {
    return;
  }
  m_bInit = false;
  mjr_freeContext(&m_con);
  mjv_freeScene(&m_scn);

  // close file, free buffers
  std::fclose(m_fp);
  std::free(m_rgb);
  std::free(m_depth);

#if defined(MJ_EGL)
  // get current display
  EGLDisplay eglDpy = eglGetCurrentDisplay();
  if (eglDpy == EGL_NO_DISPLAY)
  {
    return;
  }

  // get current context
  EGLContext eglCtx = eglGetCurrentContext();

  // release context
  eglMakeCurrent(eglDpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

  // destroy context if valid
  if (eglCtx != EGL_NO_CONTEXT)
  {
    eglDestroyContext(eglDpy, eglCtx);
  }

  // terminate display
  eglTerminate(eglDpy);

  //------------------------ OSMESA
#elif defined(MJ_OSMESA)
  OSMesaDestroyContext(ctx);

  //------------------------ GLFW
#else
// terminate GLFW (crashes with Linux NVidia drivers)
#if defined(__APPLE__) || defined(_WIN32)
  glfwTerminate();
#endif
#endif
}

// create OpenGL context/window
void Recoder::init_opengl()
{
  //------------------------ EGL
#if defined(MJ_EGL)
  // desired config
  const EGLint configAttribs[] = {
      EGL_RED_SIZE, 8,
      EGL_GREEN_SIZE, 8,
      EGL_BLUE_SIZE, 8,
      EGL_ALPHA_SIZE, 8,
      EGL_DEPTH_SIZE, 24,
      EGL_STENCIL_SIZE, 8,
      EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
      EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
      EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
      EGL_NONE};

  // get default display
  EGLDisplay eglDpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  if (eglDpy == EGL_NO_DISPLAY)
  {
    mju_error_i("Could not get EGL display, error 0x%x\n", eglGetError());
  }

  // initialize
  EGLint major, minor;
  if (eglInitialize(eglDpy, &major, &minor) != EGL_TRUE)
  {
    mju_error_i("Could not initialize EGL, error 0x%x\n", eglGetError());
  }

  // choose config
  EGLint numConfigs;
  EGLConfig eglCfg;
  if (eglChooseConfig(eglDpy, configAttribs, &eglCfg, 1, &numConfigs) != EGL_TRUE)
  {
    mju_error_i("Could not choose EGL config, error 0x%x\n", eglGetError());
  }

  // bind OpenGL API
  if (eglBindAPI(EGL_OPENGL_API) != EGL_TRUE)
  {
    mju_error_i("Could not bind EGL OpenGL API, error 0x%x\n", eglGetError());
  }

  // create context
  EGLContext eglCtx = eglCreateContext(eglDpy, eglCfg, EGL_NO_CONTEXT, NULL);
  if (eglCtx == EGL_NO_CONTEXT)
  {
    mju_error_i("Could not create EGL context, error 0x%x\n", eglGetError());
  }

  // make context current, no surface (let OpenGL handle FBO)
  if (eglMakeCurrent(eglDpy, EGL_NO_SURFACE, EGL_NO_SURFACE, eglCtx) != EGL_TRUE)
  {
    mju_error_i("Could not make EGL context current, error 0x%x\n", eglGetError());
  }

  //------------------------ OSMESA
#elif defined(MJ_OSMESA)
  // create context
  ctx = OSMesaCreateContextExt(GL_RGBA, 24, 8, 8, 0);
  if (!ctx)
  {
    mju_error("OSMesa context creation failed");
  }

  // make current
  if (!OSMesaMakeCurrent(ctx, buffer, GL_UNSIGNED_BYTE, 800, 800))
  {
    mju_error("OSMesa make current failed");
  }

  //------------------------ GLFW
#else
  // init GLFW
  if (!glfwInit())
  {
    mju_error("Could not initialize GLFW");
  }

  // create invisible window, single-buffered
  glfwWindowHint(GLFW_VISIBLE, 0);
  glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_FALSE);
  GLFWwindow *window = glfwCreateWindow(800, 800, "Invisible window", NULL, NULL);
  if (!window)
  {
    mju_error("Could not create GLFW window");
  }

  // make context current
  glfwMakeContextCurrent(window);
#endif
}


原始官方的代码

// Copyright 2021 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstdio>
#include <cstdlib>
#include <cstring>

#include "mujoco.h"

// select EGL, OSMESA or GLFW
#if defined(MJ_EGL)
  #include <EGL/egl.h>
#elif defined(MJ_OSMESA)
  #include <GL/osmesa.h>
  OSMesaContext ctx;
  unsigned char buffer[10000000];
#else
  #include <GLFW/glfw3.h>
#endif

#include "array_safety.h"
namespace mju = ::mujoco::sample_util;

//-------------------------------- global data ------------------------------------------
char xmlpath[] = "./dbpendulum/doublependulum.xml";
char rgb_file[] = "./rgb.out";
// MuJoCo model and data
mjModel* m = 0;
mjData* d = 0;

// MuJoCo visualization
mjvScene scn;//关于使用OPENGL渲染的设置
mjvCamera cam;
mjvOption opt;
mjrContext con;//关于OPENGL的上下文


//-------------------------------- utility functions ------------------------------------

// load model, init simulation and rendering
//加载模型,以及设置相机的参数
void initMuJoCo(const char* filename) {
  // load and compile
  char error[1000] = "Could not load binary model";
  if (std::strlen(filename)>4 && !std::strcmp(filename+std::strlen(filename)-4, ".mjb")) {
    m = mj_loadModel(filename, 0);
  } else {
    m = mj_loadXML(filename, 0, error, 1000);
  }
  if (!m) {
    mju_error_s("Load model error: %s", error);
  }

  // make data, run one computation to initialize all fields
  d = mj_makeData(m);
  mj_forward(m, d);

  // initialize visualization data structures
  mjv_defaultCamera(&cam);
  mjv_defaultOption(&opt);
  mjv_defaultScene(&scn);
  mjr_defaultContext(&con);

  // create scene and context
  mjv_makeScene(m, &scn, 2000);
  mjr_makeContext(m, &con, 200);

  // center and scale view
  //这里的相机是看向一个固定点,对于动态移动的物体,考虑用d->qpos等信息动态追踪
    double arr_view[] = {89.608063, -11.588379, 5, 0.000000, 0.000000, 2.000000};
    cam.azimuth = arr_view[0];
    cam.elevation = arr_view[1];
    cam.distance = arr_view[2];
    cam.lookat[0] = arr_view[3];
    cam.lookat[1] = arr_view[4];
    cam.lookat[2] = arr_view[5];

}


// deallocate everything
void closeMuJoCo(void) {
  mj_deleteData(d);
  mj_deleteModel(m);
  mjr_freeContext(&con);
  mjv_freeScene(&scn);
}


// create OpenGL context/window
//OPENGL环境的构建
void initOpenGL(void) {
  //------------------------ EGL
#if defined(MJ_EGL)
  // desired config
  const EGLint configAttribs[] = {
    EGL_RED_SIZE,           8,
    EGL_GREEN_SIZE,         8,
    EGL_BLUE_SIZE,          8,
    EGL_ALPHA_SIZE,         8,
    EGL_DEPTH_SIZE,         24,
    EGL_STENCIL_SIZE,       8,
    EGL_COLOR_BUFFER_TYPE,  EGL_RGB_BUFFER,
    EGL_SURFACE_TYPE,       EGL_PBUFFER_BIT,
    EGL_RENDERABLE_TYPE,    EGL_OPENGL_BIT,
    EGL_NONE
  };

  // get default display
  EGLDisplay eglDpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  if (eglDpy==EGL_NO_DISPLAY) {
    mju_error_i("Could not get EGL display, error 0x%x\n", eglGetError());
  }

  // initialize
  EGLint major, minor;
  if (eglInitialize(eglDpy, &major, &minor)!=EGL_TRUE) {
    mju_error_i("Could not initialize EGL, error 0x%x\n", eglGetError());
  }

  // choose config
  EGLint numConfigs;
  EGLConfig eglCfg;
  if (eglChooseConfig(eglDpy, configAttribs, &eglCfg, 1, &numConfigs)!=EGL_TRUE) {
    mju_error_i("Could not choose EGL config, error 0x%x\n", eglGetError());
  }

  // bind OpenGL API
  if (eglBindAPI(EGL_OPENGL_API)!=EGL_TRUE) {
    mju_error_i("Could not bind EGL OpenGL API, error 0x%x\n", eglGetError());
  }

  // create context
  EGLContext eglCtx = eglCreateContext(eglDpy, eglCfg, EGL_NO_CONTEXT, NULL);
  if (eglCtx==EGL_NO_CONTEXT) {
    mju_error_i("Could not create EGL context, error 0x%x\n", eglGetError());
  }

  // make context current, no surface (let OpenGL handle FBO)
  if (eglMakeCurrent(eglDpy, EGL_NO_SURFACE, EGL_NO_SURFACE, eglCtx)!=EGL_TRUE) {
    mju_error_i("Could not make EGL context current, error 0x%x\n", eglGetError());
  }

  //------------------------ OSMESA
#elif defined(MJ_OSMESA)
  // create context
  ctx = OSMesaCreateContextExt(GL_RGBA, 24, 8, 8, 0);
  if (!ctx) {
    mju_error("OSMesa context creation failed");
  }

  // make current
  if (!OSMesaMakeCurrent(ctx, buffer, GL_UNSIGNED_BYTE, 800, 800)) {
    mju_error("OSMesa make current failed");
  }

  //------------------------ GLFW
#else
  // init GLFW
  if (!glfwInit()) {
    mju_error("Could not initialize GLFW");
  }

  // create invisible window, single-buffered
  glfwWindowHint(GLFW_VISIBLE, 0);
  glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_FALSE);
  GLFWwindow* window = glfwCreateWindow(800, 800, "Invisible window", NULL, NULL);
  if (!window) {
    mju_error("Could not create GLFW window");
  }

  // make context current
  glfwMakeContextCurrent(window);
#endif
}


// close OpenGL context/window
void closeOpenGL(void) {
  //------------------------ EGL
#if defined(MJ_EGL)
  // get current display
  EGLDisplay eglDpy = eglGetCurrentDisplay();
  if (eglDpy==EGL_NO_DISPLAY) {
    return;
  }

  // get current context
  EGLContext eglCtx = eglGetCurrentContext();

  // release context
  eglMakeCurrent(eglDpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

  // destroy context if valid
  if (eglCtx!=EGL_NO_CONTEXT) {
    eglDestroyContext(eglDpy, eglCtx);
  }

  // terminate display
  eglTerminate(eglDpy);

  //------------------------ OSMESA
#elif defined(MJ_OSMESA)
  OSMesaDestroyContext(ctx);

  //------------------------ GLFW
#else
  // terminate GLFW (crashes with Linux NVidia drivers)
  #if defined(__APPLE__) || defined(_WIN32)
    glfwTerminate();
  #endif
#endif
}


//-------------------------------- main function ----------------------------------------

int main(int argc, const char** argv) {
 
  // parse numeric arguments
  double duration = 10, fps = 60;
 
  // initialize OpenGL and MuJoCo
  initOpenGL();
  initMuJoCo(xmlpath);

  // set rendering to offscreen buffer
  mjr_setBuffer(mjFB_OFFSCREEN, &con);
  if (con.currentBuffer!=mjFB_OFFSCREEN) {
    std::printf("Warning: offscreen rendering not supported, using default/window framebuffer\n");
  }

  // get size of active renderbuffer
  mjrRect viewport =  mjr_maxViewport(&con);
  int W = viewport.width;
  int H = viewport.height;

  // allocate rgb and depth buffers
  //一幅图像有长和宽,每个像素个R,G,B三个通道,depth是OPENGL的概念
  unsigned char* rgb = (unsigned char*)std::malloc(3*W*H);
  float* depth = (float*)std::malloc(sizeof(float)*W*H);
  if (!rgb || !depth) {
    mju_error("Could not allocate buffers");
  }

  // create output rgb file
  std::FILE* fp = std::fopen(rgb_file, "wb");
  if (!fp) {
    mju_error("Could not open rgbfile for writing");
  }


  d->qpos[0] = 0.5;
  // main loop
  double frametime = 0;
  int framecount = 0;
  while (d->time<duration) {
    // render new frame if it is time (or first frame)
    if ((d->time-frametime)>1/fps || frametime==0) {
      // update abstract scene 模型以及状态刷新到场景中
      mjv_updateScene(m, d, &opt, NULL, &cam, mjCAT_ALL, &scn);

      // render scene in offscreen buffer 将场景画出来
      mjr_render(viewport, &scn, &con);

      // add time stamp in upper-left corner
      char stamp[50];
      mju::sprintf_arr(stamp, "Time = %.3f", d->time);
      mjr_overlay(mjFONT_NORMAL, mjGRID_TOPLEFT, viewport, stamp, NULL, &con);

      // read rgb and depth buffers 从OPENGL的上下文中读取出当前画好的内容
      mjr_readPixels(rgb, depth, viewport, &con);

      // insert subsampled depth image in lower-left corner of rgb image
      const int NS = 3;           // depth image sub-sampling
      for (int r=0; r<H; r+=NS)
        for (int c=0; c<W; c+=NS) {
          int adr = (r/NS)*W + c/NS;
          rgb[3*adr] = rgb[3*adr+1] = rgb[3*adr+2] = (unsigned char)((1.0f-depth[r*W+c])*255.0f);
        }

      // write rgb image to file
      std::fwrite(rgb, 3, W*H, fp);

      // print every 10 frames: '.' if ok, 'x' if OpenGL error
      if (((framecount++)%10)==0) {
        if (mjr_getError()) {
          std::printf("x");
        } else {
          std::printf(".");
        }
      }

      // save simulation time
      frametime = d->time;
    }

    // advance simulation
    mj_step(m, d);
  }
  std::printf("\n");

  // close file, free buffers
  std::fclose(fp);
  std::free(rgb);
  std::free(depth);

  // close MuJoCo and OpenGL
  closeMuJoCo();
  closeOpenGL();

  return 1;
}

#MAC
#COMMON=-O2 -I../../include -L../../bin -mavx -pthread
#LIBS = -w -lmujoco200 -lglfw.3
#CC = gcc

#LINUX
#COMMON=-O2 -I../../include -L../../bin -mavx -pthread -Wl,-rpath,'$$ORIGIN'
#LIBS = -lmujoco200 -lGL -lm -lglew ../../bin/libglfw.so.3
COMMON=-O2   -I../include -L../bin -mavx -pthread -Wl,-rpath,'$$ORIGIN' 
LIBS = ../bin/libmujoco.so -lGL -lm ../bin/libglew.so ../bin/libglfw.so.3
CC = g++

#WINDOWS
#COMMON=/O2 /MT /EHsc /arch:AVX /I../../include /Fe../../bin/
#LIBS = ../../bin/glfw3.lib  ../../bin/mujoco200.lib
#CC = cl

ROOT = dbpendulum

RECORD=db_record

all:
	$(CC) $(COMMON) main.c $(LIBS) -o ../bin/$(ROOT)
	$(CC) $(COMMON) record.c $(LIBS) -o ../bin/$(RECORD)

main.o:
	$(CC) $(COMMON) -c main.c

record.o:
	$(CC) $(COMMON) -c record.c

clean:
	rm *.o ../../bin/$(ROOT)

record.cc代码

// Copyright 2021 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstdio>
#include <cstdlib>
#include <cstring>

#include "mujoco.h"

// select EGL, OSMESA or GLFW
#if defined(MJ_EGL)
  #include <EGL/egl.h>
#elif defined(MJ_OSMESA)
  #include <GL/osmesa.h>
  OSMesaContext ctx;
  unsigned char buffer[10000000];
#else
  #include <GLFW/glfw3.h>
#endif

#include "array_safety.h"
namespace mju = ::mujoco::sample_util;

//-------------------------------- global data ------------------------------------------

// MuJoCo model and data
mjModel* m = 0;
mjData* d = 0;

// MuJoCo visualization
mjvScene scn;
mjvCamera cam;
mjvOption opt;
mjrContext con;


//-------------------------------- utility functions ------------------------------------

// load model, init simulation and rendering
void initMuJoCo(const char* filename) {
  // load and compile
  char error[1000] = "Could not load binary model";
  if (std::strlen(filename)>4 && !std::strcmp(filename+std::strlen(filename)-4, ".mjb")) {
    m = mj_loadModel(filename, 0);
  } else {
    m = mj_loadXML(filename, 0, error, 1000);
  }
  if (!m) {
    mju_error_s("Load model error: %s", error);
  }

  // make data, run one computation to initialize all fields
  d = mj_makeData(m);
  mj_forward(m, d);

  // initialize visualization data structures
  mjv_defaultCamera(&cam);
  mjv_defaultOption(&opt);
  mjv_defaultScene(&scn);
  mjr_defaultContext(&con);

  // create scene and context
  mjv_makeScene(m, &scn, 2000);
  mjr_makeContext(m, &con, 200);

  // center and scale view
  cam.lookat[0] = m->stat.center[0];
  cam.lookat[1] = m->stat.center[1];
  cam.lookat[2] = m->stat.center[2];
  cam.distance = 1.5 * m->stat.extent;
}


// deallocate everything
void closeMuJoCo(void) {
  mj_deleteData(d);
  mj_deleteModel(m);
  mjr_freeContext(&con);
  mjv_freeScene(&scn);
}


// create OpenGL context/window
void initOpenGL(void) {
  //------------------------ EGL
#if defined(MJ_EGL)
  // desired config
  const EGLint configAttribs[] = {
    EGL_RED_SIZE,           8,
    EGL_GREEN_SIZE,         8,
    EGL_BLUE_SIZE,          8,
    EGL_ALPHA_SIZE,         8,
    EGL_DEPTH_SIZE,         24,
    EGL_STENCIL_SIZE,       8,
    EGL_COLOR_BUFFER_TYPE,  EGL_RGB_BUFFER,
    EGL_SURFACE_TYPE,       EGL_PBUFFER_BIT,
    EGL_RENDERABLE_TYPE,    EGL_OPENGL_BIT,
    EGL_NONE
  };

  // get default display
  EGLDisplay eglDpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  if (eglDpy==EGL_NO_DISPLAY) {
    mju_error_i("Could not get EGL display, error 0x%x\n", eglGetError());
  }

  // initialize
  EGLint major, minor;
  if (eglInitialize(eglDpy, &major, &minor)!=EGL_TRUE) {
    mju_error_i("Could not initialize EGL, error 0x%x\n", eglGetError());
  }

  // choose config
  EGLint numConfigs;
  EGLConfig eglCfg;
  if (eglChooseConfig(eglDpy, configAttribs, &eglCfg, 1, &numConfigs)!=EGL_TRUE) {
    mju_error_i("Could not choose EGL config, error 0x%x\n", eglGetError());
  }

  // bind OpenGL API
  if (eglBindAPI(EGL_OPENGL_API)!=EGL_TRUE) {
    mju_error_i("Could not bind EGL OpenGL API, error 0x%x\n", eglGetError());
  }

  // create context
  EGLContext eglCtx = eglCreateContext(eglDpy, eglCfg, EGL_NO_CONTEXT, NULL);
  if (eglCtx==EGL_NO_CONTEXT) {
    mju_error_i("Could not create EGL context, error 0x%x\n", eglGetError());
  }

  // make context current, no surface (let OpenGL handle FBO)
  if (eglMakeCurrent(eglDpy, EGL_NO_SURFACE, EGL_NO_SURFACE, eglCtx)!=EGL_TRUE) {
    mju_error_i("Could not make EGL context current, error 0x%x\n", eglGetError());
  }

  //------------------------ OSMESA
#elif defined(MJ_OSMESA)
  // create context
  ctx = OSMesaCreateContextExt(GL_RGBA, 24, 8, 8, 0);
  if (!ctx) {
    mju_error("OSMesa context creation failed");
  }

  // make current
  if (!OSMesaMakeCurrent(ctx, buffer, GL_UNSIGNED_BYTE, 800, 800)) {
    mju_error("OSMesa make current failed");
  }

  //------------------------ GLFW
#else
  // init GLFW
  if (!glfwInit()) {
    mju_error("Could not initialize GLFW");
  }

  // create invisible window, single-buffered
  glfwWindowHint(GLFW_VISIBLE, 0);
  glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_FALSE);
  GLFWwindow* window = glfwCreateWindow(800, 800, "Invisible window", NULL, NULL);
  if (!window) {
    mju_error("Could not create GLFW window");
  }

  // make context current
  glfwMakeContextCurrent(window);
#endif
}


// close OpenGL context/window
void closeOpenGL(void) {
  //------------------------ EGL
#if defined(MJ_EGL)
  // get current display
  EGLDisplay eglDpy = eglGetCurrentDisplay();
  if (eglDpy==EGL_NO_DISPLAY) {
    return;
  }

  // get current context
  EGLContext eglCtx = eglGetCurrentContext();

  // release context
  eglMakeCurrent(eglDpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

  // destroy context if valid
  if (eglCtx!=EGL_NO_CONTEXT) {
    eglDestroyContext(eglDpy, eglCtx);
  }

  // terminate display
  eglTerminate(eglDpy);

  //------------------------ OSMESA
#elif defined(MJ_OSMESA)
  OSMesaDestroyContext(ctx);

  //------------------------ GLFW
#else
  // terminate GLFW (crashes with Linux NVidia drivers)
  #if defined(__APPLE__) || defined(_WIN32)
    glfwTerminate();
  #endif
#endif
}


//-------------------------------- main function ----------------------------------------

int main(int argc, const char** argv) {
  // check command-line arguments
  if (argc!=5) {
    std::printf(" USAGE:  record modelfile duration fps rgbfile\n");
    return 0;
  }

  // parse numeric arguments
  double duration = 10, fps = 30;
  std::sscanf(argv[2], "%lf", &duration);
  std::sscanf(argv[3], "%lf", &fps);

  // initialize OpenGL and MuJoCo
  initOpenGL();
  initMuJoCo(argv[1]);

  // set rendering to offscreen buffer
  mjr_setBuffer(mjFB_OFFSCREEN, &con);
  if (con.currentBuffer!=mjFB_OFFSCREEN) {
    std::printf("Warning: offscreen rendering not supported, using default/window framebuffer\n");
  }

  // get size of active renderbuffer
  mjrRect viewport =  mjr_maxViewport(&con);
  int W = viewport.width;
  int H = viewport.height;

  // allocate rgb and depth buffers
  unsigned char* rgb = (unsigned char*)std::malloc(3*W*H);
  float* depth = (float*)std::malloc(sizeof(float)*W*H);
  if (!rgb || !depth) {
    mju_error("Could not allocate buffers");
  }

  // create output rgb file
  std::FILE* fp = std::fopen(argv[4], "wb");
  if (!fp) {
    mju_error("Could not open rgbfile for writing");
  }

  // main loop
  double frametime = 0;
  int framecount = 0;
  while (d->time<duration) {
    // render new frame if it is time (or first frame)
    if ((d->time-frametime)>1/fps || frametime==0) {
      // update abstract scene
      mjv_updateScene(m, d, &opt, NULL, &cam, mjCAT_ALL, &scn);

      // render scene in offscreen buffer
      mjr_render(viewport, &scn, &con);

      // add time stamp in upper-left corner
      char stamp[50];
      mju::sprintf_arr(stamp, "Time = %.3f", d->time);
      mjr_overlay(mjFONT_NORMAL, mjGRID_TOPLEFT, viewport, stamp, NULL, &con);

      // read rgb and depth buffers
      mjr_readPixels(rgb, depth, viewport, &con);

      // insert subsampled depth image in lower-left corner of rgb image
      const int NS = 3;           // depth image sub-sampling
      for (int r=0; r<H; r+=NS)
        for (int c=0; c<W; c+=NS) {
          int adr = (r/NS)*W + c/NS;
          rgb[3*adr] = rgb[3*adr+1] = rgb[3*adr+2] = (unsigned char)((1.0f-depth[r*W+c])*255.0f);
        }

      // write rgb image to file
      std::fwrite(rgb, 3, W*H, fp);

      // print every 10 frames: '.' if ok, 'x' if OpenGL error
      if (((framecount++)%10)==0) {
        if (mjr_getError()) {
          std::printf("x");
        } else {
          std::printf(".");
        }
      }

      // save simulation time
      frametime = d->time;
    }

    // advance simulation
    mj_step(m, d);
  }
  std::printf("\n");

  // close file, free buffers
  std::fclose(fp);
  std::free(rgb);
  std::free(depth);

  // close MuJoCo and OpenGL
  closeMuJoCo();
  closeOpenGL();

  return 1;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值