From fc7b2123698f0da26c818f8cbbd1aafa94ed5bfb Mon Sep 17 00:00:00 2001 From: Levi Burner Date: Wed, 14 Jun 2023 14:42:42 -0400 Subject: [PATCH 1/9] Support reversed Z rendering and float32 offscreen depth buffer Combined, both options greatly improve camera depth accuracy compared to OpenGL default Z mapping and int24 depth buffer. Requires GL_ARB_clip_control and ARB_depth_buffer_float extensions --- include/mujoco/mjrender.h | 15 ++++++++++ introspect/enums.py | 18 ++++++++++++ introspect/structs.py | 10 +++++++ python/mujoco/render.cc | 34 ++++++++++++++++++++++ simulate/platform_ui_adapter.cc | 1 + src/render/glad/glad.c | 18 ++++++++++-- src/render/glad/glad.h | 28 +++++++++++++++++-- src/render/render_context.c | 36 ++++++++++++++++++------ src/render/render_gl3.c | 42 ++++++++++++++++++++++++++-- unity/Runtime/Bindings/MjBindings.cs | 8 ++++++ 10 files changed, 193 insertions(+), 17 deletions(-) diff --git a/include/mujoco/mjrender.h b/include/mujoco/mjrender.h index fb37d92663..cd11423b55 100644 --- a/include/mujoco/mjrender.h +++ b/include/mujoco/mjrender.h @@ -39,6 +39,15 @@ typedef enum mjtFramebuffer_ { // OpenGL framebuffer option mjFB_OFFSCREEN // offscreen buffer } mjtFramebuffer; +typedef enum mjtDepthMapping_ { // OpenGL depth buffer mapping (from znear to zfar) + mjDB_NEGONETOONE = 0, // Negative one to one (OpenGL default) + mjDB_ONETOZERO // Reversed Z buffer (decreases numerical error) +} mjtDepthMapping; + +typedef enum mjtDepthPrecision_ { // OpenGL depth buffer precision + mjDB_INT24 = 0, // 24 bit integer buffer (OpenGL default) + mjDB_FLOAT32 // 32 bit float buffer +} mjtDepthPrecision; typedef enum mjtFontScale_ { // font scale, used at context creation mjFONTSCALE_50 = 50, // 50% scale, suitable for low-res rendering @@ -151,6 +160,12 @@ struct mjrContext_ { // custom OpenGL context // pixel output format int readPixelFormat; // default color pixel format for mjr_readPixels + + // depth buffer mode + int depthMapping; // depth buffer mapping from [znear zfar] to normalized device coordinates: mjDB_NEGONETOONE or mjDB_ONETOZERO + + // depth buffer precision + int depthPrecision; // depth buffer precision: mjDB_INT24 or mjDB_FLOAT32 }; typedef struct mjrContext_ mjrContext; diff --git a/introspect/enums.py b/introspect/enums.py index fbee5402e2..d14523b3d0 100644 --- a/introspect/enums.py +++ b/introspect/enums.py @@ -621,6 +621,24 @@ ('mjFB_OFFSCREEN', 1), ]), )), + ('mjtDepthMapping', + EnumDecl( + name='mjtDepthMapping', + declname='enum mjtDepthMapping_', + values=dict([ + ('mjDB_NEGONETOONE', 0), + ('mjDB_ONETOZERO', 1), + ]), + )), + ('mjtDepthPrecision', + EnumDecl( + name='mjtDepthPrecision', + declname='enum mjtDepthPrecision_', + values=dict([ + ('mjDB_INT24', 0), + ('mjDB_FLOAT32', 1), + ]), + )), ('mjtFontScale', EnumDecl( name='mjtFontScale', diff --git a/introspect/structs.py b/introspect/structs.py index a06e9a67f2..fd718da4ba 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -7126,6 +7126,16 @@ type=ValueType(name='int'), doc='default color pixel format for mjr_readPixels', ), + StructFieldDecl( + name='depthMapping', + type=ValueType(name='int'), + doc='depth buffer mapping from [znear zfar] to normalized device coordinates: mjDB_NEGONETOONE or mjDB_ONETOZERO', # pylint: disable=line-too-long + ), + StructFieldDecl( + name='depthPrecision', + type=ValueType(name='int'), + doc='depth buffer precision: mjDB_INT24 or mjDB_FLOAT32', + ), ), )), ('mjuiState', diff --git a/python/mujoco/render.cc b/python/mujoco/render.cc index 622bbb7adf..c1d2b9c866 100644 --- a/python/mujoco/render.cc +++ b/python/mujoco/render.cc @@ -34,6 +34,10 @@ class MjWrapper : public WrapperBase { public: MjWrapper(); MjWrapper(const MjModelWrapper& model, int fontscale); + MjWrapper(const MjModelWrapper& model, + int fontscale, + mjtDepthMapping depthmapping, + mjtDepthPrecision depthprecision); MjWrapper(const MjWrapper&) = delete; MjWrapper(MjWrapper&&) = default; ~MjWrapper() = default; @@ -122,6 +126,35 @@ MjrContextWrapper::MjWrapper(const MjModelWrapper& model, int fontscale) X_SKIN(skinfaceVBO), X(charWidth), X(charWidthBig) {} + +MjrContextWrapper::MjWrapper(const MjModelWrapper& model, + int fontscale, + mjtDepthMapping depthmapping, + mjtDepthPrecision depthprecision) + : WrapperBase([fontscale, depthmapping, depthprecision](const raw::MjModel* m) { + raw::MjrContext *const ctx = new raw::MjrContext; + mjr_defaultContext(ctx); + ctx->depthMapping = depthmapping; + ctx->depthPrecision = depthprecision; + InterceptMjErrors(mjr_makeContext)(m, ctx, fontscale); + return ctx; + }(model.get()), &MjrContextCapsuleDestructor), + X(fogRGBA), + X(auxWidth), + X(auxHeight), + X(auxSamples), + X(auxFBO), + X(auxFBO_r), + X(auxColor), + X(auxColor_r), + X(textureType), + X(texture), + X_SKIN(skinvertVBO), + X_SKIN(skinnormalVBO), + X_SKIN(skintexcoordVBO), + X_SKIN(skinfaceVBO), + X(charWidth), + X(charWidthBig) {} #undef X_SKIN #undef X @@ -166,6 +199,7 @@ PYBIND11_MODULE(_render, pymodule) { py::class_ mjrContext(pymodule, "MjrContext"); mjrContext.def(py::init<>()); mjrContext.def(py::init()); + mjrContext.def(py::init()); mjrContext.def( "free", [](MjrContextWrapper& self) { self.Free(); }, py::doc("Frees resources in current active OpenGL context, sets struct " diff --git a/simulate/platform_ui_adapter.cc b/simulate/platform_ui_adapter.cc index 5f502a477e..f5b154ed38 100644 --- a/simulate/platform_ui_adapter.cc +++ b/simulate/platform_ui_adapter.cc @@ -27,6 +27,7 @@ void PlatformUIAdapter::FreeMjrContext() { bool PlatformUIAdapter::RefreshMjrContext(const mjModel* m, int fontscale) { if (m != last_model_ || fontscale != last_fontscale_) { + // con_.depthMapping = mjDB_ONETOZERO; mjr_makeContext(m, &con_, fontscale); last_model_ = m; last_fontscale_ = fontscale; diff --git a/src/render/glad/glad.c b/src/render/glad/glad.c index 9b5e4c6d4b..fa1642c441 100644 --- a/src/render/glad/glad.c +++ b/src/render/glad/glad.c @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -// Based on the OpenGL loader generated by glad 0.1.35 on Mon Mar 21 12:42:33 2022. +// Based on the OpenGL loader generated by glad 0.1.34 on Thu Jun 22 14:40:03 2023. // // The original generated code is distributed under CC0. // https://creativecommons.org/publicdomain/zero/1.0/ @@ -22,6 +22,8 @@ // APIs: gl=1.5 // Profile: compatibility // Extensions: +// GL_ARB_clip_control, +// GL_ARB_depth_buffer_float, // GL_ARB_framebuffer_object, // GL_ARB_seamless_cube_map, // GL_ARB_vertex_buffer_object, @@ -32,9 +34,9 @@ // Reproducible: False // // Commandline: -// --profile="compatibility" --api="gl=1.5" --generator="c" --spec="gl" --extensions="GL_ARB_framebuffer_object,GL_ARB_seamless_cube_map,GL_ARB_vertex_buffer_object,GL_KHR_debug" +// --profile="compatibility" --api="gl=1.5" --generator="c" --spec="gl" --extensions="GL_ARB_clip_control,GL_ARB_depth_buffer_float,GL_ARB_framebuffer_object,GL_ARB_seamless_cube_map,GL_ARB_vertex_buffer_object,GL_KHR_debug" // Online: -// https://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D1.5&extensions=GL_ARB_framebuffer_object&extensions=GL_ARB_seamless_cube_map&extensions=GL_ARB_vertex_buffer_object&extensions=GL_KHR_debug +// https://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D1.5&extensions=GL_ARB_clip_control&extensions=GL_ARB_depth_buffer_float&extensions=GL_ARB_framebuffer_object&extensions=GL_ARB_seamless_cube_map&extensions=GL_ARB_vertex_buffer_object&extensions=GL_KHR_debug #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push @@ -829,10 +831,13 @@ PFNGLWINDOWPOS3IPROC mjGlad_glWindowPos3i = NULL; PFNGLWINDOWPOS3IVPROC mjGlad_glWindowPos3iv = NULL; PFNGLWINDOWPOS3SPROC mjGlad_glWindowPos3s = NULL; PFNGLWINDOWPOS3SVPROC mjGlad_glWindowPos3sv = NULL; +int mjGLAD_GL_ARB_clip_control = 0; +int mjGLAD_GL_ARB_depth_buffer_float = 0; int mjGLAD_GL_ARB_framebuffer_object = 0; int mjGLAD_GL_ARB_seamless_cube_map = 0; int mjGLAD_GL_ARB_vertex_buffer_object = 0; int mjGLAD_GL_KHR_debug = 0; +PFNGLCLIPCONTROLPROC mjGlad_glClipControl = NULL; PFNGLISRENDERBUFFERPROC mjGlad_glIsRenderbuffer = NULL; PFNGLBINDRENDERBUFFERPROC mjGlad_glBindRenderbuffer = NULL; PFNGLDELETERENDERBUFFERSPROC mjGlad_glDeleteRenderbuffers = NULL; @@ -1355,6 +1360,10 @@ static void mjGlad_load_GL_VERSION_1_5(GLADloadproc load) { mjGlad_glGetBufferParameteriv = (PFNGLGETBUFFERPARAMETERIVPROC)load("glGetBufferParameteriv"); mjGlad_glGetBufferPointerv = (PFNGLGETBUFFERPOINTERVPROC)load("glGetBufferPointerv"); } +static void mjGlad_load_GL_ARB_clip_control(GLADloadproc load) { + if(!mjGLAD_GL_ARB_clip_control) return; + mjGlad_glClipControl = (PFNGLCLIPCONTROLPROC)load("glClipControl"); +} static void mjGlad_load_GL_ARB_framebuffer_object(GLADloadproc load) { if(!mjGLAD_GL_ARB_framebuffer_object) return; mjGlad_glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)load("glIsRenderbuffer"); @@ -1419,6 +1428,8 @@ static void mjGlad_load_GL_KHR_debug(GLADloadproc load) { } static int mjGlad_find_extensionsGL(void) { if (!mjGlad_get_exts()) return 0; + mjGLAD_GL_ARB_clip_control = mjGlad_has_ext("GL_ARB_clip_control"); + mjGLAD_GL_ARB_depth_buffer_float = mjGlad_has_ext("GL_ARB_depth_buffer_float"); mjGLAD_GL_ARB_framebuffer_object = mjGlad_has_ext("GL_ARB_framebuffer_object"); mjGLAD_GL_ARB_seamless_cube_map = mjGlad_has_ext("GL_ARB_seamless_cube_map"); mjGLAD_GL_ARB_vertex_buffer_object = mjGlad_has_ext("GL_ARB_vertex_buffer_object"); @@ -1491,6 +1502,7 @@ int mjGladLoadGLUnsafe(void) { mjGlad_load_GL_VERSION_1_5(mjGlad_get_proc); if (!mjGlad_find_extensionsGL()) return 0; + mjGlad_load_GL_ARB_clip_control(mjGlad_get_proc); mjGlad_load_GL_ARB_framebuffer_object(mjGlad_get_proc); mjGlad_load_GL_ARB_vertex_buffer_object(mjGlad_get_proc); mjGlad_load_GL_KHR_debug(mjGlad_get_proc); diff --git a/src/render/glad/glad.h b/src/render/glad/glad.h index 4fc2c6c8c7..1b1fd44993 100644 --- a/src/render/glad/glad.h +++ b/src/render/glad/glad.h @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -// Based on the OpenGL loader generated by glad 0.1.35 on Mon Mar 21 12:42:33 2022. +// Based on the OpenGL loader generated by glad 0.1.34 on Thu Jun 22 14:40:03 2023. // // The original generated code is distributed under CC0. // https://creativecommons.org/publicdomain/zero/1.0/ @@ -22,6 +22,8 @@ // APIs: gl=1.5 // Profile: compatibility // Extensions: +// GL_ARB_clip_control, +// GL_ARB_depth_buffer_float, // GL_ARB_framebuffer_object, // GL_ARB_seamless_cube_map, // GL_ARB_vertex_buffer_object, @@ -32,9 +34,9 @@ // Reproducible: False // // Commandline: -// --profile="compatibility" --api="gl=1.5" --generator="c" --spec="gl" --extensions="GL_ARB_framebuffer_object,GL_ARB_seamless_cube_map,GL_ARB_vertex_buffer_object,GL_KHR_debug" +// --profile="compatibility" --api="gl=1.5" --generator="c" --spec="gl" --extensions="GL_ARB_clip_control,GL_ARB_depth_buffer_float,GL_ARB_framebuffer_object,GL_ARB_seamless_cube_map,GL_ARB_vertex_buffer_object,GL_KHR_debug" // Online: -// https://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D1.5&extensions=GL_ARB_framebuffer_object&extensions=GL_ARB_seamless_cube_map&extensions=GL_ARB_vertex_buffer_object&extensions=GL_KHR_debug +// https://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D1.5&extensions=GL_ARB_clip_control&extensions=GL_ARB_depth_buffer_float&extensions=GL_ARB_framebuffer_object&extensions=GL_ARB_seamless_cube_map&extensions=GL_ARB_vertex_buffer_object&extensions=GL_KHR_debug #ifndef MUJOCO_SRC_RENDER_GLAD_GLAD_H_ #define MUJOCO_SRC_RENDER_GLAD_GLAD_H_ @@ -2304,6 +2306,15 @@ typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC)(GLenum target, GLenum pname, GLAPI PFNGLGETBUFFERPOINTERVPROC mjGlad_glGetBufferPointerv; #define glGetBufferPointerv mjGlad_glGetBufferPointerv #endif +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_NEGATIVE_ONE_TO_ONE 0x935E +#define GL_ZERO_TO_ONE 0x935F +#define GL_CLIP_ORIGIN 0x935C +#define GL_CLIP_DEPTH_MODE 0x935D +#define GL_DEPTH_COMPONENT32F 0x8CAC +#define GL_DEPTH32F_STENCIL8 0x8CAD +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD #define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 #define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 #define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 @@ -2487,6 +2498,17 @@ GLAPI PFNGLGETBUFFERPOINTERVPROC mjGlad_glGetBufferPointerv; #define GL_STACK_OVERFLOW_KHR 0x0503 #define GL_STACK_UNDERFLOW_KHR 0x0504 #define GL_DISPLAY_LIST 0x82E7 +#ifndef GL_ARB_clip_control +#define GL_ARB_clip_control 1 +GLAPI int mjGLAD_GL_ARB_clip_control; +typedef void (APIENTRYP PFNGLCLIPCONTROLPROC)(GLenum origin, GLenum depth); +GLAPI PFNGLCLIPCONTROLPROC mjGlad_glClipControl; +#define glClipControl mjGlad_glClipControl +#endif +#ifndef GL_ARB_depth_buffer_float +#define GL_ARB_depth_buffer_float 1 +GLAPI int mjGLAD_GL_ARB_depth_buffer_float; +#endif #ifndef GL_ARB_framebuffer_object #define GL_ARB_framebuffer_object 1 GLAPI int mjGLAD_GL_ARB_framebuffer_object; diff --git a/src/render/render_context.c b/src/render/render_context.c index 18c7180590..6ba5da826c 100644 --- a/src/render/render_context.c +++ b/src/render/render_context.c @@ -1062,7 +1062,11 @@ static void makeShadow(const mjModel* m, mjrContext* con) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + if (con->depthMapping == mjDB_ONETOZERO) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_GEQUAL); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + } glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); @@ -1121,16 +1125,17 @@ static void makeOff(mjrContext* con) { glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, con->offColor); // create depth and stencil buffer + GLenum depth_buffer_format = con->depthPrecision == mjDB_FLOAT32 ? GL_DEPTH32F_STENCIL8 : GL_DEPTH24_STENCIL8; glGenRenderbuffers(1, &con->offDepthStencil); if (!con->offDepthStencil) { mju_error("Could not allocate offscreen depth and stencil buffer"); } glBindRenderbuffer(GL_RENDERBUFFER, con->offDepthStencil); if (con->offSamples) { - glRenderbufferStorageMultisample(GL_RENDERBUFFER, con->offSamples, GL_DEPTH24_STENCIL8, + glRenderbufferStorageMultisample(GL_RENDERBUFFER, con->offSamples, depth_buffer_format, con->offWidth, con->offHeight); } else { - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, con->offWidth, con->offHeight); + glRenderbufferStorage(GL_RENDERBUFFER, depth_buffer_format, con->offWidth, con->offHeight); } glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, con->offDepthStencil); @@ -1169,7 +1174,7 @@ static void makeOff(mjrContext* con) { mju_error("Could not allocate offscreen depth and stencil buffer_r"); } glBindRenderbuffer(GL_RENDERBUFFER, con->offDepthStencil_r); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, con->offWidth, con->offHeight); + glRenderbufferStorage(GL_RENDERBUFFER, depth_buffer_format, con->offWidth, con->offHeight); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, con->offDepthStencil_r); @@ -1489,6 +1494,12 @@ void mjr_makeContext_offSize(const mjModel* m, mjrContext* con, int fontscale, if (!mjGLAD_GL_ARB_vertex_buffer_object) { mju_error("OpenGL ARB_vertex_buffer_object required"); } + if (!mjGLAD_GL_ARB_clip_control) { + mju_error("OpenGL ARB_clip_control required"); + } + if (!mjGLAD_GL_ARB_depth_buffer_float) { + mju_error("OpenGL ARB_depth_buffer_float required"); + } con->glInitialized = 1; // determine window availability (could be EGL-headless) @@ -1535,8 +1546,15 @@ void mjr_makeContext_offSize(const mjModel* m, mjrContext* con, int fontscale, glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - // free previous context + // free previous context but keep depth settings + // Doing this is the only way users can control these + // settings without breaking changes to either the + // behaviour of mjr_makeContext or the signature + int oldDepthMapping = con->depthMapping; + int oldDepthPrecision = con->depthPrecision; mjr_freeContext(con); + con->depthMapping = oldDepthMapping; + con->depthPrecision = oldDepthPrecision; // no model: offscreen and font only if (!m) { @@ -1828,12 +1846,14 @@ MJAPI void mjr_resizeOffscreen(int width, int height, mjrContext* con) { glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, con->offWidth, con->offHeight); } + + GLenum depth_buffer_format = con->depthPrecision == mjDB_FLOAT32 ? GL_DEPTH32F_STENCIL8 : GL_DEPTH24_STENCIL8; glBindRenderbuffer(GL_RENDERBUFFER, con->offDepthStencil); if (con->offSamples) { - glRenderbufferStorageMultisample(GL_RENDERBUFFER, con->offSamples, GL_DEPTH24_STENCIL8, + glRenderbufferStorageMultisample(GL_RENDERBUFFER, con->offSamples, depth_buffer_format, con->offWidth, con->offHeight); } else { - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, con->offWidth, con->offHeight); + glRenderbufferStorage(GL_RENDERBUFFER, depth_buffer_format, con->offWidth, con->offHeight); } if (con->offSamples) { @@ -1841,6 +1861,6 @@ MJAPI void mjr_resizeOffscreen(int width, int height, mjrContext* con) { glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, con->offWidth, con->offHeight); glBindRenderbuffer(GL_RENDERBUFFER, con->offDepthStencil_r); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, con->offWidth, con->offHeight); + glRenderbufferStorage(GL_RENDERBUFFER, depth_buffer_format, con->offWidth, con->offHeight); } } diff --git a/src/render/render_gl3.c b/src/render/render_gl3.c index 526e130c6e..97e18715a1 100644 --- a/src/render/render_gl3.c +++ b/src/render/render_gl3.c @@ -498,6 +498,11 @@ static void initGL3(const mjvScene* scn, const mjrContext* con) { // common options glDisable(GL_BLEND); glEnable(GL_NORMALIZE); + if (con->depthMapping == mjDB_ONETOZERO) { + glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); + } else { + glClipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); + } glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); if (scn->flags[mjRND_CULL_FACE]) { @@ -506,11 +511,19 @@ static void initGL3(const mjvScene* scn, const mjrContext* con) { glDisable(GL_CULL_FACE); } glShadeModel(GL_SMOOTH); - glDepthFunc(GL_LEQUAL); + if (con->depthMapping == mjDB_ONETOZERO) { + glDepthFunc(GL_GEQUAL); + } else { + glDepthFunc(GL_LEQUAL); + } glDepthRange(0, 1); glAlphaFunc(GL_GEQUAL, 0.99f); glClearColor(0, 0, 0, 0); - glClearDepth(1); + if (con->depthMapping == mjDB_ONETOZERO) { + glClearDepth(0); + } else { + glClearDepth(1); + } glClearStencil(0); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); @@ -600,6 +613,11 @@ static void setView(int view, mjrRect viewport, const mjvScene* scn, const mjrCo // set projection glMatrixMode(GL_PROJECTION); glLoadIdentity(); + if (con->depthMapping == mjDB_ONETOZERO) { + // account for GL_ZERO_TO_ONE and reverse Z + glTranslatef(0,0,0.5); + glScalef(1,1,-0.5); + } glFrustum(cam.frustum_center - halfwidth, cam.frustum_center + halfwidth, cam.frustum_bottom, @@ -672,12 +690,25 @@ void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con) { float temp[4], headpos[3], forward[3], skyboxdst; float camProject[16], camView[16], lightProject[16], lightView[16]; double clipplane[4]; - float biasMatrix[16] = { + float biasMatrixOneToZero[16] = { + 0.5f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.5f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.0f, 1.0f + }; + float biasMatrixNegOneToOne[16] = { 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f }; + float* biasMatrix; + if (con->depthMapping == mjDB_ONETOZERO) { + biasMatrix = biasMatrixOneToZero; + } else { + biasMatrix = biasMatrixNegOneToOne; + } + float tempMatrix[16], textureMatrix[16]; mjvGeom *thisgeom, tempgeom; mjvLight *thislight; @@ -1025,6 +1056,11 @@ void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con) { // set projection: from light viewpoint glMatrixMode(GL_PROJECTION); glLoadIdentity(); + if (con->depthMapping == mjDB_ONETOZERO) { + // account for GL_ZERO_TO_ONE and reverse Z + glTranslatef(0,0,0.5); + glScalef(1,1,-0.5); + } if (thislight->directional) { glOrtho(-con->shadowClip, con->shadowClip, -con->shadowClip, con->shadowClip, diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index ac5c8161cd..40e938b3e6 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -389,6 +389,14 @@ public enum mjtFramebuffer : int{ mjFB_WINDOW = 0, mjFB_OFFSCREEN = 1, } +public enum mjtDepthMapping : int{ + mjDB_NEGONETOONE = 0, + mjDB_ONETOZERO = 1, +} +public enum mjtDepthPrecision : int{ + mjDB_INT24 = 0, + mjDB_FLOAT32 = 1, +} public enum mjtFontScale : int{ mjFONTSCALE_50 = 50, mjFONTSCALE_100 = 100, From 14f1386fc0e377d5880de5c3b99ab7cff93af3fa Mon Sep 17 00:00:00 2001 From: Levi Burner Date: Wed, 21 Jun 2023 17:49:18 -0400 Subject: [PATCH 2/9] renderer.py improve accuracy and support depth precision and mapping flags By more carefully inverting the operations performed when OpenGL transforms metric distance to window coordinates, more accurate depth values can be estimated from the depth buffer --- python/mujoco/renderer.py | 45 +++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/python/mujoco/renderer.py b/python/mujoco/renderer.py index e96cd89e1d..e83f24354e 100644 --- a/python/mujoco/renderer.py +++ b/python/mujoco/renderer.py @@ -32,7 +32,9 @@ def __init__( model: _structs.MjModel, height: int = 240, width: int = 320, - max_geom: int = 10000 + max_geom: int = 10000, + depth_mapping: _enums.mjtDepthMapping = _enums.mjtDepthMapping.mjDB_NEGONETOONE, + depth_precision: _enums.mjtDepthPrecision = _enums.mjtDepthPrecision.mjDB_INT24, ) -> None: """Initializes a new `Renderer`. @@ -43,6 +45,8 @@ def __init__( max_geom: Optional integer specifying the maximum number of geoms that can be rendered in the same scene. If None this will be chosen automatically based on the estimated maximum number of renderable geoms in the model. + depth_mapping: Type of mapping from znear to zfar to z buffer ndc values + depth_precision: Precision of z buffer Raises: ValueError: If `camera_id` is outside the valid range, or if `width` or `height` exceed the dimensions of MuJoCo's offscreen framebuffer. @@ -70,6 +74,7 @@ def __init__( self._width = width self._height = height self._model = model + self._depth_mapping = depth_mapping self._scene = _structs.MjvScene(model=model, maxgeom=max_geom) self._scene_option = _structs.MjvOption() @@ -81,7 +86,7 @@ def __init__( self._gl_context = gl_context.GLContext(width, height) # type: ignore self._gl_context.make_current() self._mjr_context = _render.MjrContext( - model, _enums.mjtFontScale.mjFONTSCALE_150.value + model, _enums.mjtFontScale.mjFONTSCALE_150.value, depth_mapping, depth_precision ) _render.mjr_setBuffer( _enums.mjtFramebuffer.mjFB_OFFSCREEN.value, self._mjr_context @@ -138,7 +143,8 @@ def render(self, *, out: Optional[np.ndarray] = None) -> np.ndarray: """ original_flags = self._scene.flags.copy() - if self._segmentation_rendering: + # Using segmented rendering for depth makes the calculated depth more accurate at far distances + if self._depth_rendering or self._segmentation_rendering: self._scene.flags[_enums.mjtRndFlag.mjRND_SEGMENT] = True self._scene.flags[_enums.mjtRndFlag.mjRND_IDCOLOR] = True @@ -173,11 +179,36 @@ def render(self, *, out: Optional[np.ndarray] = None) -> np.ndarray: near = self._model.vis.map.znear * extent far = self._model.vis.map.zfar * extent - # Convert from [0 1] to depth in units of length, see links below: - # http://stackoverflow.com/a/6657284/1461210 - # https://www.khronos.org/opengl/wiki/Depth_Buffer_Precision - out = near / (1 - out * (1 - near / far)) + # Calculate OpenGL perspective matrix values in float32 precision + # so they are close to what glFrustum returns + # https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glFrustum.xml + zfar = np.float32(far) + znear = np.float32(near) + C = -(zfar + znear)/(zfar - znear) + D = -(np.float32(2)*zfar*znear)/(zfar - znear) + # In reverse Z mode the perspective matrix is transformed by the following + if self._depth_mapping == _enums.mjtDepthMapping.mjDB_ONETOZERO: + C = np.float32(-0.5)*C - np.float32(0.5) + D = np.float32(-0.5)*D + + # We need 64 bits to convert Z from ndc to metric depth without noticeable losses in precision + out_64 = out.astype(np.float64) + + # Convert depth from window coordinates to normalized device coordinates + # In reversed Z mode the mapping is identity + # https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDepthRange.xhtml + if self._depth_mapping == _enums.mjtDepthMapping.mjDB_NEGONETOONE: + out_64 = 2.0*out_64 - 1.0 + + # Undo OpenGL projection + out_64 = D / (out_64 + C) + + # Cast result back to float32 for backwards compatibility + out[:] = out_64.astype(np.float32) + + # Reset scene flags. + np.copyto(self._scene.flags, original_flags) elif self._segmentation_rendering: _render.mjr_readPixels(out, None, self._rect, self._mjr_context) From d32eb6bd123156985fa38653384145559d2fc9e7 Mon Sep 17 00:00:00 2001 From: Levi Burner Date: Tue, 11 Jul 2023 14:10:14 -0400 Subject: [PATCH 3/9] In place change to reverse Z rendering and 32 bit float offscreen depth buffer --- include/mujoco/mjrender.h | 15 --------- introspect/enums.py | 18 ----------- introspect/structs.py | 10 ------ python/mujoco/render.cc | 34 -------------------- python/mujoco/renderer.py | 23 ++++--------- simulate/platform_ui_adapter.cc | 1 - src/render/render_context.c | 28 ++++------------ src/render/render_gl3.c | 48 ++++++---------------------- unity/Runtime/Bindings/MjBindings.cs | 8 ----- 9 files changed, 24 insertions(+), 161 deletions(-) diff --git a/include/mujoco/mjrender.h b/include/mujoco/mjrender.h index cd11423b55..fb37d92663 100644 --- a/include/mujoco/mjrender.h +++ b/include/mujoco/mjrender.h @@ -39,15 +39,6 @@ typedef enum mjtFramebuffer_ { // OpenGL framebuffer option mjFB_OFFSCREEN // offscreen buffer } mjtFramebuffer; -typedef enum mjtDepthMapping_ { // OpenGL depth buffer mapping (from znear to zfar) - mjDB_NEGONETOONE = 0, // Negative one to one (OpenGL default) - mjDB_ONETOZERO // Reversed Z buffer (decreases numerical error) -} mjtDepthMapping; - -typedef enum mjtDepthPrecision_ { // OpenGL depth buffer precision - mjDB_INT24 = 0, // 24 bit integer buffer (OpenGL default) - mjDB_FLOAT32 // 32 bit float buffer -} mjtDepthPrecision; typedef enum mjtFontScale_ { // font scale, used at context creation mjFONTSCALE_50 = 50, // 50% scale, suitable for low-res rendering @@ -160,12 +151,6 @@ struct mjrContext_ { // custom OpenGL context // pixel output format int readPixelFormat; // default color pixel format for mjr_readPixels - - // depth buffer mode - int depthMapping; // depth buffer mapping from [znear zfar] to normalized device coordinates: mjDB_NEGONETOONE or mjDB_ONETOZERO - - // depth buffer precision - int depthPrecision; // depth buffer precision: mjDB_INT24 or mjDB_FLOAT32 }; typedef struct mjrContext_ mjrContext; diff --git a/introspect/enums.py b/introspect/enums.py index d14523b3d0..fbee5402e2 100644 --- a/introspect/enums.py +++ b/introspect/enums.py @@ -621,24 +621,6 @@ ('mjFB_OFFSCREEN', 1), ]), )), - ('mjtDepthMapping', - EnumDecl( - name='mjtDepthMapping', - declname='enum mjtDepthMapping_', - values=dict([ - ('mjDB_NEGONETOONE', 0), - ('mjDB_ONETOZERO', 1), - ]), - )), - ('mjtDepthPrecision', - EnumDecl( - name='mjtDepthPrecision', - declname='enum mjtDepthPrecision_', - values=dict([ - ('mjDB_INT24', 0), - ('mjDB_FLOAT32', 1), - ]), - )), ('mjtFontScale', EnumDecl( name='mjtFontScale', diff --git a/introspect/structs.py b/introspect/structs.py index fd718da4ba..a06e9a67f2 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -7126,16 +7126,6 @@ type=ValueType(name='int'), doc='default color pixel format for mjr_readPixels', ), - StructFieldDecl( - name='depthMapping', - type=ValueType(name='int'), - doc='depth buffer mapping from [znear zfar] to normalized device coordinates: mjDB_NEGONETOONE or mjDB_ONETOZERO', # pylint: disable=line-too-long - ), - StructFieldDecl( - name='depthPrecision', - type=ValueType(name='int'), - doc='depth buffer precision: mjDB_INT24 or mjDB_FLOAT32', - ), ), )), ('mjuiState', diff --git a/python/mujoco/render.cc b/python/mujoco/render.cc index c1d2b9c866..622bbb7adf 100644 --- a/python/mujoco/render.cc +++ b/python/mujoco/render.cc @@ -34,10 +34,6 @@ class MjWrapper : public WrapperBase { public: MjWrapper(); MjWrapper(const MjModelWrapper& model, int fontscale); - MjWrapper(const MjModelWrapper& model, - int fontscale, - mjtDepthMapping depthmapping, - mjtDepthPrecision depthprecision); MjWrapper(const MjWrapper&) = delete; MjWrapper(MjWrapper&&) = default; ~MjWrapper() = default; @@ -126,35 +122,6 @@ MjrContextWrapper::MjWrapper(const MjModelWrapper& model, int fontscale) X_SKIN(skinfaceVBO), X(charWidth), X(charWidthBig) {} - -MjrContextWrapper::MjWrapper(const MjModelWrapper& model, - int fontscale, - mjtDepthMapping depthmapping, - mjtDepthPrecision depthprecision) - : WrapperBase([fontscale, depthmapping, depthprecision](const raw::MjModel* m) { - raw::MjrContext *const ctx = new raw::MjrContext; - mjr_defaultContext(ctx); - ctx->depthMapping = depthmapping; - ctx->depthPrecision = depthprecision; - InterceptMjErrors(mjr_makeContext)(m, ctx, fontscale); - return ctx; - }(model.get()), &MjrContextCapsuleDestructor), - X(fogRGBA), - X(auxWidth), - X(auxHeight), - X(auxSamples), - X(auxFBO), - X(auxFBO_r), - X(auxColor), - X(auxColor_r), - X(textureType), - X(texture), - X_SKIN(skinvertVBO), - X_SKIN(skinnormalVBO), - X_SKIN(skintexcoordVBO), - X_SKIN(skinfaceVBO), - X(charWidth), - X(charWidthBig) {} #undef X_SKIN #undef X @@ -199,7 +166,6 @@ PYBIND11_MODULE(_render, pymodule) { py::class_ mjrContext(pymodule, "MjrContext"); mjrContext.def(py::init<>()); mjrContext.def(py::init()); - mjrContext.def(py::init()); mjrContext.def( "free", [](MjrContextWrapper& self) { self.Free(); }, py::doc("Frees resources in current active OpenGL context, sets struct " diff --git a/python/mujoco/renderer.py b/python/mujoco/renderer.py index e83f24354e..3360523cbd 100644 --- a/python/mujoco/renderer.py +++ b/python/mujoco/renderer.py @@ -32,9 +32,7 @@ def __init__( model: _structs.MjModel, height: int = 240, width: int = 320, - max_geom: int = 10000, - depth_mapping: _enums.mjtDepthMapping = _enums.mjtDepthMapping.mjDB_NEGONETOONE, - depth_precision: _enums.mjtDepthPrecision = _enums.mjtDepthPrecision.mjDB_INT24, + max_geom: int = 10000 ) -> None: """Initializes a new `Renderer`. @@ -45,8 +43,6 @@ def __init__( max_geom: Optional integer specifying the maximum number of geoms that can be rendered in the same scene. If None this will be chosen automatically based on the estimated maximum number of renderable geoms in the model. - depth_mapping: Type of mapping from znear to zfar to z buffer ndc values - depth_precision: Precision of z buffer Raises: ValueError: If `camera_id` is outside the valid range, or if `width` or `height` exceed the dimensions of MuJoCo's offscreen framebuffer. @@ -74,7 +70,6 @@ def __init__( self._width = width self._height = height self._model = model - self._depth_mapping = depth_mapping self._scene = _structs.MjvScene(model=model, maxgeom=max_geom) self._scene_option = _structs.MjvOption() @@ -86,7 +81,7 @@ def __init__( self._gl_context = gl_context.GLContext(width, height) # type: ignore self._gl_context.make_current() self._mjr_context = _render.MjrContext( - model, _enums.mjtFontScale.mjFONTSCALE_150.value, depth_mapping, depth_precision + model, _enums.mjtFontScale.mjFONTSCALE_150.value ) _render.mjr_setBuffer( _enums.mjtFramebuffer.mjFB_OFFSCREEN.value, self._mjr_context @@ -188,20 +183,16 @@ def render(self, *, out: Optional[np.ndarray] = None) -> np.ndarray: D = -(np.float32(2)*zfar*znear)/(zfar - znear) # In reverse Z mode the perspective matrix is transformed by the following - if self._depth_mapping == _enums.mjtDepthMapping.mjDB_ONETOZERO: - C = np.float32(-0.5)*C - np.float32(0.5) - D = np.float32(-0.5)*D + C = np.float32(-0.5)*C - np.float32(0.5) + D = np.float32(-0.5)*D # We need 64 bits to convert Z from ndc to metric depth without noticeable losses in precision out_64 = out.astype(np.float64) - # Convert depth from window coordinates to normalized device coordinates - # In reversed Z mode the mapping is identity - # https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDepthRange.xhtml - if self._depth_mapping == _enums.mjtDepthMapping.mjDB_NEGONETOONE: - out_64 = 2.0*out_64 - 1.0 - # Undo OpenGL projection + # Note: We do not need to take action to convert from window coordinates to + # normalized device coordinates because in reversed Z mode the mapping + # is identity out_64 = D / (out_64 + C) # Cast result back to float32 for backwards compatibility diff --git a/simulate/platform_ui_adapter.cc b/simulate/platform_ui_adapter.cc index f5b154ed38..5f502a477e 100644 --- a/simulate/platform_ui_adapter.cc +++ b/simulate/platform_ui_adapter.cc @@ -27,7 +27,6 @@ void PlatformUIAdapter::FreeMjrContext() { bool PlatformUIAdapter::RefreshMjrContext(const mjModel* m, int fontscale) { if (m != last_model_ || fontscale != last_fontscale_) { - // con_.depthMapping = mjDB_ONETOZERO; mjr_makeContext(m, &con_, fontscale); last_model_ = m; last_fontscale_ = fontscale; diff --git a/src/render/render_context.c b/src/render/render_context.c index 6ba5da826c..0d673de2db 100644 --- a/src/render/render_context.c +++ b/src/render/render_context.c @@ -1062,11 +1062,7 @@ static void makeShadow(const mjModel* m, mjrContext* con) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); - if (con->depthMapping == mjDB_ONETOZERO) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_GEQUAL); - } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_GEQUAL); glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); @@ -1125,17 +1121,16 @@ static void makeOff(mjrContext* con) { glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, con->offColor); // create depth and stencil buffer - GLenum depth_buffer_format = con->depthPrecision == mjDB_FLOAT32 ? GL_DEPTH32F_STENCIL8 : GL_DEPTH24_STENCIL8; glGenRenderbuffers(1, &con->offDepthStencil); if (!con->offDepthStencil) { mju_error("Could not allocate offscreen depth and stencil buffer"); } glBindRenderbuffer(GL_RENDERBUFFER, con->offDepthStencil); if (con->offSamples) { - glRenderbufferStorageMultisample(GL_RENDERBUFFER, con->offSamples, depth_buffer_format, + glRenderbufferStorageMultisample(GL_RENDERBUFFER, con->offSamples, GL_DEPTH32F_STENCIL8, con->offWidth, con->offHeight); } else { - glRenderbufferStorage(GL_RENDERBUFFER, depth_buffer_format, con->offWidth, con->offHeight); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH32F_STENCIL8, con->offWidth, con->offHeight); } glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, con->offDepthStencil); @@ -1174,7 +1169,7 @@ static void makeOff(mjrContext* con) { mju_error("Could not allocate offscreen depth and stencil buffer_r"); } glBindRenderbuffer(GL_RENDERBUFFER, con->offDepthStencil_r); - glRenderbufferStorage(GL_RENDERBUFFER, depth_buffer_format, con->offWidth, con->offHeight); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH32F_STENCIL8, con->offWidth, con->offHeight); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, con->offDepthStencil_r); @@ -1546,15 +1541,7 @@ void mjr_makeContext_offSize(const mjModel* m, mjrContext* con, int fontscale, glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - // free previous context but keep depth settings - // Doing this is the only way users can control these - // settings without breaking changes to either the - // behaviour of mjr_makeContext or the signature - int oldDepthMapping = con->depthMapping; - int oldDepthPrecision = con->depthPrecision; mjr_freeContext(con); - con->depthMapping = oldDepthMapping; - con->depthPrecision = oldDepthPrecision; // no model: offscreen and font only if (!m) { @@ -1847,13 +1834,12 @@ MJAPI void mjr_resizeOffscreen(int width, int height, mjrContext* con) { } - GLenum depth_buffer_format = con->depthPrecision == mjDB_FLOAT32 ? GL_DEPTH32F_STENCIL8 : GL_DEPTH24_STENCIL8; glBindRenderbuffer(GL_RENDERBUFFER, con->offDepthStencil); if (con->offSamples) { - glRenderbufferStorageMultisample(GL_RENDERBUFFER, con->offSamples, depth_buffer_format, + glRenderbufferStorageMultisample(GL_RENDERBUFFER, con->offSamples, GL_DEPTH32F_STENCIL8, con->offWidth, con->offHeight); } else { - glRenderbufferStorage(GL_RENDERBUFFER, depth_buffer_format, con->offWidth, con->offHeight); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH32F_STENCIL8, con->offWidth, con->offHeight); } if (con->offSamples) { @@ -1861,6 +1847,6 @@ MJAPI void mjr_resizeOffscreen(int width, int height, mjrContext* con) { glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, con->offWidth, con->offHeight); glBindRenderbuffer(GL_RENDERBUFFER, con->offDepthStencil_r); - glRenderbufferStorage(GL_RENDERBUFFER, depth_buffer_format, con->offWidth, con->offHeight); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH32F_STENCIL8, con->offWidth, con->offHeight); } } diff --git a/src/render/render_gl3.c b/src/render/render_gl3.c index 97e18715a1..2833665bd0 100644 --- a/src/render/render_gl3.c +++ b/src/render/render_gl3.c @@ -498,11 +498,7 @@ static void initGL3(const mjvScene* scn, const mjrContext* con) { // common options glDisable(GL_BLEND); glEnable(GL_NORMALIZE); - if (con->depthMapping == mjDB_ONETOZERO) { - glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); - } else { - glClipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); - } + glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); if (scn->flags[mjRND_CULL_FACE]) { @@ -511,19 +507,11 @@ static void initGL3(const mjvScene* scn, const mjrContext* con) { glDisable(GL_CULL_FACE); } glShadeModel(GL_SMOOTH); - if (con->depthMapping == mjDB_ONETOZERO) { - glDepthFunc(GL_GEQUAL); - } else { - glDepthFunc(GL_LEQUAL); - } + glDepthFunc(GL_GEQUAL); glDepthRange(0, 1); glAlphaFunc(GL_GEQUAL, 0.99f); glClearColor(0, 0, 0, 0); - if (con->depthMapping == mjDB_ONETOZERO) { - glClearDepth(0); - } else { - glClearDepth(1); - } + glClearDepth(0); glClearStencil(0); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); @@ -613,11 +601,9 @@ static void setView(int view, mjrRect viewport, const mjvScene* scn, const mjrCo // set projection glMatrixMode(GL_PROJECTION); glLoadIdentity(); - if (con->depthMapping == mjDB_ONETOZERO) { - // account for GL_ZERO_TO_ONE and reverse Z - glTranslatef(0,0,0.5); - glScalef(1,1,-0.5); - } + // reverse Z rendering mapping (znear, zfar) -> (1, 0) + glTranslatef(0,0,0.5); + glScalef(1,1,-0.5); glFrustum(cam.frustum_center - halfwidth, cam.frustum_center + halfwidth, cam.frustum_bottom, @@ -690,24 +676,12 @@ void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con) { float temp[4], headpos[3], forward[3], skyboxdst; float camProject[16], camView[16], lightProject[16], lightView[16]; double clipplane[4]; - float biasMatrixOneToZero[16] = { + float biasMatrix[16] = { 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.0f, 1.0f }; - float biasMatrixNegOneToOne[16] = { - 0.5f, 0.0f, 0.0f, 0.0f, - 0.0f, 0.5f, 0.0f, 0.0f, - 0.0f, 0.0f, 0.5f, 0.0f, - 0.5f, 0.5f, 0.5f, 1.0f - }; - float* biasMatrix; - if (con->depthMapping == mjDB_ONETOZERO) { - biasMatrix = biasMatrixOneToZero; - } else { - biasMatrix = biasMatrixNegOneToOne; - } float tempMatrix[16], textureMatrix[16]; mjvGeom *thisgeom, tempgeom; @@ -1056,11 +1030,9 @@ void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con) { // set projection: from light viewpoint glMatrixMode(GL_PROJECTION); glLoadIdentity(); - if (con->depthMapping == mjDB_ONETOZERO) { - // account for GL_ZERO_TO_ONE and reverse Z - glTranslatef(0,0,0.5); - glScalef(1,1,-0.5); - } + // reverse Z rendering mapping (znear, zfar) -> (1, 0) + glTranslatef(0,0,0.5); + glScalef(1,1,-0.5); if (thislight->directional) { glOrtho(-con->shadowClip, con->shadowClip, -con->shadowClip, con->shadowClip, diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 40e938b3e6..ac5c8161cd 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -389,14 +389,6 @@ public enum mjtFramebuffer : int{ mjFB_WINDOW = 0, mjFB_OFFSCREEN = 1, } -public enum mjtDepthMapping : int{ - mjDB_NEGONETOONE = 0, - mjDB_ONETOZERO = 1, -} -public enum mjtDepthPrecision : int{ - mjDB_INT24 = 0, - mjDB_FLOAT32 = 1, -} public enum mjtFontScale : int{ mjFONTSCALE_50 = 50, mjFONTSCALE_100 = 100, From 2e52bf807b9a227905bd25bb012ed0229ed0d658 Mon Sep 17 00:00:00 2001 From: Levi Burner Date: Tue, 26 Sep 2023 13:34:00 -0400 Subject: [PATCH 4/9] Add flag to mjrContext to support legacy depth mapping by default --- include/mujoco/mjrender.h | 7 +++++++ introspect/enums.py | 9 +++++++++ introspect/structs.py | 5 +++++ python/mujoco/render.cc | 1 + python/mujoco/renderer.py | 2 ++ src/render/render_context.c | 3 +++ src/render/render_gl2.c | 9 +++++++++ unity/Runtime/Bindings/MjBindings.cs | 4 ++++ 8 files changed, 40 insertions(+) diff --git a/include/mujoco/mjrender.h b/include/mujoco/mjrender.h index fb37d92663..6d304f8924 100644 --- a/include/mujoco/mjrender.h +++ b/include/mujoco/mjrender.h @@ -39,6 +39,10 @@ typedef enum mjtFramebuffer_ { // OpenGL framebuffer option mjFB_OFFSCREEN // offscreen buffer } mjtFramebuffer; +typedef enum mjtDepthMapping_ { // OpenGL depth buffer readout mapping (from znear to zfar) + mjDM_ZEROTOONE = 0, // Legacy default, reverses reversed Z rendering, performance penalty + mjDM_ONETOZERO // Native output of reversed Z rendering, decreases numerical error +} mjtDepthMapping; typedef enum mjtFontScale_ { // font scale, used at context creation mjFONTSCALE_50 = 50, // 50% scale, suitable for low-res rendering @@ -151,6 +155,9 @@ struct mjrContext_ { // custom OpenGL context // pixel output format int readPixelFormat; // default color pixel format for mjr_readPixels + + // depth output format + int readDepthMapping; // depth mapping, 0 to 1 (default, legacy), or 1 to 0 (reversed, native) }; typedef struct mjrContext_ mjrContext; diff --git a/introspect/enums.py b/introspect/enums.py index fbee5402e2..4c9efece44 100644 --- a/introspect/enums.py +++ b/introspect/enums.py @@ -621,6 +621,15 @@ ('mjFB_OFFSCREEN', 1), ]), )), + ('mjtDepthMapping', + EnumDecl( + name='mjtDepthMapping', + declname='enum mjtDepthMapping_', + values=dict([ + ('mjDM_ZEROTOONE', 0), + ('mjDM_ONETOZERO', 1), + ]), + )), ('mjtFontScale', EnumDecl( name='mjtFontScale', diff --git a/introspect/structs.py b/introspect/structs.py index a06e9a67f2..81e90786e9 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -7126,6 +7126,11 @@ type=ValueType(name='int'), doc='default color pixel format for mjr_readPixels', ), + StructFieldDecl( + name='readDepthMapping', + type=ValueType(name='int'), + doc='depth mapping, 0 to 1 (default, legacy), or 1 to 0 (reversed, native)', # pylint: disable=line-too-long + ), ), )), ('mjuiState', diff --git a/python/mujoco/render.cc b/python/mujoco/render.cc index 622bbb7adf..bae7a29984 100644 --- a/python/mujoco/render.cc +++ b/python/mujoco/render.cc @@ -215,6 +215,7 @@ PYBIND11_MODULE(_render, pymodule) { X(windowDoublebuffer); X(currentBuffer); X(readPixelFormat); + X(readDepthMapping); #undef X #define X(var) \ diff --git a/python/mujoco/renderer.py b/python/mujoco/renderer.py index 3360523cbd..c147148509 100644 --- a/python/mujoco/renderer.py +++ b/python/mujoco/renderer.py @@ -86,6 +86,7 @@ def __init__( _render.mjr_setBuffer( _enums.mjtFramebuffer.mjFB_OFFSCREEN.value, self._mjr_context ) + self._mjr_context.readDepthMapping = _enums.mjtDepthMapping.mjDM_ONETOZERO # Default render flags. self._depth_rendering = False @@ -196,6 +197,7 @@ def render(self, *, out: Optional[np.ndarray] = None) -> np.ndarray: out_64 = D / (out_64 + C) # Cast result back to float32 for backwards compatibility + # This has a small accuracy cost out[:] = out_64.astype(np.float32) # Reset scene flags. diff --git a/src/render/render_context.c b/src/render/render_context.c index 0d673de2db..86c0415157 100644 --- a/src/render/render_context.c +++ b/src/render/render_context.c @@ -1612,6 +1612,9 @@ void mjr_makeContext_offSize(const mjModel* m, mjrContext* con, int fontscale, // set default color pixel format for mjr_readPixels con->readPixelFormat = GL_RGB; + + // set default depth mapping for mjr_readPixels + con->readDepthMapping = mjDM_ZEROTOONE; } diff --git a/src/render/render_gl2.c b/src/render/render_gl2.c index 095d5b41f6..91084eb49f 100644 --- a/src/render/render_gl2.c +++ b/src/render/render_gl2.c @@ -115,6 +115,10 @@ void mjr_readPixels(unsigned char* rgb, float* depth, if (depth) { glReadPixels(viewport.left, viewport.bottom, viewport.width, viewport.height, GL_DEPTH_COMPONENT, GL_FLOAT, depth); + if (con->readDepthMapping == mjDM_ZEROTOONE) { + int N_pixels = viewport.width * viewport.height; + for (int i = 0; i < N_pixels; i++) depth[i] = 1.0 - depth[i]; // Reverse the reversed Z buffer + } } } @@ -159,6 +163,11 @@ void mjr_readPixels(unsigned char* rgb, float* depth, if (depth) { glReadPixels(viewport.left, viewport.bottom, viewport.width, viewport.height, GL_DEPTH_COMPONENT, GL_FLOAT, depth); + + if (con->readDepthMapping == mjDM_ZEROTOONE) { + int N_pixels = viewport.width * viewport.height; + for (int i = 0; i < N_pixels; i++) depth[i] = 1.0 - depth[i]; // Reverse the reversed Z buffer + } } // restore currentBuffer diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index ac5c8161cd..4afa85979e 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -389,6 +389,10 @@ public enum mjtFramebuffer : int{ mjFB_WINDOW = 0, mjFB_OFFSCREEN = 1, } +public enum mjtDepthMapping : int{ + mjDM_ZEROTOONE = 0, + mjDM_ONETOZERO = 1, +} public enum mjtFontScale : int{ mjFONTSCALE_50 = 50, mjFONTSCALE_100 = 100, From 56403b4dcec0915429f21657017a8df87c74c6ae Mon Sep 17 00:00:00 2001 From: Levi Burner Date: Fri, 29 Sep 2023 11:34:14 -0400 Subject: [PATCH 5/9] Fixups to comments and flags for reversed Z rendering --- include/mujoco/mjrender.h | 10 +++++----- introspect/enums.py | 10 +++++----- introspect/structs.py | 4 ++-- python/mujoco/render.cc | 2 +- python/mujoco/renderer.py | 2 +- src/render/render_context.c | 3 +-- src/render/render_gl2.c | 4 ++-- src/render/render_gl3.c | 13 ++++++------- unity/Runtime/Bindings/MjBindings.cs | 6 +++--- 9 files changed, 26 insertions(+), 28 deletions(-) diff --git a/include/mujoco/mjrender.h b/include/mujoco/mjrender.h index 6d304f8924..f298069057 100644 --- a/include/mujoco/mjrender.h +++ b/include/mujoco/mjrender.h @@ -39,10 +39,10 @@ typedef enum mjtFramebuffer_ { // OpenGL framebuffer option mjFB_OFFSCREEN // offscreen buffer } mjtFramebuffer; -typedef enum mjtDepthMapping_ { // OpenGL depth buffer readout mapping (from znear to zfar) - mjDM_ZEROTOONE = 0, // Legacy default, reverses reversed Z rendering, performance penalty - mjDM_ONETOZERO // Native output of reversed Z rendering, decreases numerical error -} mjtDepthMapping; +typedef enum mjtDepthMap_ { // depth mapping for `mjr_readPixels` + mjDEPTHMAP_01 = 0, // standard depth map; 0: znear, 1: zfar + mjDEPTHMAP_10 = 1 // reversed depth map; 1: znear, 0: zfar +} mjtDepthMap; typedef enum mjtFontScale_ { // font scale, used at context creation mjFONTSCALE_50 = 50, // 50% scale, suitable for low-res rendering @@ -157,7 +157,7 @@ struct mjrContext_ { // custom OpenGL context int readPixelFormat; // default color pixel format for mjr_readPixels // depth output format - int readDepthMapping; // depth mapping, 0 to 1 (default, legacy), or 1 to 0 (reversed, native) + int readDepthMap; // depth mapping: mjDEPTHMAP_01 or mjDEPTHMAP_10 }; typedef struct mjrContext_ mjrContext; diff --git a/introspect/enums.py b/introspect/enums.py index 4c9efece44..90671ea183 100644 --- a/introspect/enums.py +++ b/introspect/enums.py @@ -621,13 +621,13 @@ ('mjFB_OFFSCREEN', 1), ]), )), - ('mjtDepthMapping', + ('mjtDepthMap', EnumDecl( - name='mjtDepthMapping', - declname='enum mjtDepthMapping_', + name='mjtDepthMap', + declname='enum mjtDepthMap_', values=dict([ - ('mjDM_ZEROTOONE', 0), - ('mjDM_ONETOZERO', 1), + ('mjDEPTHMAP_01', 0), + ('mjDEPTHMAP_10', 1), ]), )), ('mjtFontScale', diff --git a/introspect/structs.py b/introspect/structs.py index 81e90786e9..c318bead0c 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -7127,9 +7127,9 @@ doc='default color pixel format for mjr_readPixels', ), StructFieldDecl( - name='readDepthMapping', + name='readDepthMap', type=ValueType(name='int'), - doc='depth mapping, 0 to 1 (default, legacy), or 1 to 0 (reversed, native)', # pylint: disable=line-too-long + doc='depth mapping: mjDEPTHMAP_01 or mjDEPTHMAP_10', ), ), )), diff --git a/python/mujoco/render.cc b/python/mujoco/render.cc index bae7a29984..b2922efca6 100644 --- a/python/mujoco/render.cc +++ b/python/mujoco/render.cc @@ -215,7 +215,7 @@ PYBIND11_MODULE(_render, pymodule) { X(windowDoublebuffer); X(currentBuffer); X(readPixelFormat); - X(readDepthMapping); + X(readDepthMap); #undef X #define X(var) \ diff --git a/python/mujoco/renderer.py b/python/mujoco/renderer.py index c147148509..1b1d2cd0a7 100644 --- a/python/mujoco/renderer.py +++ b/python/mujoco/renderer.py @@ -86,7 +86,7 @@ def __init__( _render.mjr_setBuffer( _enums.mjtFramebuffer.mjFB_OFFSCREEN.value, self._mjr_context ) - self._mjr_context.readDepthMapping = _enums.mjtDepthMapping.mjDM_ONETOZERO + self._mjr_context.readDepthMap = _enums.mjtDepthMap.mjDEPTHMAP_10 # Default render flags. self._depth_rendering = False diff --git a/src/render/render_context.c b/src/render/render_context.c index 86c0415157..ab7aac9da5 100644 --- a/src/render/render_context.c +++ b/src/render/render_context.c @@ -1614,7 +1614,7 @@ void mjr_makeContext_offSize(const mjModel* m, mjrContext* con, int fontscale, con->readPixelFormat = GL_RGB; // set default depth mapping for mjr_readPixels - con->readDepthMapping = mjDM_ZEROTOONE; + con->readDepthMap = mjDEPTHMAP_01; } @@ -1836,7 +1836,6 @@ MJAPI void mjr_resizeOffscreen(int width, int height, mjrContext* con) { glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, con->offWidth, con->offHeight); } - glBindRenderbuffer(GL_RENDERBUFFER, con->offDepthStencil); if (con->offSamples) { glRenderbufferStorageMultisample(GL_RENDERBUFFER, con->offSamples, GL_DEPTH32F_STENCIL8, diff --git a/src/render/render_gl2.c b/src/render/render_gl2.c index 91084eb49f..2d54c99e37 100644 --- a/src/render/render_gl2.c +++ b/src/render/render_gl2.c @@ -115,7 +115,7 @@ void mjr_readPixels(unsigned char* rgb, float* depth, if (depth) { glReadPixels(viewport.left, viewport.bottom, viewport.width, viewport.height, GL_DEPTH_COMPONENT, GL_FLOAT, depth); - if (con->readDepthMapping == mjDM_ZEROTOONE) { + if (con->readDepthMap == mjDEPTHMAP_01) { int N_pixels = viewport.width * viewport.height; for (int i = 0; i < N_pixels; i++) depth[i] = 1.0 - depth[i]; // Reverse the reversed Z buffer } @@ -164,7 +164,7 @@ void mjr_readPixels(unsigned char* rgb, float* depth, glReadPixels(viewport.left, viewport.bottom, viewport.width, viewport.height, GL_DEPTH_COMPONENT, GL_FLOAT, depth); - if (con->readDepthMapping == mjDM_ZEROTOONE) { + if (con->readDepthMap == mjDEPTHMAP_01) { int N_pixels = viewport.width * viewport.height; for (int i = 0; i < N_pixels; i++) depth[i] = 1.0 - depth[i]; // Reverse the reversed Z buffer } diff --git a/src/render/render_gl3.c b/src/render/render_gl3.c index 2833665bd0..833ea69d2e 100644 --- a/src/render/render_gl3.c +++ b/src/render/render_gl3.c @@ -601,9 +601,9 @@ static void setView(int view, mjrRect viewport, const mjvScene* scn, const mjrCo // set projection glMatrixMode(GL_PROJECTION); glLoadIdentity(); - // reverse Z rendering mapping (znear, zfar) -> (1, 0) - glTranslatef(0,0,0.5); - glScalef(1,1,-0.5); + // reverse Z rendering mapping [znear, zfar] -> [1, 0] + glTranslatef(0.0f, 0.0f, 0.5f); + glScalef(1.0f, 1.0f, -0.5f); glFrustum(cam.frustum_center - halfwidth, cam.frustum_center + halfwidth, cam.frustum_bottom, @@ -682,7 +682,6 @@ void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con) { 0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.0f, 1.0f }; - float tempMatrix[16], textureMatrix[16]; mjvGeom *thisgeom, tempgeom; mjvLight *thislight; @@ -1030,9 +1029,9 @@ void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con) { // set projection: from light viewpoint glMatrixMode(GL_PROJECTION); glLoadIdentity(); - // reverse Z rendering mapping (znear, zfar) -> (1, 0) - glTranslatef(0,0,0.5); - glScalef(1,1,-0.5); + // reverse Z rendering mapping [znear, zfar] -> [1, 0] + glTranslatef(0.0f, 0.0f, 0.5f); + glScalef(1.0f , 1.0f, -0.5f); if (thislight->directional) { glOrtho(-con->shadowClip, con->shadowClip, -con->shadowClip, con->shadowClip, diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 4afa85979e..7e3268e78d 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -389,9 +389,9 @@ public enum mjtFramebuffer : int{ mjFB_WINDOW = 0, mjFB_OFFSCREEN = 1, } -public enum mjtDepthMapping : int{ - mjDM_ZEROTOONE = 0, - mjDM_ONETOZERO = 1, +public enum mjtDepthMap : int{ + mjDEPTHMAP_01 = 0, + mjDEPTHMAP_10 = 1, } public enum mjtFontScale : int{ mjFONTSCALE_50 = 50, From 9e51f20ce273087efcbec5ad476c21c06fd09fcb Mon Sep 17 00:00:00 2001 From: Levi Burner Date: Fri, 29 Sep 2023 18:24:54 -0400 Subject: [PATCH 6/9] More fixups to mjtDepthMap names --- include/mujoco/mjrender.h | 6 +++--- introspect/enums.py | 4 ++-- introspect/structs.py | 2 +- python/mujoco/renderer.py | 2 +- src/render/render_context.c | 2 +- src/render/render_gl2.c | 4 ++-- unity/Runtime/Bindings/MjBindings.cs | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/mujoco/mjrender.h b/include/mujoco/mjrender.h index f298069057..efd82bcde8 100644 --- a/include/mujoco/mjrender.h +++ b/include/mujoco/mjrender.h @@ -40,8 +40,8 @@ typedef enum mjtFramebuffer_ { // OpenGL framebuffer option } mjtFramebuffer; typedef enum mjtDepthMap_ { // depth mapping for `mjr_readPixels` - mjDEPTHMAP_01 = 0, // standard depth map; 0: znear, 1: zfar - mjDEPTHMAP_10 = 1 // reversed depth map; 1: znear, 0: zfar + mjDEPTH_ZERONEAR = 0, // standard depth map; 0: znear, 1: zfar + mjDEPTH_ZEROFAR = 1 // reversed depth map; 1: znear, 0: zfar } mjtDepthMap; typedef enum mjtFontScale_ { // font scale, used at context creation @@ -157,7 +157,7 @@ struct mjrContext_ { // custom OpenGL context int readPixelFormat; // default color pixel format for mjr_readPixels // depth output format - int readDepthMap; // depth mapping: mjDEPTHMAP_01 or mjDEPTHMAP_10 + int readDepthMap; // depth mapping: mjDEPTH_ZERONEAR or mjDEPTH_ZEROFAR }; typedef struct mjrContext_ mjrContext; diff --git a/introspect/enums.py b/introspect/enums.py index 90671ea183..04b772d1c8 100644 --- a/introspect/enums.py +++ b/introspect/enums.py @@ -626,8 +626,8 @@ name='mjtDepthMap', declname='enum mjtDepthMap_', values=dict([ - ('mjDEPTHMAP_01', 0), - ('mjDEPTHMAP_10', 1), + ('mjDEPTH_ZERONEAR', 0), + ('mjDEPTH_ZEROFAR', 1), ]), )), ('mjtFontScale', diff --git a/introspect/structs.py b/introspect/structs.py index c318bead0c..aed98c9e9a 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -7129,7 +7129,7 @@ StructFieldDecl( name='readDepthMap', type=ValueType(name='int'), - doc='depth mapping: mjDEPTHMAP_01 or mjDEPTHMAP_10', + doc='depth mapping: mjDEPTH_ZERONEAR or mjDEPTH_ZEROFAR', ), ), )), diff --git a/python/mujoco/renderer.py b/python/mujoco/renderer.py index 1b1d2cd0a7..b07940d39b 100644 --- a/python/mujoco/renderer.py +++ b/python/mujoco/renderer.py @@ -86,7 +86,7 @@ def __init__( _render.mjr_setBuffer( _enums.mjtFramebuffer.mjFB_OFFSCREEN.value, self._mjr_context ) - self._mjr_context.readDepthMap = _enums.mjtDepthMap.mjDEPTHMAP_10 + self._mjr_context.readDepthMap = _enums.mjtDepthMap.mjDEPTH_ZEROFAR # Default render flags. self._depth_rendering = False diff --git a/src/render/render_context.c b/src/render/render_context.c index ab7aac9da5..99daf29921 100644 --- a/src/render/render_context.c +++ b/src/render/render_context.c @@ -1614,7 +1614,7 @@ void mjr_makeContext_offSize(const mjModel* m, mjrContext* con, int fontscale, con->readPixelFormat = GL_RGB; // set default depth mapping for mjr_readPixels - con->readDepthMap = mjDEPTHMAP_01; + con->readDepthMap = mjDEPTH_ZERONEAR; } diff --git a/src/render/render_gl2.c b/src/render/render_gl2.c index 2d54c99e37..5ba11d344a 100644 --- a/src/render/render_gl2.c +++ b/src/render/render_gl2.c @@ -115,7 +115,7 @@ void mjr_readPixels(unsigned char* rgb, float* depth, if (depth) { glReadPixels(viewport.left, viewport.bottom, viewport.width, viewport.height, GL_DEPTH_COMPONENT, GL_FLOAT, depth); - if (con->readDepthMap == mjDEPTHMAP_01) { + if (con->readDepthMap == mjDEPTH_ZERONEAR) { int N_pixels = viewport.width * viewport.height; for (int i = 0; i < N_pixels; i++) depth[i] = 1.0 - depth[i]; // Reverse the reversed Z buffer } @@ -164,7 +164,7 @@ void mjr_readPixels(unsigned char* rgb, float* depth, glReadPixels(viewport.left, viewport.bottom, viewport.width, viewport.height, GL_DEPTH_COMPONENT, GL_FLOAT, depth); - if (con->readDepthMap == mjDEPTHMAP_01) { + if (con->readDepthMap == mjDEPTH_ZERONEAR) { int N_pixels = viewport.width * viewport.height; for (int i = 0; i < N_pixels; i++) depth[i] = 1.0 - depth[i]; // Reverse the reversed Z buffer } diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 7e3268e78d..f4dc268038 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -390,8 +390,8 @@ public enum mjtFramebuffer : int{ mjFB_OFFSCREEN = 1, } public enum mjtDepthMap : int{ - mjDEPTHMAP_01 = 0, - mjDEPTHMAP_10 = 1, + mjDEPTH_ZERONEAR = 0, + mjDEPTH_ZEROFAR = 1, } public enum mjtFontScale : int{ mjFONTSCALE_50 = 50, From 9868486af0de75863126e0f47b6d1e9df8253675 Mon Sep 17 00:00:00 2001 From: Levi Burner Date: Fri, 29 Sep 2023 20:52:31 -0400 Subject: [PATCH 7/9] Add changelog.rst entry for reversed Z rendering --- doc/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/changelog.rst b/doc/changelog.rst index 02aecec64f..3ff666e019 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -115,6 +115,9 @@ General attributes are specified. See the following `example model `__. - Note that these attributes only take effect for offline rendering and do not affect interactive visualisation. +21. Implemented reversed Z rendering and added enum ``mjtDepthMap`` with elements ``mjDEPTH_ZERONEAR`` and + ``mjDEPTH_ZEROFAR`` which can be used to set ``readDepthMap`` in ``mjrContext`` to control how the depth returned by + ``mjr_readPixels`` is mapped from ``znear`` to ``zfar``. Python bindings ^^^^^^^^^^^^^^^ From c6961b77d73861337d125e6e624b091e28bdadad Mon Sep 17 00:00:00 2001 From: Levi Burner Date: Mon, 2 Oct 2023 18:20:58 -0400 Subject: [PATCH 8/9] Fallback for if GL_ARB_clip_control not available If GL_ARB_clip_control not available then [znear, zfar] -> [1, -1] instead of [1, 0] in normalized device coordinates Thus, Z is still reversed, but not shifted, clipping should still work properly, and conversion to window coordinates does not cause accuracy regression compared to non-reversed Z rendering. --- src/render/render_context.c | 3 --- src/render/render_gl2.c | 7 ++++++- src/render/render_gl3.c | 32 +++++++++++++++++++++++++------- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/render/render_context.c b/src/render/render_context.c index 99daf29921..cafe9519f8 100644 --- a/src/render/render_context.c +++ b/src/render/render_context.c @@ -1489,9 +1489,6 @@ void mjr_makeContext_offSize(const mjModel* m, mjrContext* con, int fontscale, if (!mjGLAD_GL_ARB_vertex_buffer_object) { mju_error("OpenGL ARB_vertex_buffer_object required"); } - if (!mjGLAD_GL_ARB_clip_control) { - mju_error("OpenGL ARB_clip_control required"); - } if (!mjGLAD_GL_ARB_depth_buffer_float) { mju_error("OpenGL ARB_depth_buffer_float required"); } diff --git a/src/render/render_gl2.c b/src/render/render_gl2.c index 5ba11d344a..88bb6213f2 100644 --- a/src/render/render_gl2.c +++ b/src/render/render_gl2.c @@ -119,6 +119,9 @@ void mjr_readPixels(unsigned char* rgb, float* depth, int N_pixels = viewport.width * viewport.height; for (int i = 0; i < N_pixels; i++) depth[i] = 1.0 - depth[i]; // Reverse the reversed Z buffer } + else if (!mjGLAD_GL_ARB_clip_control) { + mju_warning("ARB_clip_control unavailable while mjDEPTH_ZEROFAR requested, depth accuracy will be limited"); + } } } @@ -163,11 +166,13 @@ void mjr_readPixels(unsigned char* rgb, float* depth, if (depth) { glReadPixels(viewport.left, viewport.bottom, viewport.width, viewport.height, GL_DEPTH_COMPONENT, GL_FLOAT, depth); - if (con->readDepthMap == mjDEPTH_ZERONEAR) { int N_pixels = viewport.width * viewport.height; for (int i = 0; i < N_pixels; i++) depth[i] = 1.0 - depth[i]; // Reverse the reversed Z buffer } + else if (!mjGLAD_GL_ARB_clip_control) { + mju_warning("ARB_clip_control unavailable while mjDEPTH_ZEROFAR requested, depth accuracy will be limited"); + } } // restore currentBuffer diff --git a/src/render/render_gl3.c b/src/render/render_gl3.c index 833ea69d2e..fc89aa06af 100644 --- a/src/render/render_gl3.c +++ b/src/render/render_gl3.c @@ -498,7 +498,7 @@ static void initGL3(const mjvScene* scn, const mjrContext* con) { // common options glDisable(GL_BLEND); glEnable(GL_NORMALIZE); - glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); + if (mjGLAD_GL_ARB_clip_control) glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); if (scn->flags[mjRND_CULL_FACE]) { @@ -601,9 +601,15 @@ static void setView(int view, mjrRect viewport, const mjvScene* scn, const mjrCo // set projection glMatrixMode(GL_PROJECTION); glLoadIdentity(); - // reverse Z rendering mapping [znear, zfar] -> [1, 0] - glTranslatef(0.0f, 0.0f, 0.5f); - glScalef(1.0f, 1.0f, -0.5f); + if (mjGLAD_GL_ARB_clip_control) { + // reverse Z rendering mapping [znear, zfar] -> [1, 0] (ndc) + glTranslatef(0.0f, 0.0f, 0.5f); + glScalef(1.0f, 1.0f, -0.5f); + } + else { + // reverse Z rendering mapping without shift [znear, zfar] -> [1, -1] (ndc) + glScalef(1.0f, 1.0f, -1.0f); + } glFrustum(cam.frustum_center - halfwidth, cam.frustum_center + halfwidth, cam.frustum_bottom, @@ -682,6 +688,12 @@ void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con) { 0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.0f, 1.0f }; + if (!mjGLAD_GL_ARB_clip_control) { + // account for conversion from ndc to window coordinates + biasMatrix[2*4+2] = 0.5; + biasMatrix[3*4+2] = 0.5; + } + float tempMatrix[16], textureMatrix[16]; mjvGeom *thisgeom, tempgeom; mjvLight *thislight; @@ -1029,9 +1041,15 @@ void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con) { // set projection: from light viewpoint glMatrixMode(GL_PROJECTION); glLoadIdentity(); - // reverse Z rendering mapping [znear, zfar] -> [1, 0] - glTranslatef(0.0f, 0.0f, 0.5f); - glScalef(1.0f , 1.0f, -0.5f); + if (mjGLAD_GL_ARB_clip_control) { + // reverse Z rendering mapping [znear, zfar] -> [1, 0] (ndc) + glTranslatef(0.0f, 0.0f, 0.5f); + glScalef(1.0f, 1.0f, -0.5f); + } + else { + // reverse Z rendering mapping without shift [znear, zfar] -> [1, -1] (ndc) + glScalef(1.0f, 1.0f, -1.0f); + } if (thislight->directional) { glOrtho(-con->shadowClip, con->shadowClip, -con->shadowClip, con->shadowClip, From b1e67281e1ebdbb07361db49aa8e1df4286ae0c6 Mon Sep 17 00:00:00 2001 From: Levi Burner Date: Tue, 3 Oct 2023 11:27:00 -0400 Subject: [PATCH 9/9] Fallback for if ARB_depth_buffer_float is unavailable --- src/render/render_context.c | 11 +++++------ src/render/render_gl2.c | 14 ++++++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/render/render_context.c b/src/render/render_context.c index cafe9519f8..68b336368f 100644 --- a/src/render/render_context.c +++ b/src/render/render_context.c @@ -1126,11 +1126,13 @@ static void makeOff(mjrContext* con) { mju_error("Could not allocate offscreen depth and stencil buffer"); } glBindRenderbuffer(GL_RENDERBUFFER, con->offDepthStencil); + + GLenum depth_buffer_format = mjGLAD_GL_ARB_depth_buffer_float ? GL_DEPTH32F_STENCIL8 : GL_DEPTH24_STENCIL8; if (con->offSamples) { - glRenderbufferStorageMultisample(GL_RENDERBUFFER, con->offSamples, GL_DEPTH32F_STENCIL8, + glRenderbufferStorageMultisample(GL_RENDERBUFFER, con->offSamples, depth_buffer_format, con->offWidth, con->offHeight); } else { - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH32F_STENCIL8, con->offWidth, con->offHeight); + glRenderbufferStorage(GL_RENDERBUFFER, depth_buffer_format, con->offWidth, con->offHeight); } glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, con->offDepthStencil); @@ -1169,7 +1171,7 @@ static void makeOff(mjrContext* con) { mju_error("Could not allocate offscreen depth and stencil buffer_r"); } glBindRenderbuffer(GL_RENDERBUFFER, con->offDepthStencil_r); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH32F_STENCIL8, con->offWidth, con->offHeight); + glRenderbufferStorage(GL_RENDERBUFFER, depth_buffer_format, con->offWidth, con->offHeight); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, con->offDepthStencil_r); @@ -1489,9 +1491,6 @@ void mjr_makeContext_offSize(const mjModel* m, mjrContext* con, int fontscale, if (!mjGLAD_GL_ARB_vertex_buffer_object) { mju_error("OpenGL ARB_vertex_buffer_object required"); } - if (!mjGLAD_GL_ARB_depth_buffer_float) { - mju_error("OpenGL ARB_depth_buffer_float required"); - } con->glInitialized = 1; // determine window availability (could be EGL-headless) diff --git a/src/render/render_gl2.c b/src/render/render_gl2.c index 88bb6213f2..9cdb781c02 100644 --- a/src/render/render_gl2.c +++ b/src/render/render_gl2.c @@ -119,8 +119,11 @@ void mjr_readPixels(unsigned char* rgb, float* depth, int N_pixels = viewport.width * viewport.height; for (int i = 0; i < N_pixels; i++) depth[i] = 1.0 - depth[i]; // Reverse the reversed Z buffer } - else if (!mjGLAD_GL_ARB_clip_control) { - mju_warning("ARB_clip_control unavailable while mjDEPTH_ZEROFAR requested, depth accuracy will be limited"); + if (con->readDepthMap == mjDEPTH_ZEROFAR && !mjGLAD_GL_ARB_clip_control) { + mju_warning("mjDEPTH_ZEROFAR requested but ARB_clip_control unavailable, depth accuracy will be limited"); + } + if (con->readDepthMap == mjDEPTH_ZEROFAR) { + mju_warning("mjDEPTH_ZEROFAR requested but window depth buffer precision may be limited"); } } } @@ -170,8 +173,11 @@ void mjr_readPixels(unsigned char* rgb, float* depth, int N_pixels = viewport.width * viewport.height; for (int i = 0; i < N_pixels; i++) depth[i] = 1.0 - depth[i]; // Reverse the reversed Z buffer } - else if (!mjGLAD_GL_ARB_clip_control) { - mju_warning("ARB_clip_control unavailable while mjDEPTH_ZEROFAR requested, depth accuracy will be limited"); + if (con->readDepthMap == mjDEPTH_ZEROFAR && !mjGLAD_GL_ARB_clip_control) { + mju_warning("mjDEPTH_ZEROFAR requested but ARB_clip_control unavailable, depth accuracy will be limited"); + } + if (con->readDepthMap == mjDEPTH_ZEROFAR && !mjGLAD_GL_ARB_depth_buffer_float) { + mju_warning("mjDEPTH_ZEROFAR requested but ARB_depth_buffer_float unavailable, depth accuracy will be limited"); } }