-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Developer documentation (WIP)
The goal of this page is to give developers interested in contributing to manim a deeper look into its inner workings and hopefully make it easier to understand the codebase. The target audience is therefore those interested in developing manim and may not be as useful to users. If you fall into the latter category and want to learn how to create animations with manim we have documentation for that here
The Opengl rendering pipeline in manim involves logic in both python code and programs called shaders. Shaders are programs designed to run on graphics cards. They are written in a language called glsl that is similar in syntax to c++. Shaders can be broken into three categories Vertex Shaders, Geometry Shaders and Fragment Shaders. Generally speaking each OpenGLMobject
will be assigned to a vertex shader, a fragment shader and optionally a geometry shader. In some cases multiple of a given type of shader can also be used.
The order of processing is vertex shader -> geometry shader -> fragment shader.
In this section we will discuss how manim's existing shaders are assigned. There are also options to use custom shaders.
Manim's shaders are stored in manim/renderer/shaders. You will notice that shaders are stored in groups containing at least a vertex shader, a fragment shader and optionally a geometry shader. This makes up the opengl pipeline. In manim we only need to point it to the directory and it will detect the shaders based on the following naming convention:
- Vertex shader -> vert.glsl
- Geometry shader -> geom.glsl
- Fragment shader -> frag.glsl
Manim knows what shader folder to look by class attributes. There are two base classes that are relevant here:
-
OpenGLMobject
uses a single class attributeshader_folder
to define the shader that should be used. By default no shader is defined, however subclasses that require a shader should set this. An example of a class that defines this attribute isOpenGLPMobject
as shown below
class OpenGLPMobject(OpenGLMobject):
shader_folder = "true_dot"
-
OpenGLVMobject
uses two groups of shaders, one for stroke and one for fills and so two class attributes can be set to define the shaders that are to be used. These attributes arestroke_shader_folder
that defaults toquadratic_bezier_stroke
andfill_shader_folder
that defaults toquadratic_bezier_fill
. When extending this class these attributes can be set for subclasses to use different shaders for example the below class will extendOpenGLVMobject
and set its own shaders
class MyCustomClass(OpenGLVMobject):
stroke_shader_folder = "vectorized_mobject_stroke"
fill_shader_folder = "vectorized_mobject_fill"
We need to pass data to the shaders to be processed and rendered on the graphics card. There are different types of data that can be used by shaders, the first type we will discuss is data that can vary for each vertex, in opengl these are known as attributes. If we take a simple triangle as an example, it can be defined by 3 vertices. We need a way to pass data such as color and position of each vertex. We do this using a flexible descriptor _Data
that can be found here. This allows us to use keys such as 'points' to hold our position data and map them to the shader input 'point' attribute for a each vertex. Let's look at an example of how this is set up.
Taking OpenGLMobject
as an example we initialise points
as at the class level as below:
class OpenGLMobject:
...
points = _Data()
This will create the key in our descriptor and we can now treat it like an instance attribute, and in this case this assign positions to each vertex as below.
self.points = points # numpy array containing xyz points with shape (n, 3)
Now that we have our attribute created and all our points are ready, however this array isn't passed directly to the vertex shader. The vertex shader may use different keys, for example it may have an input such as in vec3 point;
. The reason for this is that the vertex shader will only take in a single vertex (point) at a time, so if we have three vertices for our triangle the vertex shader will only have access to one at a time. OpenGLMObject
contains a method read_data_to_shader
that will map from manim's data keys to the shaders keys, in other words map the 'points' key to the vertex shader's 'point' key.