如何对 Poly3DCollection 进行光照和着色

Bev*_*nes 1 python matplotlib

我正在尝试将 3D 等高线图进行阴影处理,或者使用阴影使其“看起来”为 3D。我正在使用 matplotlib,主要是因为图的质量很高,并且更愿意继续使用它。

最终,我想要一个单一或平坦的彩色表面,在 matplotlib 样式的图中投射阴影。

我正在使用 scipy 进行一些插值和 skimage 以及行进立方体算法来生成轮廓。然后最后使用它来创建和着色多边形集合。

import numpy as np
from skimage import measure
from scipy.interpolate import griddata
import matplotlib as mpl
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from matplotlib.colors import LightSource

# Generate an grid to inerpolate to
X, Y, Z = np.meshgrid(0.0:1.0:50j, 0.0:1.0:50j, 0.0:1.0:50j)

# Interpolate (coor and phi are the numerical grid and scalar values)
F = griddata(coor, phi, (X, Y, Z), method='nearest')

# Make the contour, marching cubes
marchCubeSpace = 1.0 / 50.0
verts, faces, normals, values = measure.marching_cubes_lewiner(F, 0.5, spacing=(marchCubeSpace, marchCubeSpace, marchCubeSpace))

# Create Ploy3D
mesh = Poly3DCollection(verts[faces], alpha=1.0)

# An attempt to get some sort of height data.
facearray = np.array([np.array((np.sum(verts[face[:], 0]/3), np.sum(verts[face[:], 1]/3), np.sum(verts[face[:], 2]/3))) for face in faces])

# light source, ultimately I want to use not `reds` but just a red for all faces.
ls = LightSource(azdeg=45.0, altdeg=90.0)
rgb = ls.blend_hsv(rgb=ls.shade(facearray, plt.cm.Reds), intensity=ls.shade_normals(normals, fraction=0.25))
mesh.set_facecolor(rgb[:, 0])

# Plot
fig = plt.figure()
ax = fig.add_subplot(0, 0, 0, projection='3d')
ax.add_collection3d(mesh)
Run Code Online (Sandbox Code Playgroud)

我正在寻找生成这样的东西: 在此处输入图片说明

Bev*_*nes 5

好的,所以我有一个可以接受的解决方案。如果您需要更多帮助,请给我留言,我很乐意引导任何人完成此过程。注意下面的代码需要coorphi您的数据集,所以没有,如果你不提供3D标量场给它这个代码将无法运行。

import numpy as np
from skimage import measure
from scipy.interpolate import griddata
import matplotlib as mpl
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from matplotlib.colors import LightSource

# Generate an grid to inerpolate to
X, Y, Z = np.meshgrid(0.0:1.0:50j, 0.0:1.0:50j, 0.0:1.0:50j)

# Interpolate (coor and phi are the numerical grid and scalar values)
F = griddata(coor, phi, (X, Y, Z), method='nearest')

# Make the contour, marching cubes
marchCubeSpace = 1.0 / 50.0
verts, faces, normals, values = measure.marching_cubes_lewiner(F, 0.5, spacing=(marchCubeSpace, marchCubeSpace, marchCubeSpace))

# Create Ploy3D and set up a light source
mesh = Poly3DCollection(verts[faces], alpha=1.0)
ls = LightSource(azdeg=225.0, altdeg=45.0)

# First change - normals are per vertex, so I made it per face.
normalsarray = np.array([np.array((np.sum(normals[face[:], 0]/3), np.sum(normals[face[:], 1]/3), np.sum(normals[face[:], 2]/3))/np.sqrt(np.sum(normals[face[:], 0]/3)**2 + np.sum(normals[face[:], 1]/3)**2 + np.sum(normals[face[:], 2]/3)**2)) for face in faces])

# Next this is more asthetic, but it prevents the shadows of the image being too dark. (linear interpolation to correct)
min = np.min(ls.shade_normals(normalsarray, fraction=1.0)) # min shade value
max = np.max(ls.shade_normals(normalsarray, fraction=1.0)) # max shade value
diff = max-min
newMin = 0.3
newMax = 0.95
newdiff = newMax-newMin

# Using a constant color, put in desired RGB values here.
colourRGB = np.array((255.0/255.0, 54.0/255.0, 57/255.0, 1.0))

# The correct shading for shadows are now applied. Use the face normals and light orientation to generate a shading value and apply to the RGB colors for each face.
rgbNew = np.array([colourRGB*(newMin + newdiff*((shade-min)/diff)) for shade in ls.shade_normals(normalsarray, fraction=1.0)])

# Apply color to face
mesh.set_facecolor(rgbNew)

# Plot
fig = plt.figure()
ax = fig.add_subplot(0, 0, 0, projection='3d')
ax.add_collection3d(mesh)
Run Code Online (Sandbox Code Playgroud)

所以这就是我一直在寻找的。(请注意,这与上图的情况不完全相同)在此处输入图片说明