Ir al contenido

Escenas, configuración, cámaras, renderizadores y 3D

Nos acercamos a la recta final. Durante todo el taller hemos estado usando la clase Scene para renderizar nuestras escenas pero no hemos acabado de entender qué es.

En las escenas dibujamos los Mobjects que queremos ver y definimos las animaciones que queremos aplicarles. Al usar funciones como add o play le estamos diciendo a la escena que tome un Mobject y se lo pase a una Camera para que esta capture al Mobject y lo renderice.

La forma en la que la escena hace esto se puede cambiar y es configurable.

Respecto a la configuración, podemos usar config, una instancia de ManimConfig que se crea cada vez que llamamos a Manim. Con config podemos cambiar propiedades de las escena como el color del fondo, el tamaño de la escena tanto en píxeles como en unidades de Manim, el framerate, el nombre del archivo que vamos a generar y mil cosas más.

Por ejemplo, podemos modificar la configuración para hacer vídeos verticales:

scene_config_editing.py
from manim import *
config.background_color = BLUE_A
config.pixel_width = 1080
config.pixel_height = 1920
config.frame_width = 6
config.frame_height = 10.6667
class SceneEditingExample(Scene):
def construct(self):
self.wait()
self.play(Write(Square(fill_opacity=1, color=RED)))
self.wait()

Aparte de la configuración, podemos modificar cómo la escena interactúa con la cámara. Una Scene tiene una cámara estática, pero otras como ZoomedScene o MovingCameraScene pueden hacer auténticas virguerías.

moved_zoomed_scene.py
from manim import *
class MovingZoomedSceneAround(ZoomedScene):
contributed by TheoremofBeethoven, www.youtube.com/c/TheoremofBeethoven
def __init__(self, **kwargs):
ZoomedScene.__init__(
self,
zoom_factor=0.3,
zoomed_display_height=1,
zoomed_display_width=6,
image_frame_stroke_width=20,
zoomed_camera_config={
"default_frame_stroke_width": 3,
},
**kwargs
)
def construct(self):
dot = Dot().shift(UL * 2)
image = ImageMobject(np.uint8([[0, 100, 30, 200],
[255, 0, 5, 33]]))
image.height = 7
frame_text = Text("Frame", color=PURPLE, font_size=67)
zoomed_camera_text = Text("Zoomed camera", color=RED, font_size=67)
self.add(image, dot)
zoomed_camera = self.zoomed_camera
zoomed_display = self.zoomed_display
frame = zoomed_camera.frame
zoomed_display_frame = zoomed_display.display_frame
frame.move_to(dot)
frame.set_color(PURPLE)
zoomed_display_frame.set_color(RED)
zoomed_display.shift(DOWN)
zd_rect = BackgroundRectangle(zoomed_display, fill_opacity=0, buff=MED_SMALL_BUFF)
self.add_foreground_mobject(zd_rect)
unfold_camera = UpdateFromFunc(zd_rect, lambda rect: rect.replace(zoomed_display))
frame_text.next_to(frame, DOWN)
self.play(Create(frame), FadeIn(frame_text, shift=UP))
self.activate_zooming()
self.play(self.get_zoomed_display_pop_out_animation(), unfold_camera)
zoomed_camera_text.next_to(zoomed_display_frame, DOWN)
self.play(FadeIn(zoomed_camera_text, shift=UP))
# Scale in x y z
scale_factor = [0.5, 1.5, 0]
self.play(
frame.animate.scale(scale_factor),
zoomed_display.animate.scale(scale_factor),
FadeOut(zoomed_camera_text),
FadeOut(frame_text)
)
self.wait()
self.play(ScaleInPlace(zoomed_display, 2))
self.wait()
self.play(frame.animate.shift(2.5 * DOWN))
self.wait()
self.play(self.get_zoomed_display_pop_out_animation(), unfold_camera, rate_func=lambda t: smooth(1 - t))
self.play(Uncreate(zoomed_display_frame), FadeOut(frame))
self.wait()
following_graph_camera.py
from manim import *
class FollowingGraphCamera(MovingCameraScene):
def construct(self):
self.camera.frame.save_state()
# create the axes and the curve
ax = Axes(x_range=[-1, 10], y_range=[-1, 10])
graph = ax.plot(lambda x: np.sin(x), color=BLUE, x_range=[0, 3 * PI])
# create dots based on the graph
moving_dot = Dot(ax.i2gp(graph.t_min, graph), color=ORANGE)
dot_1 = Dot(ax.i2gp(graph.t_min, graph))
dot_2 = Dot(ax.i2gp(graph.t_max, graph))
self.add(ax, graph, dot_1, dot_2, moving_dot)
self.play(self.camera.frame.animate.scale(0.5).move_to(moving_dot))
def update_curve(mob):
mob.move_to(moving_dot.get_center())
self.camera.frame.add_updater(update_curve)
self.play(MoveAlongPath(moving_dot, graph, rate_func=linear))
self.camera.frame.remove_updater(update_curve)
self.play(Restore(self.camera.frame))

Si hay un tipo de escena que merece su propio apartado son las escenas 3D. Hasta ahora hemos trabajado solamente en dos dimensiones, pero Manim puede renderizar también en 3D.

Muchos Mobjects 2D son aplicables a 3D pero tenemos clases como Sphere, Polyhedra, Cylinder o Surface que están específicamente pensados para trabajar en 3 dimensiones.

Para trabajar en 3 dimensiones, en lugar de Scene usamos ThreeDScene y en lugar de Camera usamos la ThreeDCamera

Aquí vienen algunos ejemplos de lo que se puede hacer en 3 dimensiones con Manim.

three_d_scene_example.py
from manim import *
class ThreeDExamples(ThreeDScene):
def surface_func(self, u, v):
return np.array([np.cos(u) * np.cos(v), np.cos(u) * np.sin(v), u])
def construct(self):
self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)
axes = ThreeDAxes()
self.add(axes, Circle())
text3d = Text("This is a fixed 3D text").to_corner(UL)
self.add_fixed_in_frame_mobjects(text3d)
self.begin_ambient_camera_rotation(rate=0.1)
self.wait()
self.play(
Create(Dodecahedron().move_to([-2, 2, -3])), # Notice how we are using the third axis now
Create(Sphere().move_to([2, 2, -3 ])),
Create(Cylinder().move_to([2, -2, 3])),
Create(Icosahedron().move_to([-2, -2, 3])),
Create(Arrow3D().move_to([-2, -2, -3])),
Create(Surface(
lambda u, v: axes.c2p(*self.surface_func(u, v)),
u_range=[-PI, PI],
v_range=[0, TAU],
resolution=8,),
),
)
self.wait(10)

Si ManimConfig se encarga de la configuración de la escena, las Cameras de capturar los Mobjects de la escena y las escenas de contener a los Mobjects y las animaciones que se les aplican, los renderers son las clases encargadas de convertir toda esa información en imágenes que se puedan ver.

Por defecto, Manim usa Cairo, el cual es muy bueno para animaciones en dos dimensiones.

Sin embargo, para tres dimensiones existe uno más potente: OpenGL. Al utilizar la GPU para renderizar es, en teoría, más rápido que Cairo, más limpio con el 3D y permite muchas más funcionalidades como usar shaders. Por desgracia la implementación de OpenGL en Manim no está completa desde hace años, incluyendo la documentación y la compatibilidad con todos los Mobjets y animaciones.

Para que no parezca que lo menciono solo de pasada, aquí tenemos el mismo ejemplo 3D de antes renderizado con OpenGL. Para usarlo podemos pasar el flag --renderer=opengl o podemos modificarlo en la configuración de la escena.

three_d_scene_example.py
from manim import *
class ThreeDExamples(ThreeDScene):
def surface_func(self, u, v):
return np.array([np.cos(u) * np.cos(v), np.cos(u) * np.sin(v), u])
def construct(self):
self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)
axes = ThreeDAxes()
self.add(axes, Circle())
text3d = Text("This is a fixed 3D text").to_corner(UL)
self.add_fixed_in_frame_mobjects(text3d)
self.begin_ambient_camera_rotation(rate=0.1)
self.wait()
self.play(
Create(Dodecahedron().move_to([-2, 2, -3])), # Notice how we are using the third axis now
Create(Sphere().move_to([2, 2, -3 ])),
Create(Cylinder().move_to([2, -2, 3])),
Create(Icosahedron().move_to([-2, -2, 3])),
Create(Arrow3D().move_to([-2, -2, -3])),
Create(Surface(
lambda u, v: axes.c2p(*self.surface_func(u, v)),
u_range=[-PI, PI],
v_range=[0, TAU],
resolution=8,),
),
)
self.wait(10)

Podemos ver que es algo diferente. A eso me refiero con la falta de compatibilidad que queda todavía.