How to handle the lights in PyVista when using a transform matrix for the camera?

Viewed 144

I'm using camera transform matrices in PyVista to do an animation. But my object is shadowed:

enter image description here

I apply the same transformation matrices to the lights, as you can see below.

circles = fractal3(4)
for i, matrix in enumerate(matrices):
    pltr = pv.Plotter(window_size=[512, 512], off_screen=True)
    pltr.set_background("#363940")
    pltr.set_position(satellite0)
    pltr.camera.zoom(0.9)
    pltr.camera.model_transform_matrix = matrix
    for circle in circles:
        center, radius = circle
        sphere = pv.Sphere(radius, center = center+(0,))
        pltr.add_mesh(sphere, smooth_shading=True, color="red", specular=10)
    pltr.set_focus((0,0,0))
    for light in pltr.renderer.lights:
        light.transform_matrix = matrix
    pngname = "zzpic%03d.png" % i
    pltr.show(screenshot=pngname)

EDIT

Here is the animation I get when I don't do anything to the lights:

enter image description here

1 Answers

As I noted in comments, you are probably better off using something like orbit_on_path(). You noted that the gif ends up being low-quality which is why you were looking for other options.

You could still either save to an MP4 file and convert that to a gif with whatever quality requirements you have, or you can replicate what orbit_on_path() does under the hood. Moving the camera (rather than manipulating its transform matrix) should be a lot more robust.

Anyway, to answer your exact question here, you would have to transform the lights in a non-trivial manner. Here's a minimal example to replicate your situation, based on your incomplete code:

mport numpy as np
import pyvista as pv
from pyvista.examples import download_bunny

# example data
mesh = download_bunny()
mesh.rotate_x(90)
matrices = [
    np.array([
        [np.cos(phi), -np.sin(phi), 0, 0],
        [np.sin(phi), np.cos(phi), 0, 0],
        [0, 0, 1, 0],
        [0, 0, 0, 1]
    ])
    for phi in np.linspace(0, 2*np.pi, 22, endpoint=False)
]

for i, matrix in enumerate(matrices):
    plotter = pv.Plotter(window_size=[512, 512], off_screen=True)
    plotter.set_background("#363940")
    plotter.camera.zoom(3)
    plotter.camera.model_transform_matrix = matrix
    plotter.add_mesh(mesh, smooth_shading=True, color="red", specular=10)
    plotter.set_focus(mesh.center)
    #for light in plotter.renderer.lights:
    #    pass  # option 1
    #    light.transform_matrix = matrix  # option 2
    #    light.transform_matrix = np.linalg.inv(matrix)  # option 3
    pngname = f"zzpic{i:03d}.png"
    plotter.show(screenshot=pngname)

In there I left 3 options for manipulating the renderer's default lights.

The first option is to leave the lights' transform matrix alone. The resulting animation looks like this: animation with rotating bunny, lights rotate with it

As you can see, the lights are static with respect to the scene (the bunny). The bunny has a dark side and a light side. This is somewhat surprising, because the default lights are camera lights which move together with the camera. But setting the transform matrix seems to bypass this linkage, probably moving the camera's effective position away from its physical position.

The second option is what you have in your question: applying the same transform matrix to the lights: rotating bunny with weirdly wobbling lights It's less obvious than in your example, but the lights move around, neither linked to the bunny nor the camera. With some squinting one might notice that there are two dark and two light instances during a complete turn of the bunny.

Which brings me to the last option: applying the inverse transform to the lights! Here's how the result looks: rotating bunny which is always well-lit

It seems that this setup keeps the bunny consistently well-lit. On your fractaly example and satellite path it will be very obvious if this indeed fixes the lighting. I would expect this to put the effective position of the lights back with the effective position of the camera.

Related