2025, Nov 11 21:02
Почему 3D-линии в Matplotlib перекрываются неправильно и как исправить
Разбираем, почему 3D‑линии в Matplotlib рендерятся по порядку отрисовки, а не по глубине. Минимальный пример и решение с MayaVi2 для корректной окклюзии.
Когда в Python с помощью Matplotlib вы строите несколько трёхмерных траекторий, логично ожидать, что перекрытие будет подчиняться глубине в пространстве. Но на деле линии могут «перескакивать» друг перед другом или уходить назад лишь из‑за порядка отрисовки. При вращении фигуры эффект усиливается: картинка выглядит противоречиво и её сложнее понять.
Минимальный пример, демонстрирующий проблему
Ниже приведён набросок, который создаёт трёхмерную систему координат и рисует семейство кривых. Формально всё отображается без ошибок, но визуальное перекрытие не учитывает реальное положение сегментов в пространстве.
import matplotlib.pyplot as plt
import numpy as np
canvas = plt.figure()
panel3d = canvas.add_subplots(projection='3d')
u1, v1, w1 = np.array([...] ), np.array([...] ), np.array([...] )
u2, v2, w2 = np.array([...] ), np.array([...] ), np.array([...] )
# ...
uq, vq, wq = np.array([...] ), np.array([...] ), np.array([...] )
pack_x, pack_y, pack_z = [u1, u2, ..., uq], [v1, v2, ..., vq], [w1, w2, ..., wq]
for idx in range(q):
panel3d.plot(pack_x[idx], pack_y[idx], pack_z[idx])
plt.show()
Что на самом деле происходит и почему это выглядит неправильно
Matplotlib выводит объекты в том порядке, в каком они нарисованы, а не по их фактическому положению в 3D. Это поведение описано в их FAQ. Когда на одних осях много линий, более поздние «артисты» могут визуально закрывать более ранние, независимо от того, какие сегменты на самом деле ближе к наблюдателю. Поэтому вращение фигуры артефакт не устраняет: последовательность «рисовальщика» неизменна, и перекрытия продолжают подчиняться порядку отрисовки, а не глубине.
Практичное решение: перейти на MayaVi2
Рекомендованный обходной путь — использовать MayaVi2. Его API для базовой 3D‑визуализации достаточно похож, если вы уже привыкли к Matplotlib. Переписать цикл несложно, и в результате вы получите графики, где перекрытия соответствуют геометрии в пространстве, а не порядку вызовов отрисовки.
from mayavi import mlab
import numpy as np
p1x, p1y, p1z = np.array([...] ), np.array([...] ), np.array([...] )
p2x, p2y, p2z = np.array([...] ), np.array([...] ), np.array([...] )
# ...
pkx, pky, pkz = np.array([...] ), np.array([...] ), np.array([...] )
sx, sy, sz = [p1x, p2x, ..., pkx], [p1y, p2y, ..., pky], [p1z, p2z, ..., pkz]
for j in range(k):
mlab.plot3d(sx[j], sy[j], sz[j])
mlab.show()
Почему это важно понимать
3D‑визуализации часто применяют для рассуждений о топологии, столкновениях, орбитах и пространственных связях. Если движок рендера ставит порядок «артистов» выше глубины, легко неверно понять пересечения и взаимное расположение. Это особенно рискованно, когда вы исследуете сложные семейства кривых и полагаетесь на вращение, чтобы сформировать интуицию.
Выводы
Если вам важно, чтобы 3D‑линии учитывали глубину при рендеринге, не рассчитывайте, что стандартный 3D‑модуль Matplotlib разрешит перекрытия по пространственному признаку. Для корректной окклюзии используйте MayaVi2 — там перекрытия отражают геометрию, а не последовательность вызовов. И обращаясь за помощью или открывая задачу, прикладывайте минимальный воспроизводимый пример и небольшую иллюстрацию артефакта — так поведение становится однозначным, а разбор проблемы идёт быстрее.
Материал основан на вопросе на StackOverflow от Albannach5446 и ответе Ennpeh.