I want to have a surface plot in vispy, but also display a 2D image in place of the white background.
I used vispy's Draw a SurfacePlot example as a base. The first thing I tried is just adding a scene.visuals.Image
object to the view, but that ended up rendering the image as one of the objects in the 3D scene:
canvas = scene.SceneCanvas(keys='interactive', bgcolor='w')
background_texture = scene.visuals.Image(np.random.rand(512, 512, 3).astype(np.float32))
view = canvas.central_widget.add_view()
view.camera = scene.TurntableCamera(up='z', fov=60)
view.add(background_texture)
...
I don't have reputation to display images: failed image background surfaceplot attempt #1
Since the image needs to be rendered in the background, I figured it needs its own 2D view on top of which the 3D scene of the SurfacePlot
will be rendered, so I created a new view for the image:
canvas = scene.SceneCanvas(keys='interactive', bgcolor='w')
background_texture = scene.visuals.Image(np.random.rand(512, 512, 3).astype(np.float32))
viewbg = canvas.central_widget.add_view()
viewbg.add(background_texture)
view = canvas.central_widget.add_view()
view.camera = scene.TurntableCamera(up='z', fov=60)
...
However, the image is rendered on top of the surface plot, with the exception of axis labels which are rendered on top of the image. Reordering view
and viewbg
code so view
is created first doesn't make a difference; it only makes things worse because then I also lose the ability to interact with the surface plot.
I don't have reputation to display images: failed image background surfaceplot attempt #2
Is there a way to render the entire 3D scene on top of the image?
Thanks to guidance from @djhoese, I was able to come up with some solutions. First and foremost, the background image must be outside of
view
in the scene graph, lest it becomes a part of the 3D space displayed by the view'sTurntableCamera
. After that comes the problem of actually displaying the image behind the 3D scene.Solution 1: Clear the OpenGL depth buffer after drawing the image and before drawing the 3D scene
The cleanest approach would be to first draw the image, then clear the OpenGL depth buffer before drawing the 3D scene. This will ensure that the scene is drawn on top of the image. The easiest way to do this would be to subclass
scene.Image
and add a call togloo.clear(color=False, depth=True)
after drawing:Ensure that
BackgroundImage
is rendered before anything else. Full code below:A
PanZoomCamera
is used withbackground_image
to ensure that the image is scaled to fit the entire canvas. You won't be able to interact with that camera since the view with theTurntableCamera
is drawn on top so it takes focus.I don't have reputation to embed images in my answer: surface plot with a background image
Generalizing the solution to draw any scene in the background
If you want to draw any scene, not just an
Image
in the background of another scene, you can implement aNode
subclass that clears the depth buffer after its children are drawn. It's probably best to inherit fromWidget
since it provides the most features:Make sure that
Background
is actually drawn before anything else. Full surface plot code with this approach below:Make sure to use the
add_widget
andadd_view
methods when usingWidget
or a subclass ofWidget
so canvas events are handled for you properly. The neat thing about this approach is that you can have multipleBackground
instances rendered in a row, with each one being rendered on top of the previous, providing you with a multi-layered background where each background can be an arbitrary 2D or 3D scene.Solution 2: Use translate and scale transforms on the image outside of a view
An alternative to clearing the OpenGL depth buffer would be to translate the
Image
so itsz
coordinate is as close to1.0
as possible, but not actually equal to1.0
. This approach won't work withPanZoomCamera
, though, so you will also have to handle the scaling of the image so its fits the entire canvas once the canvas is resized. Theresize
event is also called during initial canvas setup. Full code below:With this approach you don't have to worry about drawing the
Image
before the 3D scene. Any drawing order works. If the image doesn't show, itsz
coordinate is probably being rounded up to1.0
, so try to remove a few decimals in thetranslate
parameter ofSTTransform
. Values of0.9999
and upwards should work well enough. I just went and found the maximum possible value I could put in thez
coordinate that would display the image for me.