From fc7b2123698f0da26c818f8cbbd1aafa94ed5bfb Mon Sep 17 00:00:00 2001 From: Levi Burner Date: Wed, 14 Jun 2023 14:42:42 -0400 Subject: [PATCH 1/8] 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 fb37d926..cd11423b 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 fbee5402..d14523b3 100644 --- a/introspect/enums.py +++ b/introspect/enums.py @@ -621,6 +621,24 @@ ENUMS: Mapping[str, EnumDecl] = dict([ ('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 a06e9a67..fd718da4 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -7126,6 +7126,16 @@ STRUCTS: Mapping[str, StructDecl] = dict([ 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 622bbb7a..c1d2b9c8 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 5f502a47..f5b154ed 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 9b5e4c6d..fa1642c4 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 4fc2c6c8..1b1fd449 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 18c71805..6ba5da82 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 526e130c..97e18715 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 ac5c8161..40e938b3 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/8] 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 e96cd89e..e83f2435 100644 --- a/python/mujoco/renderer.py +++ b/python/mujoco/renderer.py @@ -32,7 +32,9 @@ class Renderer: 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 @@ class Renderer: 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 @@ the clause: 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 @@ the clause: 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 @@ the clause: """ 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 @@ the clause: 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/8] 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 cd11423b..fb37d926 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 d14523b3..fbee5402 100644 --- a/introspect/enums.py +++ b/introspect/enums.py @@ -621,24 +621,6 @@ ENUMS: Mapping[str, EnumDecl] = dict([ ('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 fd718da4..a06e9a67 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -7126,16 +7126,6 @@ STRUCTS: Mapping[str, StructDecl] = dict([ 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 c1d2b9c8..622bbb7a 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 e83f2435..3360523c 100644 --- a/python/mujoco/renderer.py +++ b/python/mujoco/renderer.py @@ -32,9 +32,7 @@ class Renderer: 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 @@ class Renderer: 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 @@ the clause: 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 @@ the clause: 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 @@ the clause: 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 f5b154ed..5f502a47 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 6ba5da82..0d673de2 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 97e18715..2833665b 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 40e938b3..ac5c8161 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/8] 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 fb37d926..6d304f89 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 fbee5402..4c9efece 100644 --- a/introspect/enums.py +++ b/introspect/enums.py @@ -621,6 +621,15 @@ ENUMS: Mapping[str, EnumDecl] = dict([ ('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 a06e9a67..81e90786 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -7126,6 +7126,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ 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 622bbb7a..bae7a299 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 3360523c..c1471485 100644 --- a/python/mujoco/renderer.py +++ b/python/mujoco/renderer.py @@ -86,6 +86,7 @@ the clause: _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 @@ the clause: 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 0d673de2..86c04151 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 095d5b41..91084eb4 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 ac5c8161..4afa8597 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/8] 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 6d304f89..f2980690 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 4c9efece..90671ea1 100644 --- a/introspect/enums.py +++ b/introspect/enums.py @@ -621,13 +621,13 @@ ENUMS: Mapping[str, EnumDecl] = dict([ ('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 81e90786..c318bead 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -7127,9 +7127,9 @@ STRUCTS: Mapping[str, StructDecl] = dict([ 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 bae7a299..b2922efc 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 c1471485..1b1d2cd0 100644 --- a/python/mujoco/renderer.py +++ b/python/mujoco/renderer.py @@ -86,7 +86,7 @@ the clause: _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 86c04151..ab7aac9d 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 91084eb4..2d54c99e 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 2833665b..833ea69d 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 4afa8597..7e3268e7 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/8] 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 f2980690..efd82bcd 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 90671ea1..04b772d1 100644 --- a/introspect/enums.py +++ b/introspect/enums.py @@ -626,8 +626,8 @@ ENUMS: Mapping[str, EnumDecl] = dict([ 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 c318bead..aed98c9e 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -7129,7 +7129,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([ 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 1b1d2cd0..b07940d3 100644 --- a/python/mujoco/renderer.py +++ b/python/mujoco/renderer.py @@ -86,7 +86,7 @@ the clause: _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 ab7aac9d..99daf299 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 2d54c99e..5ba11d34 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 7e3268e7..f4dc2680 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/8] 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 02aecec6..3ff666e0 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/8] 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 99daf299..cafe9519 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 5ba11d34..88bb6213 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 833ea69d..fc89aa06 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,