Skip to content

plot_surface should shade at draw time after projection to axes space, instead of doing so in data space #18120

@anntzer

Description

@anntzer

Bug report

Bug summary

import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
x, y = np.mgrid[-10:11, -10:11]
z = -(x ** 2 + y ** 2)

ax = fig.add_subplot(121, projection="3d")
pc = ax.plot_surface(x, y, z)
ax = fig.add_subplot(122, projection="3d")
pc = ax.plot_surface(x, y, z / 10)

plt.show()

gives
test

In the right plot, the shading is basically constant throughout the image; in the left one, it changes very sharply at the tip of the paraboloid -- in fact, it nearly looks like the patches immediately at the left and the right are close to being the darkest and the brightest ones! Given that these two patches have nearly the same orientation, how is that possible?

From a quick investigation, I believe the root of the issue is the following: the shading is computed as the dot product of the patch normal(*) and the illumination direction, but this dot product is taken in data space (immediately when the Poly3DCollection is built in plot_surface) rather than after projection to "3d screen space" (at draw time). In data space, the angle between the patches at the tip of the paraboloid is indeed much larger (the z-scale is much wider than the x/y-scale), hence the large change in dot product.

(*) The patch normal is approximated using just three of the vertices of each patch, but this is not the source of the issue -- using e.g. the average of the normals computed at each vertex of the patch doesn't help.

Therefore, I believe the shading should instead be computed at draw time, after projection to "3d screen space". As another argument in favor of such behavior, consider also that if we ever manage to support log-scale in mplot3d, we'd certainly have to compute the dot products after scale application (because they are even more meaningless before scale application). As usual, this could possibly be opt-in via a flag... or perhaps not.

Matplotlib version

  • Operating system: linux
  • Matplotlib version: master
  • Matplotlib backend (print(matplotlib.get_backend())): qt5agg
  • Python version: 38
  • Jupyter version (if applicable):
  • Other libraries:

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions