mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
First pass at a winding-based fill approach
This commit is contained in:
parent
d01658bc5b
commit
4774d2bc3b
6 changed files with 128 additions and 137 deletions
|
@ -231,6 +231,7 @@ class Camera(object):
|
|||
self.init_textures()
|
||||
self.init_light_source()
|
||||
self.refresh_perspective_uniforms()
|
||||
self.init_fill_fbo() # Experimental
|
||||
# A cached map from mobjects to their associated list of render groups
|
||||
# so that these render groups are not regenerated unnecessarily for static
|
||||
# mobjects
|
||||
|
@ -254,6 +255,54 @@ class Camera(object):
|
|||
# This is the frame buffer we'll draw into when emitting frames
|
||||
self.draw_fbo = self.get_fbo(samples=0)
|
||||
|
||||
def init_fill_fbo(self):
|
||||
# Experimental
|
||||
self.fill_texture = self.ctx.texture(
|
||||
size=self.get_pixel_shape(),
|
||||
components=4,
|
||||
samples=self.samples,
|
||||
)
|
||||
fill_depth = self.ctx.depth_renderbuffer(self.get_pixel_shape(), samples=self.samples)
|
||||
self.fill_fbo = self.ctx.framebuffer(self.fill_texture, fill_depth)
|
||||
self.fill_prog = self.ctx.program(
|
||||
vertex_shader='''
|
||||
#version 330
|
||||
|
||||
in vec2 texcoord;
|
||||
out vec2 v_textcoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4((2.0 * texcoord - 1.0), 0.0, 1.0);
|
||||
v_textcoord = texcoord;
|
||||
}
|
||||
''',
|
||||
fragment_shader='''
|
||||
#version 330
|
||||
|
||||
uniform sampler2D Texture;
|
||||
|
||||
in vec2 v_textcoord;
|
||||
out vec4 frag_color;
|
||||
|
||||
void main() {
|
||||
vec4 color = texture(Texture, v_textcoord);
|
||||
if(color.a == 0) discard;
|
||||
frag_color = color;
|
||||
// frag_color = vec4(1, 0, 0, 0.2);
|
||||
}
|
||||
''',
|
||||
)
|
||||
tid = self.n_textures
|
||||
self.fill_texture.use(tid)
|
||||
self.fill_prog['Texture'].value = tid
|
||||
self.n_textures += 1
|
||||
verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
|
||||
self.fill_vao = self.ctx.simple_vertex_array(
|
||||
self.fill_prog,
|
||||
self.ctx.buffer(verts.astype('f4').tobytes()),
|
||||
'texcoord',
|
||||
)
|
||||
|
||||
def set_ctx_blending(self, enable: bool = True) -> None:
|
||||
if enable:
|
||||
self.ctx.enable(moderngl.BLEND)
|
||||
|
@ -400,7 +449,24 @@ class Camera(object):
|
|||
self.set_shader_uniforms(shader_program, shader_wrapper)
|
||||
self.set_ctx_depth_test(shader_wrapper.depth_test)
|
||||
self.set_ctx_clip_plane(shader_wrapper.use_clip_plane)
|
||||
render_group["vao"].render(int(shader_wrapper.render_primitive))
|
||||
|
||||
# TODO
|
||||
if shader_wrapper.render_to_texture:
|
||||
self.fill_fbo.clear(0.0, 0.0, 0.0, 0.0)
|
||||
self.fill_fbo.use()
|
||||
self.ctx.enable(moderngl.BLEND)
|
||||
self.ctx.blend_func = moderngl.ONE, moderngl.ONE
|
||||
self.ctx.blend_equation = moderngl.FUNC_SUBTRACT
|
||||
render_group["vao"].render(int(shader_wrapper.render_primitive))
|
||||
self.ctx.blend_func = moderngl.DEFAULT_BLENDING
|
||||
self.ctx.blend_equation = moderngl.FUNC_ADD
|
||||
self.fbo.use()
|
||||
self.fill_texture.use(0)
|
||||
self.fill_prog['Texture'].value = 0
|
||||
self.fill_vao.render(moderngl.TRIANGLE_STRIP)
|
||||
else:
|
||||
render_group["vao"].render(int(shader_wrapper.render_primitive))
|
||||
|
||||
if render_group["single_use"]:
|
||||
self.release_render_group(render_group)
|
||||
|
||||
|
|
|
@ -59,13 +59,12 @@ class VMobject(Mobject):
|
|||
('stroke_width', np.float32, (1,)),
|
||||
('joint_product', np.float32, (4,)),
|
||||
('fill_rgba', np.float32, (4,)),
|
||||
('orientation', np.float32, (1,)),
|
||||
('vert_index', np.float32, (1,)),
|
||||
('base_point', np.float32, (3,)),
|
||||
])
|
||||
fill_data_names = ['point', 'fill_rgba', 'orientation', 'vert_index']
|
||||
fill_data_names = ['point', 'fill_rgba', 'base_point']
|
||||
stroke_data_names = ['point', 'stroke_rgba', 'stroke_width', 'joint_product']
|
||||
|
||||
fill_render_primitive: int = moderngl.TRIANGLES
|
||||
fill_render_primitive: int = moderngl.TRIANGLE_STRIP
|
||||
stroke_render_primitive: int = moderngl.TRIANGLE_STRIP
|
||||
|
||||
pre_function_handle_to_anchor_scale_factor: float = 0.01
|
||||
|
@ -992,10 +991,10 @@ class VMobject(Mobject):
|
|||
v12s = points[2::2] - points[1::2]
|
||||
curve_orientations = np.sign(cross2d(v01s, v12s))
|
||||
|
||||
# Reset orientation data
|
||||
self.data["orientation"][1::2, 0] = curve_orientations
|
||||
if "orientation" in self.locked_data_keys:
|
||||
self.locked_data_keys.remove("orientation")
|
||||
# # Reset orientation data
|
||||
# self.data["orientation"][1::2, 0] = curve_orientations
|
||||
# if "orientation" in self.locked_data_keys:
|
||||
# self.locked_data_keys.remove("orientation")
|
||||
|
||||
concave_parts = curve_orientations < 0
|
||||
|
||||
|
@ -1121,14 +1120,6 @@ class VMobject(Mobject):
|
|||
super().set_data(data)
|
||||
return self
|
||||
|
||||
def resize_points(
|
||||
self,
|
||||
new_length: int,
|
||||
resize_func: Callable[[np.ndarray, int], np.ndarray] = resize_array
|
||||
):
|
||||
super().resize_points(new_length, resize_func)
|
||||
self.data["vert_index"][:, 0] = np.arange(new_length)
|
||||
|
||||
# TODO, how to be smart about tangents here?
|
||||
@triggers_refreshed_triangulation
|
||||
def apply_function(
|
||||
|
@ -1160,10 +1151,10 @@ class VMobject(Mobject):
|
|||
stroke_data = np.zeros(0, dtype=stroke_dtype)
|
||||
self.fill_shader_wrapper = ShaderWrapper(
|
||||
vert_data=fill_data,
|
||||
vert_indices=np.zeros(0, dtype='i4'),
|
||||
uniforms=self.uniforms,
|
||||
shader_folder=self.fill_shader_folder,
|
||||
render_primitive=self.fill_render_primitive,
|
||||
render_to_texture=True,
|
||||
)
|
||||
self.stroke_shader_wrapper = ShaderWrapper(
|
||||
vert_data=stroke_data,
|
||||
|
@ -1187,13 +1178,15 @@ class VMobject(Mobject):
|
|||
|
||||
# Build up data lists
|
||||
fill_datas = []
|
||||
fill_indices = []
|
||||
stroke_datas = []
|
||||
back_stroke_data = []
|
||||
for submob in family:
|
||||
if submob.has_fill():
|
||||
submob.data["base_point"][:] = submob.data["point"][0]
|
||||
# submob.data["base_color"][:] = submob.data["fill_color"][0]
|
||||
fill_datas.append(submob.data[fill_names])
|
||||
fill_indices.append(submob.get_triangulation())
|
||||
# Add dummy
|
||||
fill_datas.append(submob.data[fill_names][-1:])
|
||||
if submob.has_stroke():
|
||||
submob.get_joint_products()
|
||||
if submob.stroke_behind:
|
||||
|
@ -1208,7 +1201,7 @@ class VMobject(Mobject):
|
|||
|
||||
shader_wrappers = [
|
||||
self.back_stroke_shader_wrapper.read_in(back_stroke_data),
|
||||
self.fill_shader_wrapper.read_in(fill_datas, fill_indices),
|
||||
self.fill_shader_wrapper.read_in(fill_datas),
|
||||
self.stroke_shader_wrapper.read_in(stroke_datas),
|
||||
]
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ class ShaderWrapper(object):
|
|||
depth_test: bool = False,
|
||||
use_clip_plane: bool = False,
|
||||
render_primitive: int = moderngl.TRIANGLE_STRIP,
|
||||
render_to_texture: bool = False,
|
||||
):
|
||||
self.vert_data = vert_data
|
||||
self.vert_indices = vert_indices
|
||||
|
@ -44,6 +45,7 @@ class ShaderWrapper(object):
|
|||
self.depth_test = depth_test
|
||||
self.use_clip_plane = use_clip_plane
|
||||
self.render_primitive = str(render_primitive)
|
||||
self.render_to_texture = render_to_texture
|
||||
self.init_program_code()
|
||||
self.refresh_id()
|
||||
|
||||
|
|
|
@ -2,29 +2,20 @@
|
|||
|
||||
in vec4 color;
|
||||
in float fill_all; // Either 0 or 1
|
||||
in float uv_anti_alias_width;
|
||||
|
||||
in float orientation;
|
||||
in vec2 uv_coords;
|
||||
in float is_linear;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
float sdf(){
|
||||
float x0 = uv_coords.x;
|
||||
float y0 = uv_coords.y;
|
||||
if(bool(is_linear)) return abs(y0);
|
||||
|
||||
float Fxy = y0 - x0 * x0;
|
||||
if(orientation * Fxy >= 0) return 0.0;
|
||||
|
||||
return abs(Fxy) / sqrt(1 + 4 * x0 * x0);
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
if (color.a == 0) discard;
|
||||
frag_color = color;
|
||||
if (bool(fill_all)) return;
|
||||
frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width);
|
||||
if (orientation == 0) return;
|
||||
|
||||
float x0 = uv_coords.x;
|
||||
float y0 = uv_coords.y;
|
||||
float Fxy = y0 - x0 * x0;
|
||||
if(orientation * Fxy < 0) discard;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,129 +1,68 @@
|
|||
#version 330
|
||||
|
||||
layout (triangles) in;
|
||||
layout (triangle_strip, max_vertices = 5) out;
|
||||
layout (triangle_strip, max_vertices = 7) out;
|
||||
|
||||
uniform float anti_alias_width;
|
||||
uniform float pixel_size;
|
||||
uniform vec3 corner;
|
||||
|
||||
in vec3 verts[3];
|
||||
in float v_orientation[3];
|
||||
in vec4 v_color[3];
|
||||
in vec3 v_base_point[3];
|
||||
in float v_vert_index[3];
|
||||
|
||||
|
||||
out vec4 color;
|
||||
out float fill_all;
|
||||
out float uv_anti_alias_width;
|
||||
|
||||
out float orientation;
|
||||
// uv space is where the curve coincides with y = x^2
|
||||
out vec2 uv_coords;
|
||||
out float is_linear;
|
||||
|
||||
const float ANGLE_THRESHOLD = 1e-3;
|
||||
|
||||
|
||||
// Analog of import for manim only
|
||||
#INSERT get_gl_Position.glsl
|
||||
#INSERT get_xyz_to_uv.glsl
|
||||
#INSERT get_unit_normal.glsl
|
||||
#INSERT finalize_color.glsl
|
||||
|
||||
|
||||
void emit_vertex_wrapper(vec3 point, int index, vec3 unit_normal){
|
||||
color = finalize_color(v_color[index], point, unit_normal);
|
||||
void emit_vertex_wrapper(vec3 point, vec4 v_color, vec3 unit_normal){
|
||||
color = finalize_color(v_color, point, unit_normal);
|
||||
gl_Position = get_gl_Position(point);
|
||||
EmitVertex();
|
||||
}
|
||||
|
||||
|
||||
void emit_simple_triangle(vec3 unit_normal){
|
||||
for(int i = 0; i < 3; i++){
|
||||
emit_vertex_wrapper(verts[i], i, unit_normal);
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
|
||||
void emit_pentagon(
|
||||
// Triangle vertices
|
||||
vec3 p0,
|
||||
vec3 p1,
|
||||
vec3 p2,
|
||||
// Unit tangent vector
|
||||
vec3 t01,
|
||||
vec3 t12,
|
||||
vec3 unit_normal
|
||||
){
|
||||
// Vectors perpendicular to the curve in the plane of the curve
|
||||
// pointing outside the curve
|
||||
vec3 p0_perp = cross(t01, unit_normal);
|
||||
vec3 p2_perp = cross(t12, unit_normal);
|
||||
|
||||
float angle = acos(clamp(dot(t01, t12), -1, 1));
|
||||
is_linear = float(angle < ANGLE_THRESHOLD);
|
||||
|
||||
if(bool(is_linear)){
|
||||
// Cross with unit z vector
|
||||
p0_perp = normalize(vec3(-t01.y, t01.x, 0));
|
||||
p2_perp = p0_perp;
|
||||
}
|
||||
|
||||
bool fill_inside = orientation > 0.0;
|
||||
float aaw = anti_alias_width * pixel_size;
|
||||
vec3 corners[5] = vec3[5](p0, p0, p1, p2, p2);
|
||||
|
||||
if(fill_inside || bool(is_linear)){
|
||||
// Add buffer outside the curve
|
||||
corners[0] += aaw * p0_perp;
|
||||
corners[2] += 0.5 * aaw * (p0_perp + p2_perp);
|
||||
corners[4] += aaw * p2_perp;
|
||||
} else{
|
||||
// Add buffer inside the curve
|
||||
corners[1] -= aaw * p0_perp;
|
||||
corners[3] -= aaw * p2_perp;
|
||||
}
|
||||
|
||||
// Compute xy_to_uv matrix, and potentially re-evaluate bezier degree
|
||||
bool too_steep;
|
||||
mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, p2, 10.0, too_steep);
|
||||
if(too_steep) is_linear = 1.0;
|
||||
uv_anti_alias_width = aaw * length(xyz_to_uv[0].xyz);
|
||||
|
||||
for(int i = 0; i < 5; i++){
|
||||
int j = int[5](0, 0, 1, 2, 2)[i];
|
||||
vec3 corner = corners[i];
|
||||
uv_coords = (xyz_to_uv * vec4(corner, 1.0)).xy;
|
||||
emit_vertex_wrapper(corner, j, unit_normal);
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
|
||||
void main(){
|
||||
// If vert indices are sequential, don't fill all
|
||||
fill_all = float(
|
||||
(v_vert_index[1] - v_vert_index[0]) != 1.0 ||
|
||||
(v_vert_index[2] - v_vert_index[1]) != 1.0
|
||||
);
|
||||
// We use the triangle strip primative, but
|
||||
// actually only need every other strip element
|
||||
if (int(v_vert_index[0]) % 2 == 1) return;
|
||||
|
||||
vec3 p0 = verts[0];
|
||||
vec3 p1 = verts[1];
|
||||
vec3 p2 = verts[2];
|
||||
vec3 t01 = p1 - p0;
|
||||
vec3 t12 = p2 - p1;
|
||||
vec3 unit_normal = normalize(cross(t01, t12));
|
||||
// Curves are marked as eneded when the handle after
|
||||
// the first anchor is set equal to that anchor
|
||||
if (verts[0] == verts[1]) return;
|
||||
|
||||
if(bool(fill_all)){
|
||||
emit_simple_triangle(unit_normal);
|
||||
return;
|
||||
}
|
||||
orientation = v_orientation[1];
|
||||
vec3 unit_normal = get_unit_normal(verts[0], verts[1], verts[2]);
|
||||
|
||||
emit_pentagon(
|
||||
p0, p1, p2,
|
||||
normalize(t01),
|
||||
normalize(t12),
|
||||
unit_normal
|
||||
);
|
||||
// Emit main triangle
|
||||
orientation = 0.0;
|
||||
uv_coords = vec2(0.0);
|
||||
emit_vertex_wrapper(verts[2], v_color[2], unit_normal);
|
||||
emit_vertex_wrapper(v_base_point[0], v_color[1], unit_normal);
|
||||
emit_vertex_wrapper(verts[0], v_color[0], unit_normal);
|
||||
|
||||
// Emit edge triangle
|
||||
orientation = 1.0;
|
||||
uv_coords = vec2(0, 0);
|
||||
// Two dummies
|
||||
emit_vertex_wrapper(verts[0], v_color[0], unit_normal);
|
||||
emit_vertex_wrapper(verts[0], v_color[0], unit_normal);
|
||||
// Inner corner
|
||||
uv_coords = vec2(0.5, 0);
|
||||
emit_vertex_wrapper(verts[1], v_color[1], unit_normal);
|
||||
// Last corner
|
||||
uv_coords = vec2(1.0, 1.0);
|
||||
emit_vertex_wrapper(verts[2], v_color[2], unit_normal);
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,17 +2,17 @@
|
|||
|
||||
in vec3 point;
|
||||
in vec4 fill_rgba;
|
||||
in float orientation;
|
||||
in float vert_index;
|
||||
in vec3 base_point;
|
||||
|
||||
out vec3 verts; // Bezier control point
|
||||
out float v_orientation;
|
||||
out vec4 v_joint_product;
|
||||
out vec4 v_color;
|
||||
out vec3 v_base_point;
|
||||
out float v_vert_index;
|
||||
|
||||
void main(){
|
||||
verts = point;
|
||||
v_orientation = orientation;
|
||||
v_color = fill_rgba;
|
||||
v_vert_index = vert_index;
|
||||
v_base_point = base_point;
|
||||
v_vert_index = gl_VertexID;
|
||||
}
|
Loading…
Add table
Reference in a new issue