mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
Rewrite stroke and fill shaders (#1716)
Rewrite vectorized mobject shaders to be compatible with transformation matrices.
This commit is contained in:
parent
dba9f4da1e
commit
3ee3ea1a3e
19 changed files with 587 additions and 22 deletions
|
|
@ -2,7 +2,6 @@ import os
|
|||
from pathlib import Path
|
||||
|
||||
import manim.utils.opengl as opengl
|
||||
import manim.utils.space_ops as space_ops
|
||||
from manim import *
|
||||
from manim.opengl import *
|
||||
|
||||
|
|
@ -72,6 +71,24 @@ def get_plane_mesh(context):
|
|||
return Mesh(shader, attributes)
|
||||
|
||||
|
||||
class TextTest(Scene):
|
||||
def construct(self):
|
||||
import string
|
||||
|
||||
text = OpenGLText(
|
||||
string.ascii_lowercase, stroke_width=4, stroke_color=BLUE
|
||||
).scale(2)
|
||||
text2 = (
|
||||
OpenGLText(string.ascii_uppercase, stroke_width=4, stroke_color=BLUE)
|
||||
.scale(2)
|
||||
.next_to(text, DOWN)
|
||||
)
|
||||
# self.add(text, text2)
|
||||
self.play(Write(text))
|
||||
self.play(Write(text2))
|
||||
self.interactive_embed()
|
||||
|
||||
|
||||
class GuiTest(Scene):
|
||||
def construct(self):
|
||||
mesh = get_plane_mesh(self.renderer.context)
|
||||
|
|
@ -390,7 +407,7 @@ class InteractiveDevelopment(Scene):
|
|||
# lines as if they were part of this construct method.
|
||||
# In particular, 'square', 'circle' and 'self' will all be
|
||||
# part of the local namespace in that terminal.
|
||||
self.embed()
|
||||
# self.embed()
|
||||
|
||||
# Try copying and pasting some of the lines below into
|
||||
# the interactive shell
|
||||
|
|
@ -400,10 +417,12 @@ class InteractiveDevelopment(Scene):
|
|||
self.play(Rotate(circle, 90 * DEGREES))
|
||||
self.play(circle.animate.shift(2 * RIGHT).scale(0.25))
|
||||
|
||||
# text = Text("""
|
||||
# text = Text(
|
||||
# """
|
||||
# In general, using the interactive shell
|
||||
# is very helpful when developing new scenes
|
||||
# """)
|
||||
# """
|
||||
# )
|
||||
# self.play(Write(text))
|
||||
|
||||
# # In the interactive shell, you can just type
|
||||
|
|
|
|||
|
|
@ -104,6 +104,12 @@ enable_gui = False
|
|||
# --gui_location
|
||||
gui_location = 0,0
|
||||
|
||||
# --use_projection_fill_shaders
|
||||
use_projection_fill_shaders = False
|
||||
|
||||
# --use_projection_stroke_shaders
|
||||
use_projection_stroke_shaders = False
|
||||
|
||||
# If the -t (--transparent) flag is used, these will be replaced with the
|
||||
# values specified in the [TRANSPARENT] section later in this file.
|
||||
png_mode = RGB
|
||||
|
|
|
|||
|
|
@ -285,6 +285,8 @@ class ManimConfig(MutableMapping):
|
|||
"use_webgl_renderer",
|
||||
"enable_gui",
|
||||
"gui_location",
|
||||
"use_projection_fill_shaders",
|
||||
"use_projection_stroke_shaders",
|
||||
"verbosity",
|
||||
"video_dir",
|
||||
"write_all",
|
||||
|
|
@ -519,6 +521,8 @@ class ManimConfig(MutableMapping):
|
|||
"use_opengl_renderer",
|
||||
"use_webgl_renderer",
|
||||
"enable_gui",
|
||||
"use_projection_fill_shaders",
|
||||
"use_projection_stroke_shaders",
|
||||
]:
|
||||
setattr(self, key, parser["CLI"].getboolean(key, fallback=False))
|
||||
|
||||
|
|
@ -653,6 +657,8 @@ class ManimConfig(MutableMapping):
|
|||
"use_opengl_renderer",
|
||||
"use_webgl_renderer",
|
||||
"enable_gui",
|
||||
"use_projection_fill_shaders",
|
||||
"use_projection_stroke_shaders",
|
||||
]:
|
||||
if hasattr(args, key):
|
||||
attr = getattr(args, key)
|
||||
|
|
@ -1172,6 +1178,18 @@ class ManimConfig(MutableMapping):
|
|||
doc="Enable GUI interaction.",
|
||||
)
|
||||
|
||||
use_projection_fill_shaders = property(
|
||||
lambda self: self._d["use_projection_fill_shaders"],
|
||||
lambda self, val: self._set_boolean("use_projection_fill_shaders", val),
|
||||
doc="Use shaders for OpenGLVMobject fill which are compatible with transformation matrices.",
|
||||
)
|
||||
|
||||
use_projection_stroke_shaders = property(
|
||||
lambda self: self._d["use_projection_stroke_shaders"],
|
||||
lambda self, val: self._set_boolean("use_projection_stroke_shaders", val),
|
||||
doc="Use shaders for OpenGLVMobject stroke which are compatible with transformation matrices.",
|
||||
)
|
||||
|
||||
def get_dir(self, key: str, **kwargs: str) -> Path:
|
||||
"""Resolve a config option that stores a directory.
|
||||
|
||||
|
|
|
|||
|
|
@ -124,4 +124,14 @@ render_options = option_group(
|
|||
option(
|
||||
"-t", "--transparent", is_flag=True, help="Render scenes with alpha channel."
|
||||
),
|
||||
option(
|
||||
"--use_projection_fill_shaders",
|
||||
is_flag=True,
|
||||
help="Use shaders for OpenGLVMobject fill which are compatible with transformation matrices.",
|
||||
),
|
||||
option(
|
||||
"--use_projection_stroke_shaders",
|
||||
is_flag=True,
|
||||
help="Use shaders for OpenGLVMobject stroke which are compatible with transformation matrices.",
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ class OpenGLArc(OpenGLTipableVMobject):
|
|||
self.n_components = n_components
|
||||
self.arc_center = arc_center
|
||||
super().__init__(self, **kwargs)
|
||||
self.orientation = -1
|
||||
|
||||
def init_points(self):
|
||||
self.set_points(
|
||||
|
|
@ -220,6 +221,7 @@ class OpenGLArc(OpenGLTipableVMobject):
|
|||
n_components=self.n_components,
|
||||
)
|
||||
)
|
||||
# To maintain proper orientation for fill shaders.
|
||||
self.scale(self.radius, about_point=ORIGIN)
|
||||
self.shift(self.arc_center)
|
||||
|
||||
|
|
|
|||
|
|
@ -253,6 +253,8 @@ class OpenGLSingleStringMathTex(OpenGLSVGMobject):
|
|||
self.scale(TEX_MOB_SCALE_FACTOR)
|
||||
if self.organize_left_to_right:
|
||||
self.organize_submobjects_left_to_right()
|
||||
for mob in self.submobjects:
|
||||
mob.orientation = -1
|
||||
|
||||
def __repr__(self):
|
||||
return f"{type(self).__name__}({repr(self.tex_string)})"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from typing import Optional
|
|||
import moderngl
|
||||
import numpy as np
|
||||
|
||||
from ... import config
|
||||
from ...constants import *
|
||||
from ...mobject.opengl_mobject import OpenGLMobject, OpenGLPoint
|
||||
|
||||
|
|
@ -117,6 +118,7 @@ class OpenGLVMobject(OpenGLMobject):
|
|||
|
||||
self.needs_new_triangulation = True
|
||||
self.triangulation = np.zeros(0, dtype="i4")
|
||||
self.orientation = 1
|
||||
super().__init__(**kwargs)
|
||||
self.refresh_unit_normal()
|
||||
|
||||
|
|
@ -888,11 +890,14 @@ class OpenGLVMobject(OpenGLMobject):
|
|||
|
||||
def interpolate(self, mobject1, mobject2, alpha, *args, **kwargs):
|
||||
super().interpolate(mobject1, mobject2, alpha, *args, **kwargs)
|
||||
if self.has_fill():
|
||||
tri1 = mobject1.get_triangulation()
|
||||
tri2 = mobject2.get_triangulation()
|
||||
if len(tri1) != len(tri1) or not np.all(tri1 == tri2):
|
||||
self.refresh_triangulation()
|
||||
if config["use_projection_fill_shaders"]:
|
||||
self.refresh_triangulation()
|
||||
else:
|
||||
if self.has_fill():
|
||||
tri1 = mobject1.get_triangulation()
|
||||
tri2 = mobject2.get_triangulation()
|
||||
if len(tri1) != len(tri1) or not np.all(tri1 == tri2):
|
||||
self.refresh_triangulation()
|
||||
return self
|
||||
|
||||
def pointwise_become_partial(self, vmobject, a, b):
|
||||
|
|
@ -1108,9 +1113,9 @@ class OpenGLVMobject(OpenGLMobject):
|
|||
stroke_shader_wrappers = []
|
||||
back_stroke_shader_wrappers = []
|
||||
for submob in self.family_members_with_points():
|
||||
if submob.has_fill():
|
||||
if submob.has_fill() and not config["use_projection_fill_shaders"]:
|
||||
fill_shader_wrappers.append(submob.get_fill_shader_wrapper())
|
||||
if submob.has_stroke():
|
||||
if submob.has_stroke() and not config["use_projection_stroke_shaders"]:
|
||||
ssw = submob.get_stroke_shader_wrapper()
|
||||
if submob.draw_stroke_behind_fill:
|
||||
back_stroke_shader_wrappers.append(ssw)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from manim.utils.exceptions import EndSceneEarlyException
|
|||
|
||||
from ..constants import *
|
||||
from ..mobject.opengl_mobject import OpenGLMobject, OpenGLPoint
|
||||
from ..mobject.types.opengl_vectorized_mobject import OpenGLVMobject
|
||||
from ..scene.scene_file_writer import SceneFileWriter
|
||||
from ..utils import opengl
|
||||
from ..utils.simple_functions import clip
|
||||
|
|
@ -26,6 +27,10 @@ from ..utils.space_ops import (
|
|||
)
|
||||
from .opengl_renderer_window import Window
|
||||
from .shader import Mesh, Shader
|
||||
from .vectorized_mobject_rendering import (
|
||||
render_opengl_vectorized_mobject_fill,
|
||||
render_opengl_vectorized_mobject_stroke,
|
||||
)
|
||||
|
||||
|
||||
class OpenGLCamera(OpenGLMobject):
|
||||
|
|
@ -262,6 +267,13 @@ class OpenGLRenderer:
|
|||
}
|
||||
|
||||
def render_mobject(self, mobject):
|
||||
if isinstance(mobject, OpenGLVMobject):
|
||||
if config["use_projection_fill_shaders"]:
|
||||
render_opengl_vectorized_mobject_fill(self, mobject)
|
||||
|
||||
if config["use_projection_stroke_shaders"]:
|
||||
render_opengl_vectorized_mobject_stroke(self, mobject)
|
||||
|
||||
shader_wrapper_list = mobject.get_shader_wrapper_list()
|
||||
|
||||
# Convert ShaderWrappers to Meshes.
|
||||
|
|
@ -377,11 +389,11 @@ class OpenGLRenderer:
|
|||
for obj in scene.meshes:
|
||||
for mesh in obj.get_meshes():
|
||||
mesh.shader.set_uniform(
|
||||
"model_matrix", opengl.matrix_to_shader_input(mesh.model_matrix)
|
||||
"u_model_matrix", opengl.matrix_to_shader_input(mesh.model_matrix)
|
||||
)
|
||||
mesh.shader.set_uniform("view_matrix", opengl_view_matrix)
|
||||
mesh.shader.set_uniform("u_view_matrix", opengl_view_matrix)
|
||||
mesh.shader.set_uniform(
|
||||
"projection_matrix",
|
||||
"u_projection_matrix",
|
||||
scene.camera.projection_matrix,
|
||||
)
|
||||
mesh.render()
|
||||
|
|
|
|||
|
|
@ -329,6 +329,7 @@ class Shader:
|
|||
name=None,
|
||||
source=None,
|
||||
):
|
||||
global shader_program_cache
|
||||
self.context = context
|
||||
self.name = name
|
||||
|
||||
|
|
@ -354,7 +355,7 @@ class Shader:
|
|||
self.shader_program = context.program(**source_dict)
|
||||
|
||||
# Cache the shader.
|
||||
if name is not None:
|
||||
if name is not None and name not in shader_program_cache:
|
||||
shader_program_cache[self.name] = self.shader_program
|
||||
|
||||
def set_uniform(self, name, value):
|
||||
|
|
@ -370,7 +371,6 @@ class FullScreenQuad(Mesh):
|
|||
context,
|
||||
fragment_shader_source=None,
|
||||
fragment_shader_name=None,
|
||||
output_color_variable="frag_color",
|
||||
):
|
||||
if fragment_shader_source is None and fragment_shader_name is None:
|
||||
raise Exception("Must either pass shader name or shader source.")
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@ uniform mat4 u_view_matrix;
|
|||
uniform mat4 u_projection_matrix;
|
||||
|
||||
void main() {
|
||||
gl_Position = u_projection_matrix * u_view_matrix * u_model_matrix * vec4(in_vert, 1);
|
||||
gl_Position = u_projection_matrix * u_view_matrix * u_model_matrix * vec4(in_vert, 1.0);
|
||||
}
|
||||
|
|
|
|||
9
manim/renderer/shaders/test/frag.glsl
Normal file
9
manim/renderer/shaders/test/frag.glsl
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#version 330
|
||||
|
||||
in vec4 v_color;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
void main() {
|
||||
frag_color = v_color;
|
||||
}
|
||||
11
manim/renderer/shaders/test/vert.glsl
Normal file
11
manim/renderer/shaders/test/vert.glsl
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#version 330
|
||||
|
||||
in vec2 in_vert;
|
||||
in vec4 in_color;
|
||||
|
||||
out vec4 v_color;
|
||||
|
||||
void main() {
|
||||
v_color = in_color;
|
||||
gl_Position = vec4(in_vert, 0.0, 1.0);
|
||||
}
|
||||
16
manim/renderer/shaders/vectorized_mobject_fill/frag.glsl
Normal file
16
manim/renderer/shaders/vectorized_mobject_fill/frag.glsl
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#version 330
|
||||
|
||||
in vec4 v_color;
|
||||
in vec2 v_texture_coords;
|
||||
flat in int v_texture_mode;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
void main() {
|
||||
float curve_func = v_texture_coords[0] * v_texture_coords[0] - v_texture_coords[1];
|
||||
if (v_texture_mode * curve_func >= 0.0) {
|
||||
frag_color = v_color;
|
||||
} else {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
20
manim/renderer/shaders/vectorized_mobject_fill/vert.glsl
Normal file
20
manim/renderer/shaders/vectorized_mobject_fill/vert.glsl
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#version 330
|
||||
|
||||
uniform mat4 u_view_matrix;
|
||||
uniform mat4 u_projection_matrix;
|
||||
|
||||
in vec3 in_vert;
|
||||
in vec4 in_color;
|
||||
in vec2 texture_coords;
|
||||
in int texture_mode;
|
||||
|
||||
out vec4 v_color;
|
||||
out vec2 v_texture_coords;
|
||||
flat out int v_texture_mode;
|
||||
|
||||
void main() {
|
||||
v_color = in_color;
|
||||
v_texture_coords = texture_coords;
|
||||
v_texture_mode = texture_mode;
|
||||
gl_Position = u_projection_matrix * u_view_matrix * vec4(in_vert, 1.0);
|
||||
}
|
||||
85
manim/renderer/shaders/vectorized_mobject_stroke/frag.glsl
Normal file
85
manim/renderer/shaders/vectorized_mobject_stroke/frag.glsl
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#version 330
|
||||
|
||||
in float v_degree;
|
||||
in float v_thickness;
|
||||
in vec2 uv_point;
|
||||
in vec2[3] uv_curve;
|
||||
in vec4 v_color;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
// https://www.shadertoy.com/view/ltXSDB
|
||||
// Test if point p crosses line (a, b), returns sign of result
|
||||
float testCross(vec2 a, vec2 b, vec2 p) {
|
||||
return sign((b.y-a.y) * (p.x-a.x) - (b.x-a.x) * (p.y-a.y));
|
||||
}
|
||||
|
||||
// Determine which side we're on (using barycentric parameterization)
|
||||
float signBezier(vec2 A, vec2 B, vec2 C, vec2 p)
|
||||
{
|
||||
vec2 a = C - A, b = B - A, c = p - A;
|
||||
vec2 bary = vec2(c.x*b.y-b.x*c.y,a.x*c.y-c.x*a.y) / (a.x*b.y-b.x*a.y);
|
||||
vec2 d = vec2(bary.y * 0.5, 0.0) + 1.0 - bary.x - bary.y;
|
||||
return mix(sign(d.x * d.x - d.y), mix(-1.0, 1.0,
|
||||
step(testCross(A, B, p) * testCross(B, C, p), 0.0)),
|
||||
step((d.x - d.y), 0.0)) * testCross(A, C, B);
|
||||
}
|
||||
|
||||
// Solve cubic equation for roots
|
||||
vec3 solveCubic(float a, float b, float c)
|
||||
{
|
||||
float p = b - a*a / 3.0, p3 = p*p*p;
|
||||
float q = a * (2.0*a*a - 9.0*b) / 27.0 + c;
|
||||
float d = q*q + 4.0*p3 / 27.0;
|
||||
float offset = -a / 3.0;
|
||||
if(d >= 0.0) {
|
||||
float z = sqrt(d);
|
||||
vec2 x = (vec2(z, -z) - q) / 2.0;
|
||||
vec2 uv = sign(x)*pow(abs(x), vec2(1.0/3.0));
|
||||
return vec3(offset + uv.x + uv.y);
|
||||
}
|
||||
float v = acos(-sqrt(-27.0 / p3) * q / 2.0) / 3.0;
|
||||
float m = cos(v), n = sin(v)*1.732050808;
|
||||
return vec3(m + m, -n - m, n - m) * sqrt(-p / 3.0) + offset;
|
||||
}
|
||||
|
||||
// Find the signed distance from a point to a bezier curve
|
||||
float sdBezier(vec2 A, vec2 B, vec2 C, vec2 p)
|
||||
{
|
||||
B = mix(B + vec2(1e-4), B, abs(sign(B * 2.0 - A - C)));
|
||||
vec2 a = B - A, b = A - B * 2.0 + C, c = a * 2.0, d = A - p;
|
||||
vec3 k = vec3(3.*dot(a,b),2.*dot(a,a)+dot(d,b),dot(d,a)) / dot(b,b);
|
||||
vec3 t = clamp(solveCubic(k.x, k.y, k.z), 0.0, 1.0);
|
||||
vec2 pos = A + (c + b*t.x)*t.x;
|
||||
float dis = length(pos - p);
|
||||
pos = A + (c + b*t.y)*t.y;
|
||||
dis = min(dis, length(pos - p));
|
||||
pos = A + (c + b*t.z)*t.z;
|
||||
dis = min(dis, length(pos - p));
|
||||
return dis * signBezier(A, B, C, p);
|
||||
}
|
||||
|
||||
// https://www.shadertoy.com/view/llcfR7
|
||||
float dLine(vec2 p1, vec2 p2, vec2 x) {
|
||||
vec4 colA = vec4(clamp (5.0 - length (x - p1), 0.0, 1.0));
|
||||
vec4 colB = vec4(clamp (5.0 - length (x - p2), 0.0, 1.0));
|
||||
|
||||
vec2 a_p1 = x - p1;
|
||||
vec2 p2_p1 = p2 - p1;
|
||||
float h = clamp (dot (a_p1, p2_p1) / dot (p2_p1, p2_p1), 0.0, 1.0);
|
||||
return length (a_p1 - p2_p1 * h);
|
||||
}
|
||||
|
||||
void main() {
|
||||
float distance;
|
||||
if (v_degree == 2.0) {
|
||||
distance = sdBezier(uv_curve[0], uv_curve[1], uv_curve[2], uv_point);
|
||||
} else {
|
||||
distance = dLine(uv_curve[0], uv_curve[2], uv_point);
|
||||
}
|
||||
if (abs(distance) < v_thickness) {
|
||||
frag_color = v_color;
|
||||
} else {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
125
manim/renderer/shaders/vectorized_mobject_stroke/vert.glsl
Normal file
125
manim/renderer/shaders/vectorized_mobject_stroke/vert.glsl
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#version 330
|
||||
|
||||
uniform vec3 manim_unit_normal;
|
||||
uniform mat4 u_model_view_matrix;
|
||||
uniform mat4 u_projection_matrix;
|
||||
|
||||
in vec3[3] current_curve;
|
||||
in vec2 tile_coordinate;
|
||||
in vec4 in_color;
|
||||
in float in_width;
|
||||
|
||||
out float v_degree;
|
||||
out float v_thickness;
|
||||
out vec2 uv_point;
|
||||
out vec2[3] uv_curve;
|
||||
out vec4 v_color;
|
||||
|
||||
int get_degree(in vec3 points[3], out vec3 normal) {
|
||||
float length_threshold = 1e-6;
|
||||
float angle_threshold = 5e-2;
|
||||
|
||||
vec3 v01 = (points[1] - points[0]);
|
||||
vec3 v12 = (points[2] - points[1]);
|
||||
|
||||
float dot_prod = clamp(dot(normalize(v01), normalize(v12)), -1, 1);
|
||||
bool aligned = acos(dot_prod) < angle_threshold;
|
||||
bool distinct_01 = length(v01) > length_threshold; // v01 is considered nonzero
|
||||
bool distinct_12 = length(v12) > length_threshold; // v12 is considered nonzero
|
||||
int num_distinct = int(distinct_01) + int(distinct_12);
|
||||
|
||||
bool quadratic = (num_distinct == 2) && !aligned;
|
||||
bool linear = (num_distinct == 1) || ((num_distinct == 2) && aligned);
|
||||
bool constant = (num_distinct == 0);
|
||||
|
||||
if (quadratic) {
|
||||
// If the curve is quadratic pass a normal vector to the caller.
|
||||
normal = normalize(cross(v01, v12));
|
||||
return 2;
|
||||
} else if (linear) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// https://iquilezles.org/www/articles/bezierbbox/bezierbbox.htm
|
||||
vec4 bboxBezier(in vec2 p0, in vec2 p1, in vec2 p2) {
|
||||
vec2 mi = min(p0, p2);
|
||||
vec2 ma = max(p0, p2);
|
||||
|
||||
if (p1.x < mi.x || p1.x > ma.x || p1.y < mi.y || p1.y > ma.y) {
|
||||
vec2 t = clamp((p0 - p1) / (p0 - 2.0 * p1 + p2), 0.0, 1.0);
|
||||
vec2 s = 1.0 - t;
|
||||
vec2 q = s * s * p0 + 2.0 * s * t * p1 + t * t * p2;
|
||||
mi = min(mi, q);
|
||||
ma = max(ma, q);
|
||||
}
|
||||
|
||||
return vec4(mi, ma);
|
||||
}
|
||||
|
||||
vec2 convert_to_uv(vec3 x_unit, vec3 y_unit, vec3 point) {
|
||||
return vec2(dot(point, x_unit), dot(point, y_unit));
|
||||
}
|
||||
|
||||
vec3 convert_from_uv(vec3 translation, vec3 x_unit, vec3 y_unit, vec2 point) {
|
||||
vec3 untranslated_point = point[0] * x_unit + point[1] * y_unit;
|
||||
return untranslated_point + translation;
|
||||
}
|
||||
|
||||
void main() {
|
||||
float thickness_multiplier = 0.004;
|
||||
v_color = in_color;
|
||||
|
||||
vec3 computed_normal;
|
||||
v_degree = get_degree(current_curve, computed_normal);
|
||||
|
||||
vec3 tile_x_unit = normalize(current_curve[2] - current_curve[0]);
|
||||
vec3 unit_normal;
|
||||
vec3 tile_y_unit;
|
||||
if (v_degree == 0) {
|
||||
tile_y_unit = vec3(0.0, 0.0, 0.0);
|
||||
} else if (v_degree == 1) {
|
||||
// Since the curve forms a straight line there's no way to compute a normal.
|
||||
unit_normal = manim_unit_normal;
|
||||
|
||||
tile_y_unit = cross(unit_normal, tile_x_unit);
|
||||
} else {
|
||||
// Prefer to use a computed normal vector rather than the one from manim.
|
||||
unit_normal = computed_normal;
|
||||
|
||||
// Ensure tile_y_unit is pointing toward p1 from p0.
|
||||
tile_y_unit = cross(unit_normal, tile_x_unit);
|
||||
if (dot(tile_y_unit, current_curve[1] - current_curve[0]) < 0) {
|
||||
tile_y_unit *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Project the curve onto the tile.
|
||||
for(int i = 0; i < 3; i++) {
|
||||
uv_curve[i] = convert_to_uv(tile_x_unit, tile_y_unit, current_curve[i]);
|
||||
}
|
||||
|
||||
// Compute the curve's bounding box.
|
||||
vec4 uv_bounding_box = bboxBezier(uv_curve[0], uv_curve[1], uv_curve[2]);
|
||||
vec3 tile_translation = unit_normal * dot(current_curve[0], unit_normal);
|
||||
vec3 bounding_box_min = convert_from_uv(tile_translation, tile_x_unit, tile_y_unit, uv_bounding_box.xy);
|
||||
vec3 bounding_box_max = convert_from_uv(tile_translation, tile_x_unit, tile_y_unit, uv_bounding_box.zw);
|
||||
vec3 bounding_box_vec = bounding_box_max - bounding_box_min;
|
||||
vec3 tile_origin = bounding_box_min;
|
||||
vec3 tile_x_vec = tile_x_unit * dot(tile_x_unit, bounding_box_vec);
|
||||
vec3 tile_y_vec = tile_y_unit * dot(tile_y_unit, bounding_box_vec);
|
||||
|
||||
// Expand the tile according to the line's thickness.
|
||||
v_thickness = thickness_multiplier * in_width;
|
||||
tile_origin = current_curve[0] - v_thickness * (tile_x_unit + tile_y_unit);
|
||||
tile_x_vec += 2 * v_thickness * tile_x_unit;
|
||||
tile_y_vec += 2 * v_thickness * tile_y_unit;
|
||||
|
||||
vec3 tile_point = tile_origin + \
|
||||
tile_coordinate[0] * tile_x_vec + \
|
||||
tile_coordinate[1] * tile_y_vec;
|
||||
gl_Position = u_projection_matrix * u_model_view_matrix * vec4(tile_point, 1.0);
|
||||
uv_point = convert_to_uv(tile_x_unit, tile_y_unit, tile_point);
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
#version 330
|
||||
|
||||
uniform mat4 u_model_matrix;
|
||||
uniform mat4 u_view_matrix;
|
||||
uniform mat4 u_projection_matrix;
|
||||
in vec4 in_vert;
|
||||
in vec4 in_color;
|
||||
out vec4 v_color;
|
||||
uniform mat4 model_matrix;
|
||||
uniform mat4 view_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
|
||||
void main() {
|
||||
v_color = in_color;
|
||||
gl_Position = projection_matrix * view_matrix * model_matrix * in_vert;
|
||||
gl_Position = u_projection_matrix * u_view_matrix * u_model_matrix * in_vert;
|
||||
}
|
||||
|
|
|
|||
226
manim/renderer/vectorized_mobject_rendering.py
Normal file
226
manim/renderer/vectorized_mobject_rendering.py
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
import numpy as np
|
||||
|
||||
from ..constants import *
|
||||
from ..mobject.types.opengl_vectorized_mobject import OpenGLVMobject
|
||||
from ..utils.space_ops import cross2d, earclip_triangulation, z_to_vector
|
||||
from .shader import Shader
|
||||
|
||||
|
||||
def render_opengl_vectorized_mobject_fill(renderer, mobject):
|
||||
attributes = np.empty(
|
||||
0,
|
||||
dtype=[
|
||||
("in_vert", np.float32, (3,)),
|
||||
("in_color", np.float32, (4,)),
|
||||
("texture_coords", np.float32, (2,)),
|
||||
("texture_mode", np.int32),
|
||||
],
|
||||
)
|
||||
color = np.empty((0, 4), dtype=np.float32)
|
||||
for submob in mobject.family_members_with_points():
|
||||
mobject_triangulation = triangulate_mobject(submob)
|
||||
mobject_color = np.repeat(
|
||||
submob.data["fill_rgba"], mobject_triangulation.shape[0], axis=0
|
||||
)
|
||||
attributes = np.append(attributes, mobject_triangulation)
|
||||
color = np.append(color, mobject_color, axis=0)
|
||||
attributes["in_color"] = color
|
||||
|
||||
fill_shader = Shader(
|
||||
renderer.context,
|
||||
name="vectorized_mobject_fill",
|
||||
)
|
||||
fill_shader.set_uniform(
|
||||
"u_view_matrix",
|
||||
renderer.camera.get_view_matrix(),
|
||||
)
|
||||
fill_shader.set_uniform(
|
||||
"u_projection_matrix",
|
||||
renderer.scene.camera.projection_matrix,
|
||||
)
|
||||
|
||||
vbo = renderer.context.buffer(attributes.tobytes())
|
||||
vao = renderer.context.simple_vertex_array(
|
||||
fill_shader.shader_program,
|
||||
vbo,
|
||||
*attributes.dtype.names,
|
||||
)
|
||||
vao.render()
|
||||
vao.release()
|
||||
vbo.release()
|
||||
|
||||
|
||||
def triangulate_mobject(mob):
|
||||
if not mob.needs_new_triangulation:
|
||||
return mob.triangulation
|
||||
|
||||
# Figure out how to triangulate the interior to know
|
||||
# how to send the points as to the vertex shader.
|
||||
# First triangles come directly from the points
|
||||
# normal_vector = mob.get_unit_normal()
|
||||
points = mob.get_points()
|
||||
|
||||
b0s = points[0::3]
|
||||
b1s = points[1::3]
|
||||
b2s = points[2::3]
|
||||
v01s = b1s - b0s
|
||||
v12s = b2s - b1s
|
||||
|
||||
crosses = cross2d(v01s, v12s)
|
||||
convexities = np.sign(crosses)
|
||||
if mob.orientation == 1:
|
||||
concave_parts = convexities > 0
|
||||
convex_parts = convexities <= 0
|
||||
else:
|
||||
concave_parts = convexities < 0
|
||||
convex_parts = convexities >= 0
|
||||
|
||||
# These are the vertices to which we'll apply a polygon triangulation
|
||||
atol = mob.tolerance_for_point_equality
|
||||
end_of_loop = np.zeros(len(b0s), dtype=bool)
|
||||
end_of_loop[:-1] = (np.abs(b2s[:-1] - b0s[1:]) > atol).any(1)
|
||||
end_of_loop[-1] = True
|
||||
|
||||
indices = np.arange(len(points), dtype=int)
|
||||
inner_vert_indices = np.hstack(
|
||||
[
|
||||
indices[0::3],
|
||||
indices[1::3][concave_parts],
|
||||
indices[2::3][end_of_loop],
|
||||
]
|
||||
)
|
||||
inner_vert_indices.sort()
|
||||
rings = np.arange(1, len(inner_vert_indices) + 1)[inner_vert_indices % 3 == 2]
|
||||
|
||||
# Triangulate
|
||||
inner_verts = points[inner_vert_indices]
|
||||
inner_tri_indices = inner_vert_indices[earclip_triangulation(inner_verts, rings)]
|
||||
|
||||
bezier_triangle_indices = np.reshape(indices, (-1, 3))
|
||||
concave_triangle_indices = np.reshape(bezier_triangle_indices[concave_parts], (-1))
|
||||
convex_triangle_indices = np.reshape(bezier_triangle_indices[convex_parts], (-1))
|
||||
|
||||
points = points[
|
||||
np.hstack(
|
||||
[
|
||||
concave_triangle_indices,
|
||||
convex_triangle_indices,
|
||||
inner_tri_indices,
|
||||
]
|
||||
)
|
||||
]
|
||||
texture_coords = np.tile(
|
||||
[
|
||||
[0.0, 0.0],
|
||||
[0.5, 0.0],
|
||||
[1.0, 1.0],
|
||||
],
|
||||
(points.shape[0] // 3, 1),
|
||||
)
|
||||
texture_mode = np.hstack(
|
||||
(
|
||||
np.ones((concave_triangle_indices.shape[0])),
|
||||
-1 * np.ones((convex_triangle_indices.shape[0])),
|
||||
np.zeros((inner_tri_indices.shape[0])),
|
||||
),
|
||||
)
|
||||
|
||||
attributes = np.zeros(
|
||||
points.shape[0],
|
||||
dtype=[
|
||||
("in_vert", np.float32, (3,)),
|
||||
("in_color", np.float32, (4,)),
|
||||
("texture_coords", np.float32, (2,)),
|
||||
("texture_mode", np.int32),
|
||||
],
|
||||
)
|
||||
attributes["in_vert"] = points
|
||||
attributes["texture_coords"] = texture_coords
|
||||
attributes["texture_mode"] = texture_mode
|
||||
|
||||
mob.triangulation = attributes
|
||||
mob.needs_new_triangulation = False
|
||||
|
||||
return attributes
|
||||
|
||||
|
||||
def render_opengl_vectorized_mobject_stroke(renderer, mobject):
|
||||
shader = Shader(renderer.context, "vectorized_mobject_stroke")
|
||||
|
||||
shader.set_uniform("u_model_view_matrix", renderer.camera.get_view_matrix())
|
||||
shader.set_uniform(
|
||||
"u_projection_matrix",
|
||||
renderer.scene.camera.projection_matrix,
|
||||
)
|
||||
|
||||
points = np.empty((0, 3))
|
||||
colors = np.empty((0, 4))
|
||||
widths = np.empty((0))
|
||||
for submob in mobject.family_members_with_points():
|
||||
points = np.append(points, submob.data["points"], axis=0)
|
||||
colors = np.append(
|
||||
colors,
|
||||
np.repeat(
|
||||
submob.data["stroke_rgba"], submob.data["points"].shape[0], axis=0
|
||||
),
|
||||
axis=0,
|
||||
)
|
||||
widths = np.append(
|
||||
widths,
|
||||
np.repeat(submob.data["stroke_width"], submob.data["points"].shape[0]),
|
||||
)
|
||||
|
||||
stroke_data = np.zeros(
|
||||
len(points),
|
||||
dtype=[
|
||||
# ("previous_curve", np.float32, (3, 3)),
|
||||
("current_curve", np.float32, (3, 3)),
|
||||
# ("next_curve", np.float32, (3, 3)),
|
||||
("tile_coordinate", np.float32, (2,)),
|
||||
("in_color", np.float32, (4,)),
|
||||
("in_width", np.float32),
|
||||
],
|
||||
)
|
||||
|
||||
stroke_data["in_color"] = colors
|
||||
stroke_data["in_width"] = widths
|
||||
curves = np.reshape(points, (-1, 3, 3))
|
||||
# stroke_data["previous_curve"] = np.repeat(np.roll(curves, 1, axis=0), 3, axis=0)
|
||||
stroke_data["current_curve"] = np.repeat(curves, 3, axis=0)
|
||||
# stroke_data["next_curve"] = np.repeat(np.roll(curves, -1, axis=0), 3, axis=0)
|
||||
|
||||
# Repeat each vertex in order to make a tile.
|
||||
stroke_data = np.tile(stroke_data, 2)
|
||||
stroke_data["tile_coordinate"] = np.concatenate(
|
||||
(
|
||||
np.tile(
|
||||
[
|
||||
[0.0, 0.0],
|
||||
[0.0, 1.0],
|
||||
[1.0, 1.0],
|
||||
],
|
||||
(len(points) // 3, 1),
|
||||
),
|
||||
np.tile(
|
||||
[
|
||||
[0.0, 0.0],
|
||||
[1.0, 0.0],
|
||||
[1.0, 1.0],
|
||||
],
|
||||
(len(points) // 3, 1),
|
||||
),
|
||||
),
|
||||
axis=0,
|
||||
)
|
||||
|
||||
shader.set_uniform("color", tuple(mobject.data["stroke_rgba"][0]))
|
||||
shader.set_uniform("manim_unit_normal", tuple(-mobject.data["unit_normal"][0]))
|
||||
|
||||
vbo = renderer.context.buffer(stroke_data.tobytes())
|
||||
vao = renderer.context.simple_vertex_array(
|
||||
shader.shader_program, vbo, *stroke_data.dtype.names
|
||||
)
|
||||
renderer.frame_buffer_object.use()
|
||||
vao.render()
|
||||
vao.release()
|
||||
vbo.release()
|
||||
|
|
@ -415,7 +415,6 @@ class Scene:
|
|||
return self
|
||||
|
||||
def add_mobjects_from_animations(self, animations):
|
||||
|
||||
curr_mobjects = self.get_mobject_family_members()
|
||||
for animation in animations:
|
||||
# Anything animated that's not already in the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue