Cairo does not respect order of mobjects when rendering a 3d scene
Description of bug / unexpected behavior
I created a 3d scene that contained some Square and Line3D mobjects. I sorted the mobjects into the correct z-order, but the Cairo renderer always seemed to draw the Square on top of the Line3D mobjects. I then switched to using the OpenGL renderer it produced the correct result.
Is this a known limitation of the Cairo renderer? I will workaround this issue by using the OpenGL rendered for 3d scenes. However, I thought it was worth reporting this issue.
Expected behavior
I expected the Cairo rendered to drawn the mobjects in the order that I added them to the scene. Instead, it seems to always draw the Square on top of the Line3D mobjects. The OpenGL rendered produced the expected result.
How to reproduce the issue
Run the code twice. Once with OPENGL_CONFIG and again with CAIRO_CONFIG. Note the different results.
Code for reproducing the problem
"""
This module illustrates drawing lines over a square in a 3D scene using Manim.
"""
import numpy as np
from manim import (ThreeDScene, ThreeDAxes, Line3D, Square, config, tempconfig, RendererType,
LEFT, RIGHT, UP, OUT, WHITE, RED, BLUE, Text, UL)
class Line3DExample(ThreeDScene):
def construct(self):
renderer_str = f"renderer: {config.renderer.value}"
renderer_text = Text(renderer_str)
self.add_fixed_in_frame_mobjects(renderer_text)
renderer_text.to_corner(UL)
axes = ThreeDAxes()
x_label = axes.get_x_axis_label('x')
y_label = axes.get_y_axis_label('y')
z_label = axes.get_z_axis_label('z')
self.add(axes, x_label, y_label, z_label)
# the square is in the plane z = 0
square = Square(side_length=3.0, color=RED, fill_opacity=1.0, stroke_color=BLUE, stroke_width=10)
# the 5 lines are in the planes z = -2, -1, 0, 1, 2
lines = [Line3D(2.0 * LEFT + t * (UP + OUT), 2.0 * RIGHT + t * (UP + OUT), color=WHITE)
for t in np.linspace(-2.0, 2.0, 5)]
# square and lines are added in the correct order
self.add(lines[0], lines[1], lines[2], square, lines[3], lines[4])
# opengl renders the scene correctly
# lines[3] is drawn over the square
OPENGL_CONFIG = {
"renderer": RendererType.OPENGL
}
# cairo renders the scene incorrectly
# lines[3] is drawn under the square
# this is a bug in cairo renderer
CAIRO_CONFIG = {
"renderer": RendererType.CAIRO
}
# For running the scene directly
if __name__ == "__main__":
with tempconfig(OPENGL_CONFIG):
scene = Line3DExample()
scene.render(preview=True)
Additional media files
Images/GIFs
Logs
Terminal output
PASTE HERE OR PROVIDE LINK TO https://pastebin.com/ OR SIMILAR
System specifications
System Details
- OS: macOS 15.5 (24F74) (Sequoia)
- RAM: Mac Studio 2023 Apple M2 Max 96 GB
- Python version: Python 3.13.5
- Installed modules (provide output from
pip list):
Package Version
------------------------- --------------
anyio 4.9.0
appnope 0.1.4
argon2-cffi 25.1.0
argon2-cffi-bindings 21.2.0
arrow 1.3.0
asttokens 3.0.0
async-lru 2.0.5
attrs 25.3.0
audioop-lts 0.2.1
av 13.1.0
babel 2.17.0
beautifulsoup4 4.13.4
bleach 6.2.0
certifi 2025.7.14
cffi 1.17.1
charset-normalizer 3.4.2
click 8.2.1
cloup 3.0.7
comm 0.2.2
Cython 3.1.2
debugpy 1.8.14
decorator 5.2.1
defusedxml 0.7.1
executing 2.2.0
fastjsonschema 2.21.1
fqdn 1.5.1
glcontext 3.0.0
h11 0.16.0
httpcore 1.0.9
httpx 0.28.1
idna 3.10
ipykernel 6.29.5
ipython 9.4.0
ipython_pygments_lexers 1.1.1
isoduration 20.11.0
isosurfaces 0.1.2
jedi 0.19.2
Jinja2 3.1.6
json5 0.12.0
jsonpointer 3.0.0
jsonschema 4.24.0
jsonschema-specifications 2025.4.1
jupyter_client 8.6.3
jupyter_core 5.8.1
jupyter-events 0.12.0
jupyter-lsp 2.2.5
jupyter_server 2.16.0
jupyter_server_terminals 0.5.3
jupyterlab 4.4.4
jupyterlab_pygments 0.3.0
jupyterlab_server 2.27.3
manim 0.19.0
ManimPango 0.6.0
mapbox_earcut 1.0.3
markdown-it-py 3.0.0
MarkupSafe 3.0.2
matplotlib-inline 0.1.7
mdurl 0.1.2
mistune 3.1.3
moderngl 5.12.0
moderngl-window 3.1.1
nbclient 0.10.2
nbconvert 7.16.6
nbformat 5.10.4
nest-asyncio 1.6.0
networkx 3.5
notebook_shim 0.2.4
numpy 2.3.1
overrides 7.7.0
packaging 25.0
pandocfilters 1.5.1
parso 0.8.4
pexpect 4.9.0
pillow 11.3.0
pip 25.1.1
platformdirs 4.3.8
prometheus_client 0.22.1
prompt_toolkit 3.0.51
psutil 7.0.0
ptyprocess 0.7.0
pure_eval 0.2.3
pycairo 1.28.0
pycparser 2.22
pydub 0.25.1
pyglet 2.1.6
pyglm 2.8.2
Pygments 2.19.2
pyobjc-core 11.1
pyobjc-framework-Cocoa 11.1
python-dateutil 2.9.0.post0
python-json-logger 3.3.0
PyYAML 6.0.2
pyzmq 27.0.0
referencing 0.36.2
requests 2.32.4
rfc3339-validator 0.1.4
rfc3986-validator 0.1.1
rich 14.0.0
rpds-py 0.26.0
scipy 1.16.0
screeninfo 0.8.1
Send2Trash 1.8.3
setuptools 80.9.0
six 1.17.0
skia-pathops 0.8.0.post2
sniffio 1.3.1
soupsieve 2.7
srt 3.5.3
stack-data 0.6.3
svgelements 1.9.6
terminado 0.18.1
tinycss2 1.4.0
tornado 6.5.1
tqdm 4.67.1
traitlets 5.14.3
types-python-dateutil 2.9.0.20250708
typing_extensions 4.14.1
uri-template 1.3.0
urllib3 2.5.0
watchdog 6.0.0
wcwidth 0.2.13
webcolors 24.11.1
webencodings 0.5.1
websocket-client 1.8.0
LaTeX details
- LaTeX distribution: TeXShop 5.53
- Installed LaTeX packages:
too long, not relevant
Additional comments
Your example images seems to be using normal Line because lines are white and not shaded (for 2D line z-stacking is possile by using Line(a, b, z_index= -3)) .
Cairo fundamentaly is based solely on 2DMasking and coloring, so it is has major limitations. 3D functions are little hacky in Cairoand known to have issues.
Tested:
Line3D([n,m,t],[k,m,t], z_index = t) still will produce described output.
@OliverStrait Thanks for investigating. Are you saying that this is a Cairo bug or a known 3D limitation? My use of 3D is minimal so I might try to work around the issue by using a 2D scene. I assume that Scene does respect the mobjects order.
@agryman I don't know what actual technical details are why this is happening, but it is Manim code problem because you can interface with Cairo only by ordering what is painted over old bitmap. Orders need to come outside of Cairo. Cairo is designed to render Bezier based forms, so it is good at that 2D graphics (not scalable if realtime is limit). It is old C-program and it is nothing to do with modern Rendering pipelines. Not as bad most people think, but not that good in modern sense. OpenGl is future target for 3D and there may not be that much will to fix old obscure 3D-Cairo bugs.
I assume that Scene does respect the mobjects order.
I'm not sure what you ask.
@OliverStrait Let me clarify. A Scene contains a list mobjects. The docs say "Mobjects will be displayed, from background to foreground in the order with which they are added." Does the Cairo renderer process that list in the exact order in which the mobjects appear? This means that mobjects[1] gets rendered after mobjects[0] so if they are all opaque then mobjects[1] may overwrite some of the pixels in mobjects[0].
The reason I ask is that Mobject has a z_index attribute and Camera has a use_z_index attribute. Does the Cairo renderer ever process the mobjects in the order defined by sorting on z_index? Just wondering if you know how z_index is handled. I'll inspect the code next.
@OliverStrait I checked the code.
The attribute use_z_index is used in the utility function extract_mobject_family_members which does in fact sort the mobjects if use_z_index=True. However, its default value is False.
The OpenGL renderer always sets use_z_index to True, ignoring the camera setting.
The Cairo renderer does not reference use_z_index, so when it calls extract_mobject_family_members the value will be False.
My conclusion is that Cairo never sorts by z_index and OpenGL always sorts by it. Does that sound right? If so, then I will work around my 3D problem by using a Scene with Cairo and manually sorting mobjects into the correct z-order.
收到了!谢谢!
The reason I ask is that Mobject has a z_index attribute and Camera has a use_z_index attribute. Does the Cairo renderer ever process the mobjects in the order defined by sorting on z_index? Just wondering if you know how z_index is handled. I'll inspect the code next.
Yes, generally speaking the order in which Cairo paints mobjects on its canvas is first goverend by the z_index, and then by the insertion order.
In 3D scenes, the camera tries to be intelligent, and reorders the mobjects based on how close they are to the camera. You can check the details in the code in Camera.get_mobjects_to_display and ThreeDCamera.get_mobjects_to_display. To disable this, you could create your lines by additionally passing shade_in_3d=False -- that would disable the attempted intelligent behavior of the camera. (It's possible that all of your 3d scene mobjects need to be created with this additional argument to avoid any sorting whatsoever.)
3D support in the cairo renderer is, as @OliverStrait has already mentioned, a workaround at best. We are (slowly) working on improving the OpenGL renderer -- but if you urgently need a more mature solution, I can recommend checking out manimgl.
@behackl Thanks for the suggestion. My requirement for 3D is very minimal, just a small number of convex polygons. I am going to compute the correct z-order and use a 2D scene. If that approach doesn't work I'll try your suggestion.