录制的原理就是:按照帧率,不断地采集一幅幅模拟的图像,然后通过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>
这样就成功制作出视频了
目前来说,我没办法直接将录制功能嵌入到项目,感觉录制功能
很吃资源,而且录制时用OPENGL来做,测试发现会跟mujoco的UI
有点冲突,导致mujoco的UI不显示。
使用Mujoco自带的record.cc来改就可以
但是要注意,用g++编译,如果使用gcc编译,
可能会报cstdio找不到这样的错误。
另外,文件名不能过长,不然std::fopen会打不开
record.cc有用到mujoco的array_safe.h,这个是源码提供,可以直接复制
到自己的项目里面
Mujoco录制模拟视频
官方原始代码,用起来有点麻烦,需要将其代码摘抄出来,嵌入到我们自己的项目中。
这样子直接插入一大段录制功能的代码,容易让我们自己的项目代码不好看。
我做了代码提取和封装,这样只需在项目中简单调用几个接口就可以了
/**
*
* 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
#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)
// 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;
}