diff --git a/doc/changelog.rst b/doc/changelog.rst index 4af1fae5..155dff63 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -24,6 +24,8 @@ Bug fixes which the Jacobian is computed, now fixed. - Fixed a bug that caused the parent frame of elements in the child worldbody to be incorrectly set when attaching an mjSpec to a frame or a site. +- Fixed a bug that caused shadow rendering to flicker on platforms (e.g., MacOS) that do not support ARB_clip_control. + Fixed in collaboration with :github:user:`aftersomemath`. Python bindings ^^^^^^^^^^^^^^^ diff --git a/src/render/render_context.c b/src/render/render_context.c index 6adb4f11..b305f2dd 100644 --- a/src/render/render_context.c +++ b/src/render/render_context.c @@ -1050,13 +1050,17 @@ static void makeShadow(const mjModel* m, mjrContext* con) { } glBindFramebuffer(GL_FRAMEBUFFER, con->shadowFBO); - // create shadow depth texture: in TEXTURE1 + // Create a shadow depth texture in TEXTURE1 and explicitly select an int24 + // depth buffer. A depth stencil format is used because that appears to be + // more widely supported (MacOS does not support GL_DEPTH_COMPONENT24). Using + // a fixed format makes it easier to choose glPolygonOffset parameters that + // result in reasonably consistent and artifact free shadows across platforms. glGenTextures(1, &con->shadowTex); glActiveTexture(GL_TEXTURE1); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, con->shadowTex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, - con->shadowSize, con->shadowSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, + con->shadowSize, con->shadowSize, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); diff --git a/src/render/render_gl3.c b/src/render/render_gl3.c index dffa48d7..9d168585 100644 --- a/src/render/render_gl3.c +++ b/src/render/render_gl3.c @@ -1211,9 +1211,24 @@ void mjr_render(mjrRect viewport, mjvScene* scn, const mjrContext* con) { int cull_face = glIsEnabled(GL_CULL_FACE); glDisable(GL_CULL_FACE); // all faces cast shadows glEnable(GL_POLYGON_OFFSET_FILL); - float kOffsetFactor = -1.5f; - float kOffsetUnits = -4.0f; - glPolygonOffset(kOffsetFactor, kOffsetUnits); // prevents "shadow acne" + + // The limited resolution of the shadow maps means multiple fragments + // sample the same texel. When light and camera directions differ on + // surfaces that should be lit this causes "shadow acne" because some + // fragments will be lit while adjacent fragments are not. To mitigate + // this artifact, an offset is applied to the depth values in the + // shadow map. The offset must be large enough to ensure consistent + // depth comparison occurs within the limited precision of the depth + // buffer. The offset is computed by glPolygonOffset using parameters + // that are chosen empirically. We need different values when clip + // control is on/off because this setting changes the depth precision. + float kOffsetFactor = -16.0f; + float kOffsetUnits = -512.0f; + if (mjGLAD_GL_ARB_clip_control) { + kOffsetFactor = -1.5f; + kOffsetUnits = -4.0f; + } + glPolygonOffset(kOffsetFactor, kOffsetUnits); // render all geoms to depth texture for (int j=0; j < ngeom; j++) {