Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix antialiasing and add MSAA for OpenGL fill #3321

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions manim/mobject/text/text_mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,11 @@ def add_line_to(end):
# anti-aliasing
if height is None and width is None:
self.scale(TEXT_MOB_SCALE_FACTOR)

# Just a temporary hack to get better triangulation
# See pr #1552 for details
for i in self.submobjects:
i.insert_n_curves(len(i.get_all_points()))
self.initial_height = self.height

def __repr__(self):
Expand Down
34 changes: 28 additions & 6 deletions manim/renderer/opengl_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,13 @@ def init_scene(self, scene):
self.window = Window(self)
self.context = self.window.ctx
self.frame_buffer_object = self.context.detect_framebuffer()
self.frame_buffer_object_msaa = self.get_frame_buffer_object(
self.context,
4,
self.frame_buffer_object.width,
self.frame_buffer_object.height,
)
self.frame_buffer_object_msaa.use()
else:
self.window = None
try:
Expand All @@ -267,7 +274,10 @@ def init_scene(self, scene):
backend="egl",
)
self.frame_buffer_object = self.get_frame_buffer_object(self.context, 0)
self.frame_buffer_object.use()
self.frame_buffer_object_msaa = self.get_frame_buffer_object(
self.context, 4
)
self.frame_buffer_object_msaa.use()
self.context.enable(moderngl.BLEND)
self.context.wireframe = config["enable_wireframe"]
self.context.blend_func = (
Expand Down Expand Up @@ -443,7 +453,10 @@ def play(self, scene, *args, **kwargs):
self.num_plays += 1

def clear_screen(self):
self.frame_buffer_object.clear(*self.background_color)
self.frame_buffer_object_msaa.clear(*self.background_color)
self.context.copy_framebuffer(
self.frame_buffer_object, self.frame_buffer_object_msaa
)
self.window.swap_buffers()

def render(self, scene, frame_offset, moving_mobjects):
Expand All @@ -461,7 +474,7 @@ def render(self, scene, frame_offset, moving_mobjects):
self.window.swap_buffers()

def update_frame(self, scene):
self.frame_buffer_object.clear(*self.background_color)
self.frame_buffer_object_msaa.clear(*self.background_color)
self.refresh_perspective_uniforms(scene.camera)

for mobject in scene.mobjects:
Expand All @@ -474,6 +487,9 @@ def update_frame(self, scene):
mesh.set_uniforms(self)
mesh.render()

self.context.copy_framebuffer(
self.frame_buffer_object, self.frame_buffer_object_msaa
)
self.animation_elapsed_time = time.time() - self.animation_start_time

def scene_finished(self, scene):
Expand Down Expand Up @@ -524,9 +540,9 @@ def get_image(self) -> Image.Image:
def save_static_frame_data(self, scene, static_mobjects):
pass

def get_frame_buffer_object(self, context, samples=0):
pixel_width = config["pixel_width"]
pixel_height = config["pixel_height"]
def get_frame_buffer_object(self, context, samples=0, width=None, height=None):
pixel_width = width or config["pixel_width"]
pixel_height = height or config["pixel_height"]
num_channels = 4
return context.framebuffer(
color_attachments=context.texture(
Expand All @@ -548,6 +564,9 @@ def get_raw_frame_buffer_object_data(self, dtype="f1"):
# gl.glBlitFramebuffer(
# 0, 0, pw, ph, 0, 0, pw, ph, gl.GL_COLOR_BUFFER_BIT, gl.GL_LINEAR
# )
self.context.copy_framebuffer(
self.frame_buffer_object, self.frame_buffer_object_msaa
)
num_channels = 4
ret = self.frame_buffer_object.read(
viewport=self.frame_buffer_object.viewport,
Expand All @@ -558,6 +577,9 @@ def get_raw_frame_buffer_object_data(self, dtype="f1"):

def get_frame(self):
# get current pixel values as numpy data in order to test output
self.context.copy_framebuffer(
self.frame_buffer_object, self.frame_buffer_object_msaa
)
raw = self.get_raw_frame_buffer_object_data(dtype="f1")
pixel_shape = self.get_pixel_shape()
result_dimensions = (pixel_shape[1], pixel_shape[0], 4)
Expand Down
55 changes: 16 additions & 39 deletions manim/renderer/shaders/quadratic_bezier_fill/frag.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -6,61 +6,38 @@ in vec4 color;
in float fill_all; // Either 0 or 1e
in float uv_anti_alias_width;

in vec3 xyz_coords;
in float orientation;
in vec2 uv_coords;
in vec2 uv_b2;
in float bezier_degree;

out vec4 frag_color;

// Needed for quadratic_bezier_distance insertion below
float modify_distance_for_endpoints(vec2 p, float dist, float t){
return dist;
}

#include ../include/quadratic_bezier_distance.glsl

#define ANTI_ALIASING

float sdf(){
if(bezier_degree < 2){
return abs(uv_coords[1]);
}
float u2 = uv_b2.x;
float v2 = uv_b2.y;
// For really flat curves, just take the distance to x-axis
if(abs(v2 / u2) < 0.1 * uv_anti_alias_width){
return abs(uv_coords[1]);
}
// For flat-ish curves, take the curve
else if(abs(v2 / u2) < 0.5 * uv_anti_alias_width){
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
}
// I know, I don't love this amount of arbitrary-seeming branching either,
// but a number of strange dimples and bugs pop up otherwise.

// This converts uv_coords to yet another space where the bezier points sit on
// (0, 0), (1/2, 0) and (1, 1), so that the curve can be expressed implicityly
// as y = x^2.
mat2 to_simple_space = mat2(
v2, 0,
2 - u2, 4 * v2
);
vec2 p = to_simple_space * uv_coords;
// Sign takes care of whether we should be filling the inside or outside of curve.
float sgn = orientation * sign(v2);
float Fp = (p.x * p.x - p.y);
if(sgn * Fp < 0){
return 0.0;
}else{
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
}
vec2 p = uv_coords;
float sgn = orientation;
float q = (p.x * p.x - p.y);
#ifdef ANTI_ALIASING
return sgn * q / sqrt(dFdx(q) * dFdx(q) + dFdy(q) * dFdy(q));
#endif
#ifndef ANTI_ALIASING
return -sgn * q;
#endif
}


void main() {
if (color.a == 0) discard;
frag_color = color;
if (fill_all == 1.0) return;
frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width);
#ifdef ANTI_ALIASING
frag_color.a *= 0.5 - sdf(); // Anti-aliasing
#endif
#ifndef ANTI_ALIASING
frag_color.a *= float(sdf() > 0); // No anti-aliasing
#endif
}
70 changes: 5 additions & 65 deletions manim/renderer/shaders/quadratic_bezier_fill/geom.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,23 @@ in float v_vert_index[3];

out vec4 color;
out float fill_all;
out float uv_anti_alias_width;

out vec3 xyz_coords;
out float orientation;
// uv space is where b0 = (0, 0), b1 = (1, 0), and transform is orthogonal
out vec2 uv_coords;
out vec2 uv_b2;
out float bezier_degree;


// Analog of import for manim only
#include ../include/quadratic_bezier_geometry_functions.glsl
#include ../include/get_gl_Position.glsl
#include ../include/get_unit_normal.glsl
#include ../include/finalize_color.glsl

const vec2 uv_coords_arr[3] = vec2[3](vec2(0, 0), vec2(0.5, 0), vec2(1, 1));

void emit_vertex_wrapper(vec3 point, int index){
color = finalize_color(
v_color[index],
point,
v_global_unit_normal[index],
light_source_position,
gloss,
shadow
);
xyz_coords = point;
gl_Position = get_gl_Position(xyz_coords);
color = finalize_color(v_color[index], point, v_global_unit_normal[index], light_source_position, gloss, shadow);
gl_Position = get_gl_Position(point);
uv_coords = uv_coords_arr[index];
EmitVertex();
}

Expand All @@ -62,55 +51,6 @@ void emit_simple_triangle(){
EndPrimitive();
}


void emit_pentagon(vec3[3] points, vec3 normal){
vec3 p0 = points[0];
vec3 p1 = points[1];
vec3 p2 = points[2];
// Tangent vectors
vec3 t01 = normalize(p1 - p0);
vec3 t12 = normalize(p2 - p1);
// Vectors perpendicular to the curve in the plane of the curve pointing outside the curve
vec3 p0_perp = cross(t01, normal);
vec3 p2_perp = cross(t12, normal);

bool fill_inside = orientation > 0;
float aaw = anti_alias_width;
vec3 corners[5];
if(fill_inside){
// Note, straight lines will also fall into this case, and since p0_perp and p2_perp
// will point to the right of the curve, it's just what we want
corners = vec3[5](
p0 + aaw * p0_perp,
p0,
p1 + 0.5 * aaw * (p0_perp + p2_perp),
p2,
p2 + aaw * p2_perp
);
}else{
corners = vec3[5](
p0,
p0 - aaw * p0_perp,
p1,
p2 - aaw * p2_perp,
p2
);
}

mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, normal);
uv_b2 = (xyz_to_uv * vec4(p2, 1)).xy;
uv_anti_alias_width = anti_alias_width / length(p1 - p0);

for(int i = 0; i < 5; i++){
vec3 corner = corners[i];
uv_coords = (xyz_to_uv * vec4(corner, 1)).xy;
int j = int(sign(i - 1) + 1); // Maps i = [0, 1, 2, 3, 4] onto j = [0, 0, 1, 2, 2]
emit_vertex_wrapper(corner, j);
}
EndPrimitive();
}


void main(){
// If vert indices are sequential, don't fill all
fill_all = float(
Expand All @@ -129,7 +69,7 @@ void main(){
orientation = sign(dot(v_global_unit_normal[0], local_unit_normal));

if(bezier_degree >= 1){
emit_pentagon(new_bp, local_unit_normal);
emit_simple_triangle();
}
// Don't emit any vertices for bezier_degree 0
}
Loading
Loading