diff --git a/manim/camera/camera.py b/manim/camera/camera.py index bf6a285caa..e1b620e682 100644 --- a/manim/camera/camera.py +++ b/manim/camera/camera.py @@ -12,7 +12,6 @@ from manim import config, logger from manim.mobject.opengl.opengl_mobject import OpenGLMobject, OpenGLPoint -from manim.renderer.shader_wrapper import ShaderWrapper from manim.utils.color import BLACK, color_to_rgba from ..constants import * @@ -157,355 +156,3 @@ def get_implied_camera_location(self) -> np.ndarray: to_camera = self.get_inverse_camera_rotation_matrix()[2] dist = self.get_focal_distance() return self.get_center() + dist * to_camera - - -# TODO: This is already ported to the renderer and now is useless, leavefor now for compoatibilty reasons -class OpenGLCamera: - def __init__( - self, - ctx: moderngl.Context | None = None, - background_image: str | None = None, - frame_config: dict = {}, - pixel_width: int = config.pixel_width, - pixel_height: int = config.pixel_height, - fps: int = config.frame_rate, - # Note: frame height and width will be resized to match the pixel aspect rati - background_color=BLACK, - background_opacity: float = 1.0, - # Points in vectorized mobjects with norm greater - # than this value will be rescaled - max_allowable_norm: float = 1.0, - image_mode: str = "RGBA", - n_channels: int = 4, - pixel_array_dtype: type = np.uint8, - light_source_position: np.ndarray = np.array([-10, 10, 10]), - # Although vector graphics handle antialiasing fine - # without multisampling, for 3d scenes one might want - # to set samples to be greater than 0. - samples: int = 0, - ) -> None: - self.background_image = background_image - self.pixel_width = pixel_width - self.pixel_height = pixel_height - self.fps = fps - self.max_allowable_norm = max_allowable_norm - self.image_mode = image_mode - self.n_channels = n_channels - self.pixel_array_dtype = pixel_array_dtype - self.light_source_position = light_source_position - self.samples = samples - - self.rgb_max_val: float = np.iinfo(self.pixel_array_dtype).max - self.background_color: list[float] = list( - color_to_rgba(background_color, background_opacity) - ) - self.init_frame(**frame_config) - self.init_context(ctx) - self.init_shaders() - self.init_textures() - self.init_light_source() - self.refresh_perspective_uniforms() - # A cached map from mobjects to their associated list of render groups - # so that these render groups are not regenerated unnecessarily for static - # mobjects - self.mob_to_render_groups: dict = {} - - def init_frame(self, **config) -> None: - self.frame = OpenGLCameraFrame(**config) - - def init_context(self, ctx: moderngl.Context | None = None) -> None: - if ctx is None: - ctx = moderngl.create_standalone_context() - fbo = self.get_fbo(ctx, 0) - else: - fbo = ctx.detect_framebuffer() - - self.ctx = ctx - self.fbo = fbo - self.set_ctx_blending() - - # For multisample antisampling - fbo_msaa = self.get_fbo(ctx, self.samples) - fbo_msaa.use() - self.fbo_msaa = fbo_msaa - - def set_ctx_blending(self, enable: bool = True) -> None: - if enable: - self.ctx.enable(moderngl.BLEND) - else: - self.ctx.disable(moderngl.BLEND) - - def set_ctx_depth_test(self, enable: bool = True) -> None: - if enable: - self.ctx.enable(moderngl.DEPTH_TEST) - else: - self.ctx.disable(moderngl.DEPTH_TEST) - - def init_light_source(self) -> None: - self.light_source = OpenGLPoint(self.light_source_position) - - # Methods associated with the frame buffer - def get_fbo(self, ctx: moderngl.Context, samples: int = 0) -> moderngl.Framebuffer: - pw = self.pixel_width - ph = self.pixel_height - return ctx.framebuffer( - color_attachments=ctx.texture( - (pw, ph), components=self.n_channels, samples=samples - ), - depth_attachment=ctx.depth_renderbuffer((pw, ph), samples=samples), - ) - - def clear(self) -> None: - self.fbo.clear(*self.background_color) - self.fbo_msaa.clear(*self.background_color) - - def reset_pixel_shape(self, new_width: int, new_height: int) -> None: - self.pixel_width = new_width - self.pixel_height = new_height - self.refresh_perspective_uniforms() - - def get_raw_fbo_data(self, dtype: str = "f1") -> bytes: - # Copy blocks from the fbo_msaa to the drawn fbo using Blit - # pw, ph = (self.pixel_width, self.pixel_height) - # gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, self.fbo_msaa.glo) - # gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, self.fbo.glo) - # gl.glBlitFramebuffer( - # 0, 0, pw, ph, 0, 0, pw, ph, gl.GL_COLOR_BUFFER_BIT, gl.GL_LINEAR - # ) - - self.ctx.copy_framebuffer(self.fbo, self.fbo_msaa) - return self.fbo.read( - viewport=self.fbo.viewport, - components=self.n_channels, - dtype=dtype, - ) - - def get_image(self) -> Image.Image: - return Image.frombytes( - "RGBA", - self.get_pixel_shape(), - self.get_raw_fbo_data(), - "raw", - "RGBA", - 0, - -1, - ) - - def get_pixel_array(self) -> np.ndarray: - raw = self.get_raw_fbo_data(dtype="f4") - flat_arr = np.frombuffer(raw, dtype="f4") - arr = flat_arr.reshape([*reversed(self.fbo.size), self.n_channels]) - arr = arr[::-1] - # Convert from float - return (self.rgb_max_val * arr).astype(self.pixel_array_dtype) - - def get_texture(self): - texture = self.ctx.texture( - size=self.fbo.size, components=4, data=self.get_raw_fbo_data(), dtype="f4" - ) - return texture - - # Getting camera attributes - def get_pixel_shape(self) -> tuple[int, int]: - return self.fbo.viewport[2:4] - # return (self.pixel_width, self.pixel_height) - - def get_pixel_width(self) -> int: - return self.get_pixel_shape()[0] - - def get_pixel_height(self) -> int: - return self.get_pixel_shape()[1] - - def get_frame_height(self) -> float: - return self.frame.get_height() - - def get_frame_width(self) -> float: - return self.frame.get_width() - - def get_frame_shape(self) -> tuple[float, float]: - return (self.get_frame_width(), self.get_frame_height()) - - def get_frame_center(self) -> np.ndarray: - return self.frame.get_center() - - def get_location(self) -> tuple[float, float, float] | np.ndarray: - return self.frame.get_implied_camera_location() - - def resize_frame_shape(self, fixed_dimension: bool = False) -> None: - """ - Changes frame_shape to match the aspect ratio - of the pixels, where fixed_dimension determines - whether frame_height or frame_width - remains fixed while the other changes accordingly. - """ - pixel_height = self.get_pixel_height() - pixel_width = self.get_pixel_width() - frame_height = self.get_frame_height() - frame_width = self.get_frame_width() - aspect_ratio = fdiv(pixel_width, pixel_height) - if not fixed_dimension: - frame_height = frame_width / aspect_ratio - else: - frame_width = aspect_ratio * frame_height - self.frame.set_height(frame_height) - self.frame.set_width(frame_width) - - # Rendering - def capture(self, *mobjects: OpenGLMobject) -> None: - self.refresh_perspective_uniforms() - for mobject in mobjects: - for render_group in self.get_render_group_list(mobject): - self.render(render_group) - - def render(self, render_group: dict[str, Any]) -> None: - shader_wrapper: ShaderWrapper = render_group["shader_wrapper"] - shader_program = render_group["prog"] - self.set_shader_uniforms(shader_program, shader_wrapper) - self.set_ctx_depth_test(shader_wrapper.depth_test) - render_group["vao"].render(int(shader_wrapper.render_primitive)) - if render_group["single_use"]: - self.release_render_group(render_group) - - def get_render_group_list(self, mobject: OpenGLMobject) -> Iterable[dict[str, Any]]: - if mobject.is_changing(): - return self.generate_render_group_list(mobject) - - # Otherwise, cache result for later use - key = id(mobject) - if key not in self.mob_to_render_groups: - self.mob_to_render_groups[key] = list( - self.generate_render_group_list(mobject) - ) - return self.mob_to_render_groups[key] - - def generate_render_group_list( - self, mobject: OpenGLMobject - ) -> Iterable[dict[str, Any]]: - return ( - self.get_render_group(sw, single_use=mobject.is_changing()) - for sw in mobject.get_shader_wrapper_list() - ) - - def get_render_group( - self, shader_wrapper: ShaderWrapper, single_use: bool = True - ) -> dict[str, Any]: - # Data buffers - vbo = self.ctx.buffer(shader_wrapper.vert_data.tobytes()) - if shader_wrapper.vert_indices is None: - ibo = None - else: - vert_index_data = shader_wrapper.vert_indices.astype("i4").tobytes() - if vert_index_data: - ibo = self.ctx.buffer(vert_index_data) - else: - ibo = None - - # Program an vertex array - shader_program, vert_format = self.get_shader_program(shader_wrapper) # type: ignore - vao = self.ctx.vertex_array( - program=shader_program, - content=[(vbo, vert_format, *shader_wrapper.vert_attributes)], - index_buffer=ibo, - ) - return { - "vbo": vbo, - "ibo": ibo, - "vao": vao, - "prog": shader_program, - "shader_wrapper": shader_wrapper, - "single_use": single_use, - } - - def release_render_group(self, render_group: dict[str, Any]) -> None: - for key in ["vbo", "ibo", "vao"]: - if render_group[key] is not None: - render_group[key].release() - - def refresh_static_mobjects(self) -> None: - for render_group in it.chain(*self.mob_to_render_groups.values()): - self.release_render_group(render_group) - self.mob_to_render_groups = {} - - # Shaders - def init_shaders(self) -> None: - # Initialize with the null id going to None - self.id_to_shader_program: dict[int, tuple[moderngl.Program, str] | None] = { - hash(""): None - } - - def get_shader_program( - self, shader_wrapper: ShaderWrapper - ) -> tuple[moderngl.Program, str] | None: - sid = shader_wrapper.get_program_id() - if sid not in self.id_to_shader_program: - # Create shader program for the first time, then cache - # in the id_to_shader_program dictionary - program = self.ctx.program(**shader_wrapper.get_program_code()) - vert_format = moderngl.detect_format( - program, shader_wrapper.vert_attributes - ) - self.id_to_shader_program[sid] = (program, vert_format) - - return self.id_to_shader_program[sid] - - def set_shader_uniforms( - self, - shader: moderngl.Program, - shader_wrapper: ShaderWrapper, - ) -> None: - for name, path in shader_wrapper.texture_paths.items(): - tid = self.get_texture_id(path) - shader[name].value = tid - for name, value in it.chain( - self.perspective_uniforms.items(), shader_wrapper.uniforms.items() - ): - if name in shader: - if isinstance(value, np.ndarray) and value.ndim > 0: - value = tuple(value) - shader[name].value = value - else: - logger.debug(f"Uniform {name} not found in shader {shader}") - - def refresh_perspective_uniforms(self) -> None: - frame = self.frame - # Orient light - rotation = frame.get_inverse_camera_rotation_matrix() - offset = frame.get_center() - light_pos = np.dot(rotation, self.light_source.get_location() + offset) - cam_pos = self.frame.get_implied_camera_location() # TODO - - self.perspective_uniforms = { - "frame_shape": frame.get_shape(), - "pixel_shape": self.get_pixel_shape(), - "camera_offset": tuple(offset), - "camera_rotation": tuple(np.array(rotation).T.flatten()), - "camera_position": tuple(cam_pos), - "light_source_position": tuple(light_pos), - "focal_distance": frame.get_focal_distance(), - } - - def init_textures(self) -> None: - self.n_textures: int = 0 - self.path_to_texture: dict[str, tuple[int, moderngl.Texture]] = {} - - def get_texture_id(self, path: str) -> int: - if path not in self.path_to_texture: - if self.n_textures == 15: # I have no clue why this is needed - self.n_textures += 1 - tid = self.n_textures - self.n_textures += 1 - im = Image.open(path).convert("RGBA") - texture = self.ctx.texture( - size=im.size, - components=len(im.getbands()), - data=im.tobytes(), - ) - texture.use(location=tid) - self.path_to_texture[path] = (tid, texture) - return self.path_to_texture[path][0] - - def release_texture(self, path: str): - tid_and_texture = self.path_to_texture.pop(path, None) - if tid_and_texture: - tid_and_texture[1].release() - return self diff --git a/manim/mobject/opengl/opengl_mobject.py b/manim/mobject/opengl/opengl_mobject.py index 391f87c3bc..d902e4ff68 100644 --- a/manim/mobject/opengl/opengl_mobject.py +++ b/manim/mobject/opengl/opengl_mobject.py @@ -21,7 +21,6 @@ from manim.event_handler import EVENT_DISPATCHER from manim.event_handler.event_listener import EventListener from manim.event_handler.event_type import EventType -from manim.renderer.shader_wrapper import ShaderWrapper, get_colormap_code from manim.utils.bezier import integer_interpolate, interpolate from manim.utils.color import * from manim.utils.deprecation import deprecated @@ -60,7 +59,6 @@ PointUpdateFunction: TypeAlias = Callable[[np.ndarray], np.ndarray] from manim.renderer.renderer import RendererData - T = TypeVar("T", bound=RendererData) _F = TypeVar("_F", bound=Callable[..., Any]) UNIFORM_DTYPE = np.float64 @@ -165,7 +163,7 @@ def __init__( self.data: dict[str, np.ndarray] = {} self.uniforms: dict[str, float | np.ndarray] = {} - self.renderer_data: T | None = None + self.renderer_data: RendererData | None = None self.status = MobjectStatus() self.init_data() @@ -1414,8 +1412,6 @@ def copy(self, deep: bool = False) -> Self: setattr(result, attr, result.family[self.family.index(value)]) if isinstance(value, np.ndarray): setattr(result, attr, value.copy()) - if isinstance(value, ShaderWrapper): - setattr(result, attr, value.copy()) return result def generate_target(self, use_deepcopy: bool = False): diff --git a/manim/mobject/opengl/opengl_vectorized_mobject.py b/manim/mobject/opengl/opengl_vectorized_mobject.py index f23a3e3caf..09b1822d07 100644 --- a/manim/mobject/opengl/opengl_vectorized_mobject.py +++ b/manim/mobject/opengl/opengl_vectorized_mobject.py @@ -16,7 +16,6 @@ OpenGLMobject, OpenGLPoint, ) -from manim.renderer.shader_wrapper import ShaderWrapper from manim.utils.bezier import ( bezier, get_quadratic_approximation_of_cubic, @@ -112,9 +111,9 @@ def __init__( self.background_image_file = background_image_file self.long_lines = long_lines self.joint_type = joint_type + self.joint_product = np.ones((3, 4)) + self.joint_normal = np.zeros((0, 3)) self.flat_stroke = flat_stroke - # TODO: Remove this because the new shader doesn't need it - self.anti_alias_width = 1.0 self.needs_new_triangulation = True self.triangulation = np.zeros(0, dtype="i4") @@ -155,7 +154,6 @@ def init_data(self): def init_uniforms(self): super().init_uniforms() - self.uniforms["anti_alias_width"] = float(self.anti_alias_width) self.uniforms["joint_type"] = float(self.joint_type.value) self.uniforms["flat_stroke"] = float(self.flat_stroke) @@ -1364,6 +1362,7 @@ def interp(obj1, obj2, alpha): setattr(self, attr, result) # TODO: compare to 3b1b/manim again check if something changed so we don't need the cairo interpolation anymore + # FIXME: JOINT_PRODUCT def pointwise_become_partial( self, vmobject: OpenGLVMobject, a: float, b: float, remap: bool = True ) -> Self: diff --git a/manim/mobject/opengl/shader.py b/manim/mobject/opengl/shader.py index 892ccb5892..1e3e7d51cb 100644 --- a/manim/mobject/opengl/shader.py +++ b/manim/mobject/opengl/shader.py @@ -1,3 +1,7 @@ +######################################### +# THIS FILE IS NOT USED IN EXPERIMENTAL # +######################################### + from __future__ import annotations import re diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index 6fe51a7c14..f399b02042 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -1,3 +1,6 @@ +######################################### +# THIS FILE IS NOT USED IN EXPERIMENTAL # +######################################### """Mobjects that use vector graphics.""" from __future__ import annotations diff --git a/manim/renderer/opengl_renderer.py b/manim/renderer/opengl_renderer.py index 26899611de..6b6f1ff8f9 100644 --- a/manim/renderer/opengl_renderer.py +++ b/manim/renderer/opengl_renderer.py @@ -1,5 +1,7 @@ from __future__ import annotations +from dataclasses import dataclass + import moderngl as gl import numpy as np @@ -32,101 +34,27 @@ ("point", np.float32, (3,)), ("unit_normal", np.float32, (3,)), ("color", np.float32, (4,)), - ("vert_index", np.float32, (1,)), + ("base_point", np.float32, (3,)), ] stroke_dtype = [ ("point", np.float32, (3,)), - ("prev_point", np.float32, (3,)), - ("next_point", np.float32, (3,)), ("stroke_width", np.float32, (1,)), + ("joint_product", np.float32, (4,)), ("color", np.float32, (4,)), ] frame_dtype = [("pos", np.float32, (2,)), ("uv", np.float32, (2,))] +# FIXME: Probably should use field or default factory no clue right now! +@dataclass class GLRenderData(RendererData): - def __init__(self) -> None: - super().__init__() - self.fill_rgbas = np.zeros((1, 4)) - self.stroke_rgbas = np.zeros((1, 4)) - self.stroke_widths = np.zeros((1, 1)) - self.normals = np.zeros((1, 4)) - self.orientation = np.zeros((1, 1)) - self.vert_indices = np.zeros((0, 3)) - self.bounding_box = np.zeros((3, 3)) - - def __repr__(self) -> str: - return f"""GLRenderData -fill: -{self.fill_rgbas} -stroke: -{self.stroke_rgbas} -normals: -{self.normals} -orientation: -{self.orientation} -mesh: -{self.vert_indices} -bounding_box: -{self.bounding_box} - """ - - -# TODO: Move into GLVMobjectManager -def get_triangulation(self: OpenGLVMobject, normal_vector=None): - # 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 - if normal_vector is None: - normal_vector = self.get_unit_normal() - - points = self.points - - if len(points) <= 1: - self.triangulation = np.zeros(0, dtype="i4") - self.needs_new_triangulation = False - return self.triangulation - - if not np.isclose(normal_vector, const.OUT).all(): - # Rotate points such that unit normal vector is OUT - points = np.dot(points, z_to_vector(normal_vector)) - indices = np.arange(len(points), dtype=int) - - 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) - - atol = self.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 - - concave_parts = convexities < 0 - - # These are the vertices to which we'll apply a polygon triangulation - 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)] - - tri_indices = np.hstack([indices, inner_tri_indices]) - self.triangulation = tri_indices - self.needs_new_triangulation = False - return tri_indices + fill_rgbas = np.zeros((1, 4)) + stroke_rgbas = np.zeros((1, 4)) + stroke_widths = np.zeros((1, 1)) + normals = np.zeros((1, 4)) + orientation = np.zeros((1, 1)) + vert_indices = np.zeros((0, 3)) + bounding_box = np.zeros((3, 3)) def prepare_array(values: np.ndarray, desired_length: int): @@ -163,24 +91,6 @@ def prepare_array(values: np.ndarray, desired_length: int): return np.array(rgbas) -# TODO: Move into GLVMobjectManager -def compute_bounding_box(mob): - all_points = np.vstack( - [ - mob.points, - *(m.get_bounding_box() for m in mob.get_family()[1:] if m.has_points()), - ], - ) - if len(all_points) == 0: - return np.zeros((3, mob.dim)) - else: - # Lower left and upper right corners - mins = all_points.min(0) - maxs = all_points.max(0) - mids = (mins + maxs) / 2 - return np.array([mins, mids, maxs]) - - class ProgramManager: @staticmethod def get_available_uniforms(prog): @@ -325,38 +235,6 @@ def init_camera(self, camera: Camera): ProgramManager.write_uniforms(self.vmobject_fill_program, uniforms) ProgramManager.write_uniforms(self.vmobject_stroke_program, uniforms) - # TODO: Move into GLVMobjectManager - def get_stroke_shader_data(self, mob: OpenGLVMobject) -> np.ndarray: - if not isinstance(mob.renderer_data, GLRenderData): - raise TypeError() - - points = mob.points - stroke_data = np.zeros(len(points), dtype=stroke_dtype) - - nppc = mob.n_points_per_curve - stroke_data["point"] = points - stroke_data["prev_point"][:nppc] = points[-nppc:] - stroke_data["prev_point"][nppc:] = points[:-nppc] - stroke_data["next_point"][:-nppc] = points[nppc:] - stroke_data["next_point"][-nppc:] = points[:nppc] - stroke_data["color"] = mob.renderer_data.stroke_rgbas - stroke_data["stroke_width"] = mob.renderer_data.stroke_widths.reshape((-1, 1)) - - return stroke_data - - # TODO: Move into GLVMobjectManager - def get_fill_shader_data(self, mob: OpenGLVMobject) -> np.ndarray: - if not isinstance(mob.renderer_data, GLRenderData): - raise TypeError() - - fill_data = np.zeros(len(mob.points), dtype=fill_dtype) - fill_data["point"] = mob.points - fill_data["color"] = mob.renderer_data.fill_rgbas - # fill_data["orientation"] = mob.renderer_data.orientation - fill_data["unit_normal"] = mob.renderer_data.normals - fill_data["vert_index"] = np.reshape(range(len(mob.points)), (-1, 1)) - return fill_data - def pre_render(self, camera): self.init_camera(camera=camera) self.ctx.clear() @@ -465,12 +343,12 @@ def enable_depth(mob): # uniforms['z_shift'] = counter/9 + 1/20 self.ctx.copy_framebuffer(self.stencil_texture_fbo, self.stencil_buffer_fbo) self.stencil_texture.use(0) - self.vmobject_stroke_program["stencil_texture"] = 0 + # self.vmobject_stroke_program["stencil_texture"] = 0 if sub.has_stroke(): ProgramManager.write_uniforms(self.vmobject_stroke_program, uniforms) self.render_program( self.vmobject_stroke_program, - self.get_stroke_shader_data(sub), + GLVMobjectManager.get_stroke_shader_data(sub), np.array(range(len(sub.points))), ) @@ -484,12 +362,12 @@ def enable_depth(mob): uniforms["disable_stencil"] = float(False) self.ctx.copy_framebuffer(self.stencil_texture_fbo, self.stencil_buffer_fbo) self.stencil_texture.use(0) - self.vmobject_fill_program["stencil_texture"] = 0 + # self.vmobject_fill_program["stencil_texture"] = 0 if sub.has_fill(): ProgramManager.write_uniforms(self.vmobject_fill_program, uniforms) self.render_program( self.vmobject_fill_program, - self.get_fill_shader_data(sub), + GLVMobjectManager.get_fill_shader_data(sub), sub.renderer_data.vert_indices, ) @@ -503,12 +381,12 @@ def enable_depth(mob): # uniforms['z_shift'] = counter/9 + 1/20 self.ctx.copy_framebuffer(self.stencil_texture_fbo, self.stencil_buffer_fbo) self.stencil_texture.use(0) - self.vmobject_stroke_program["stencil_texture"] = 0 + # self.vmobject_stroke_program["stencil_texture"] = 0 if sub.has_stroke(): ProgramManager.write_uniforms(self.vmobject_stroke_program, uniforms) self.render_program( self.vmobject_stroke_program, - self.get_stroke_shader_data(sub), + GLVMobjectManager.get_stroke_shader_data(sub), np.array(range(len(sub.points))), ) @@ -525,7 +403,7 @@ def init_render_data(mob: OpenGLVMobject): mob.renderer_data = GLRenderData() # Generate Mesh - mob.renderer_data.vert_indices = get_triangulation(mob) + mob.renderer_data.vert_indices = GLVMobjectManager.get_triangulation(mob) points_length = len(mob.points) # Generate Fill Color @@ -539,7 +417,7 @@ def init_render_data(mob: OpenGLVMobject): mob.renderer_data.normals = np.repeat( [mob.get_unit_normal()], points_length, axis=0 ) - mob.renderer_data.bounding_box = compute_bounding_box(mob) + mob.renderer_data.bounding_box = GLVMobjectManager.compute_bounding_box(mob) # print(mob.renderer_data) @staticmethod @@ -555,303 +433,103 @@ def read_uniforms(mob: OpenGLVMobject): uniforms["flat_stroke"] = float(mob.flat_stroke) return uniforms + @staticmethod + def get_stroke_shader_data(mob: OpenGLVMobject) -> np.ndarray: + if not isinstance(mob.renderer_data, GLRenderData): + raise TypeError() + + points = mob.points + stroke_data = np.zeros(len(points), dtype=stroke_dtype) + stroke_data["point"] = points + stroke_data["joint_product"] = mob.joint_product + stroke_data["color"] = mob.renderer_data.stroke_rgbas + stroke_data["stroke_width"] = mob.renderer_data.stroke_widths.reshape((-1, 1)) + + return stroke_data -# def init_frame(self, **config) -> None: -# self.frame = Camera(**config) - -# def init_context(self, ctx: moderngl.Context | None = None) -> None: -# if ctx is None: -# ctx = moderngl.create_standalone_context() -# fbo = self.get_fbo(ctx, 0) -# else: -# fbo = ctx.detect_framebuffer() - -# self.ctx = ctx -# self.fbo = fbo -# self.set_ctx_blending() - -# # For multisample antisampling -# fbo_msaa = self.get_fbo(ctx, self.samples) -# fbo_msaa.use() -# self.fbo_msaa = fbo_msaa - -# def set_ctx_blending(self, enable: bool = True) -> None: -# if enable: -# self.ctx.enable(moderngl.BLEND) -# else: -# self.ctx.disable(moderngl.BLEND) - -# def set_ctx_depth_test(self, enable: bool = True) -> None: -# if enable: -# self.ctx.enable(moderngl.DEPTH_TEST) -# else: -# self.ctx.disable(moderngl.DEPTH_TEST) - -# def init_light_source(self) -> None: -# self.light_source = OpenGLPoint(self.light_source_position) - -# # Methods associated with the frame buffer -# def get_fbo(self, ctx: moderngl.Context, samples: int = 0) -> moderngl.Framebuffer: -# pw = self.pixel_width -# ph = self.pixel_height -# return ctx.framebuffer( -# color_attachments=ctx.texture( -# (pw, ph), components=self.n_channels, samples=samples -# ), -# depth_attachment=ctx.depth_renderbuffer((pw, ph), samples=samples), -# ) - -# def clear(self) -> None: -# self.fbo.clear(*self.background_color) -# self.fbo_msaa.clear(*self.background_color) - -# def reset_pixel_shape(self, new_width: int, new_height: int) -> None: -# self.pixel_width = new_width -# self.pixel_height = new_height -# self.refresh_perspective_uniforms() - -# def get_raw_fbo_data(self, dtype: str = "f1") -> bytes: -# # Copy blocks from the fbo_msaa to the drawn fbo using Blit -# # pw, ph = (self.pixel_width, self.pixel_height) -# # gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, self.fbo_msaa.glo) -# # gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, self.fbo.glo) -# # gl.glBlitFramebuffer( -# # 0, 0, pw, ph, 0, 0, pw, ph, gl.GL_COLOR_BUFFER_BIT, gl.GL_LINEAR -# # ) - -# self.ctx.copy_framebuffer(self.fbo, self.fbo_msaa) -# return self.fbo.read( -# viewport=self.fbo.viewport, -# components=self.n_channels, -# dtype=dtype, -# ) - -# def get_image(self) -> Image.Image: -# return Image.frombytes( -# "RGBA", -# self.get_pixel_shape(), -# self.get_raw_fbo_data(), -# "raw", -# "RGBA", -# 0, -# -1, -# ) - -# def get_pixel_array(self) -> np.ndarray: -# raw = self.get_raw_fbo_data(dtype="f4") -# flat_arr = np.frombuffer(raw, dtype="f4") -# arr = flat_arr.reshape([*reversed(self.fbo.size), self.n_channels]) -# arr = arr[::-1] -# # Convert from float -# return (self.rgb_max_val * arr).astype(self.pixel_array_dtype) - -# def get_texture(self): -# texture = self.ctx.texture( -# size=self.fbo.size, components=4, data=self.get_raw_fbo_data(), dtype="f4" -# ) -# return texture - -# # Getting camera attributes -# def get_pixel_shape(self) -> tuple[int, int]: -# return self.fbo.viewport[2:4] -# # return (self.pixel_width, self.pixel_height) - -# def get_pixel_width(self) -> int: -# return self.get_pixel_shape()[0] - -# def get_pixel_height(self) -> int: -# return self.get_pixel_shape()[1] - -# def get_frame_height(self) -> float: -# return self.frame.get_height() - -# def get_frame_width(self) -> float: -# return self.frame.get_width() - -# def get_frame_shape(self) -> tuple[float, float]: -# return (self.get_frame_width(), self.get_frame_height()) - -# def get_frame_center(self) -> np.ndarray: -# return self.frame.get_center() - -# def get_location(self) -> tuple[float, float, float] | np.ndarray: -# return self.frame.get_implied_camera_location() - -# def resize_frame_shape(self, fixed_dimension: bool = False) -> None: -# """ -# Changes frame_shape to match the aspect ratio -# of the pixels, where fixed_dimension determines -# whether frame_height or frame_width -# remains fixed while the other changes accordingly. -# """ -# pixel_height = self.get_pixel_height() -# pixel_width = self.get_pixel_width() -# frame_height = self.get_frame_height() -# frame_width = self.get_frame_width() -# aspect_ratio = fdiv(pixel_width, pixel_height) -# if not fixed_dimension: -# frame_height = frame_width / aspect_ratio -# else: -# frame_width = aspect_ratio * frame_height -# self.frame.set_height(frame_height) -# self.frame.set_width(frame_width) - -# # Rendering -# def capture(self, *mobjects: OpenGLMobject) -> None: -# self.refresh_perspective_uniforms() -# for mobject in mobjects: -# for render_group in self.get_render_group_list(mobject): -# self.render(render_group) - -# def render(self, render_group: dict[str, Any]) -> None: -# shader_wrapper: ShaderWrapper = render_group["shader_wrapper"] -# shader_program = render_group["prog"] -# self.set_shader_uniforms(shader_program, shader_wrapper) -# self.set_ctx_depth_test(shader_wrapper.depth_test) -# render_group["vao"].render(int(shader_wrapper.render_primitive)) -# if render_group["single_use"]: -# self.release_render_group(render_group) - -# def get_render_group_list(self, mobject: OpenGLMobject) -> Iterable[dict[str, Any]]: -# if mobject.is_changing(): -# return self.generate_render_group_list(mobject) - -# # Otherwise, cache result for later use -# key = id(mobject) -# if key not in self.mob_to_render_groups: -# self.mob_to_render_groups[key] = list( -# self.generate_render_group_list(mobject) -# ) -# return self.mob_to_render_groups[key] - -# def generate_render_group_list( -# self, mobject: OpenGLMobject -# ) -> Iterable[dict[str, Any]]: -# return ( -# self.get_render_group(sw, single_use=mobject.is_changing()) -# for sw in mobject.get_shader_wrapper_list() -# ) - -# def get_render_group( -# self, shader_wrapper: ShaderWrapper, single_use: bool = True -# ) -> dict[str, Any]: -# # Data buffers -# vbo = self.ctx.buffer(shader_wrapper.vert_data.tobytes()) -# if shader_wrapper.vert_indices is None: -# ibo = None -# else: -# vert_index_data = shader_wrapper.vert_indices.astype("i4").tobytes() -# if vert_index_data: -# ibo = self.ctx.buffer(vert_index_data) -# else: -# ibo = None - -# # Program an vertex array -# shader_program, vert_format = self.get_shader_program(shader_wrapper) # type: ignore -# vao = self.ctx.vertex_array( -# program=shader_program, -# content=[(vbo, vert_format, *shader_wrapper.vert_attributes)], -# index_buffer=ibo, -# ) -# return { -# "vbo": vbo, -# "ibo": ibo, -# "vao": vao, -# "prog": shader_program, -# "shader_wrapper": shader_wrapper, -# "single_use": single_use, -# } - -# def release_render_group(self, render_group: dict[str, Any]) -> None: -# for key in ["vbo", "ibo", "vao"]: -# if render_group[key] is not None: -# render_group[key].release() - -# def refresh_static_mobjects(self) -> None: -# for render_group in it.chain(*self.mob_to_render_groups.values()): -# self.release_render_group(render_group) -# self.mob_to_render_groups = {} - -# # Shaders -# def init_shaders(self) -> None: -# # Initialize with the null id going to None -# self.id_to_shader_program: dict[int, tuple[moderngl.Program, str] | None] = { -# hash(""): None -# } - -# def get_shader_program( -# self, shader_wrapper: ShaderWrapper -# ) -> tuple[moderngl.Program, str] | None: -# sid = shader_wrapper.get_program_id() -# if sid not in self.id_to_shader_program: -# # Create shader program for the first time, then cache -# # in the id_to_shader_program dictionary -# program = self.ctx.program(**shader_wrapper.get_program_code()) -# vert_format = moderngl.detect_format( -# program, shader_wrapper.vert_attributes -# ) -# self.id_to_shader_program[sid] = (program, vert_format) - -# return self.id_to_shader_program[sid] - -# def set_shader_uniforms( -# self, -# shader: moderngl.Program, -# shader_wrapper: ShaderWrapper, -# ) -> None: -# for name, path in shader_wrapper.texture_paths.items(): -# tid = self.get_texture_id(path) -# shader[name].value = tid -# for name, value in it.chain( -# self.perspective_uniforms.items(), shader_wrapper.uniforms.items() -# ): -# if name in shader: -# if isinstance(value, np.ndarray) and value.ndim > 0: -# value = tuple(value) -# shader[name].value = value -# else: -# logger.debug(f"Uniform {name} not found in shader {shader}") - -# def refresh_perspective_uniforms(self) -> None: -# frame = self.frame -# # Orient light -# rotation = frame.get_inverse_camera_rotation_matrix() -# offset = frame.get_center() -# light_pos = np.dot(rotation, self.light_source.get_location() + offset) -# cam_pos = self.frame.get_implied_camera_location() # TODO - -# self.perspective_uniforms = { -# "frame_shape": frame.get_shape(), -# "pixel_shape": self.get_pixel_shape(), -# "camera_offset": tuple(offset), -# "camera_rotation": tuple(np.array(rotation).T.flatten()), -# "camera_position": tuple(cam_pos), -# "light_source_position": tuple(light_pos), -# "focal_distance": frame.get_focal_distance(), -# } - -# def init_textures(self) -> None: -# self.n_textures: int = 0 -# self.path_to_texture: dict[str, tuple[int, moderngl.Texture]] = {} - -# def get_texture_id(self, path: str) -> int: -# if path not in self.path_to_texture: -# if self.n_textures == 15: # I have no clue why this is needed -# self.n_textures += 1 -# tid = self.n_textures -# self.n_textures += 1 -# im = Image.open(path).convert("RGBA") -# texture = self.ctx.texture( -# size=im.size, -# components=len(im.getbands()), -# data=im.tobytes(), -# ) -# texture.use(location=tid) -# self.path_to_texture[path] = (tid, texture) -# return self.path_to_texture[path][0] - -# def release_texture(self, path: str): -# tid_and_texture = self.path_to_texture.pop(path, None) -# if tid_and_texture: -# tid_and_texture[1].release() -# return self + @staticmethod + def get_fill_shader_data(mob: OpenGLVMobject) -> np.ndarray: + if not isinstance(mob.renderer_data, GLRenderData): + raise TypeError() + + fill_data = np.zeros(len(mob.points), dtype=fill_dtype) + fill_data["point"] = mob.points + fill_data["color"] = mob.renderer_data.fill_rgbas + # fill_data["orientation"] = mob.renderer_data.orientation + fill_data["unit_normal"] = mob.renderer_data.normals + return fill_data + + @staticmethod + def get_triangulation(mob: OpenGLVMobject, normal_vector=None): + # 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 + if normal_vector is None: + normal_vector = mob.get_unit_normal() + + points = mob.points + + if len(points) <= 1: + mob.triangulation = np.zeros(0, dtype="i4") + mob.needs_new_triangulation = False + return mob.triangulation + + if not np.isclose(normal_vector, const.OUT).all(): + # Rotate points such that unit normal vector is OUT + points = np.dot(points, z_to_vector(normal_vector)) + indices = np.arange(len(points), dtype=int) + + 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) + + 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 + + concave_parts = convexities < 0 + + # These are the vertices to which we'll apply a polygon triangulation + 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) + ] + + tri_indices = np.hstack([indices, inner_tri_indices]) + mob.triangulation = tri_indices + mob.needs_new_triangulation = False + return tri_indices + + @staticmethod + def compute_bounding_box(mob): + all_points = np.vstack( + [ + mob.points, + *(m.get_bounding_box() for m in mob.get_family()[1:] if m.has_points()), + ], + ) + if len(all_points) == 0: + return np.zeros((3, mob.dim)) + else: + # Lower left and upper right corners + mins = all_points.min(0) + maxs = all_points.max(0) + mids = (mins + maxs) / 2 + return np.array([mins, mids, maxs]) diff --git a/manim/renderer/opengl_shader_program.py b/manim/renderer/opengl_shader_program.py index d030f54b61..fa529cc13f 100644 --- a/manim/renderer/opengl_shader_program.py +++ b/manim/renderer/opengl_shader_program.py @@ -75,9 +75,9 @@ def load_shader_program_by_folder(ctx: gl.Context, folder_name: str): vertex_code = get_shader_code_from_file(Path(folder_name + "/vert.glsl")) geometry_code = get_shader_code_from_file(Path(folder_name + "/geom.glsl")) fragment_code = get_shader_code_from_file(Path(folder_name + "/frag.glsl")) - # print(folder_name) - # for i,l in enumerate(geometry_code.splitlines()): - # print(str(i) + ":" + l ) + print(folder_name) + # for i, l in enumerate(geometry_code.splitlines()): + # print(str(i) + ":" + l) if vertex_code is None or fragment_code is None: logger.error( f"Invalid program definition for {folder_name} vertex or fragment shader not present" diff --git a/manim/renderer/renderer.py b/manim/renderer/renderer.py index 82c4e5a28e..768b5809d8 100644 --- a/manim/renderer/renderer.py +++ b/manim/renderer/renderer.py @@ -1,6 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod +from dataclasses import dataclass from typing import TYPE_CHECKING, Protocol import numpy as np @@ -21,6 +22,7 @@ ImageType: TypeAlias = np.ndarray +@dataclass class RendererData: pass diff --git a/manim/renderer/shader_wrapper.py b/manim/renderer/shader_wrapper.py index 21b8592cb6..23b4e62cf3 100644 --- a/manim/renderer/shader_wrapper.py +++ b/manim/renderer/shader_wrapper.py @@ -1,3 +1,6 @@ +######################################### +# THIS FILE IS NOT USED IN EXPERIMENTAL # +######################################### from __future__ import annotations import copy diff --git a/manim/renderer/shaders/image/vert.glsl b/manim/renderer/shaders/image/vert.glsl index aaaae6bff7..9eac201f19 100644 --- a/manim/renderer/shaders/image/vert.glsl +++ b/manim/renderer/shaders/image/vert.glsl @@ -1,7 +1,5 @@ #version 330 -#include ../include/camera_uniform_declarations.glsl - uniform sampler2D Texture; in vec3 point; @@ -15,7 +13,7 @@ out float v_opacity; #include ../include/get_gl_Position.glsl #include ../include/position_point_into_frame.glsl -void main(){ +void main() { v_im_coords = im_coords; v_opacity = opacity; gl_Position = get_gl_Position(position_point_into_frame(point)); diff --git a/manim/renderer/shaders/include/camera_uniform_declarations.glsl b/manim/renderer/shaders/include/camera_uniform_declarations.glsl deleted file mode 100644 index 53b38ae12f..0000000000 --- a/manim/renderer/shaders/include/camera_uniform_declarations.glsl +++ /dev/null @@ -1,10 +0,0 @@ -layout (std140) uniform ubo_camera { - // mat4 u_projection_view_matrix; # TODO: convert to mat4 instead of the following... - vec2 frame_shape; - vec3 camera_center; - mat3 camera_rotation; - float focal_distance; - float is_fixed_in_frame; - float is_fixed_orientation; - vec3 fixed_orientation_center; -}; diff --git a/manim/renderer/shaders/include/finalize_color.glsl b/manim/renderer/shaders/include/finalize_color.glsl index a913f785a2..73f0555286 100644 --- a/manim/renderer/shaders/include/finalize_color.glsl +++ b/manim/renderer/shaders/include/finalize_color.glsl @@ -1,4 +1,8 @@ -vec3 float_to_color(float value, float min_val, float max_val, vec3[9] colormap_data){ +uniform vec3 light_position; +uniform vec3 camera_position; +uniform vec3 shading; + +vec3 float_to_color(float value, float min_val, float max_val, vec3[9] colormap_data) { float alpha = clamp((value - min_val) / (max_val - min_val), 0.0, 1.0); int disc_alpha = min(int(alpha * 8), 7); return mix( @@ -8,45 +12,41 @@ vec3 float_to_color(float value, float min_val, float max_val, vec3[9] colormap_ ); } +vec4 add_light(vec4 color, vec3 point, vec3 unit_normal) { + if (shading == vec3(0.0)) return color; -vec4 add_light(vec4 color, - vec3 point, - vec3 unit_normal, - vec3 light_coords, - float gloss, - float shadow){ - if (gloss == 0.0 && shadow == 0.0 && reflectiveness == 0.0) - return color; + float reflectiveness = shading.x; + float gloss = shading.y; + float shadow = shading.z; - // TODO, do we actually want this? It effectively treats surfaces as two-sided - if(unit_normal.z < 0){ - unit_normal *= -1; - } + vec4 result = color; + vec3 to_camera = normalize(camera_position - point); + vec3 to_light = normalize(light_position - point); - // TODO, read this in as a uniform? - float camera_distance = 6; - // Assume everything has already been rotated such that camera is in the z-direction - vec3 to_camera = vec3(0, 0, camera_distance) - point; - vec3 to_light = light_coords - point; - vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal); - float dot_prod = dot(normalize(light_reflection), normalize(to_camera)); - float shine = gloss * exp(-3 * pow(1 - dot_prod, 2)); - float dp2 = dot(normalize(to_light), unit_normal); - float darkening = mix(1, max(dp2, 0), shadow); - return vec4( - darkening * mix(color.rgb, vec3(1.0), shine), - color.a - ); + float light_to_normal = dot(to_light, unit_normal); + // When unit normal points towards light, brighten + float bright_factor = max(light_to_normal, 0) * reflectiveness; + // For glossy surface, add extra shine if light beam go towards camera + vec3 light_reflection = reflect(-to_light, unit_normal); + float light_to_cam = dot(light_reflection, to_camera); + float shine = gloss * exp(-3 * pow(1 - light_to_cam, 2)); + bright_factor += shine; + + result.rgb = mix(result.rgb, vec3(1.0), bright_factor); + if (light_to_normal < 0) { + // Darken + result.rgb = mix( + result.rgb, + vec3(0.0), + max(-light_to_normal, 0) * shadow + ); + } + return result; } -vec4 finalize_color(vec4 color, - vec3 point, - vec3 unit_normal, - vec3 light_coords, - float gloss, - float shadow){ +vec4 finalize_color(vec4 color, vec3 point, vec3 unit_normal) { ///// INSERT COLOR FUNCTION HERE ///// // The line above may be replaced by arbitrary code snippets, as per // the method Mobject.set_color_by_code - return add_light(color, point, unit_normal, light_coords, gloss, shadow); + return add_light(color, point, unit_normal); } diff --git a/manim/renderer/shaders/include/get_gl_Position.glsl b/manim/renderer/shaders/include/get_gl_Position.glsl index cd71b4c5a4..684220002a 100644 --- a/manim/renderer/shaders/include/get_gl_Position.glsl +++ b/manim/renderer/shaders/include/get_gl_Position.glsl @@ -1,38 +1,29 @@ -// Assumes the following uniforms exist in the surrounding context: -// uniform vec2 frame_shape; -// uniform float focal_distance; -// uniform float is_fixed_in_frame; -// uniform float z_shift; -const vec2 DEFAULT_FRAME_SHAPE = vec2(8.0 * 16.0 / 9.0, 8.0); +layout(std140) uniform ubo_camera { + mat4 view; + vec2 frame_shape; + vec3 camera_center; + mat3 camera_rotation; + float focal_distance; + float is_fixed_in_frame; + float is_fixed_orientation; + vec3 fixed_orientation_center; +}; -float perspective_scale_factor(float z, float focal_distance) -{ - return max(0.0, focal_distance / (focal_distance - z)); -} +const float DEFAULT_FRAME_HEIGHT = 8.0; +const float ASPECT_RATIO = 16.0 / 9.0; +const float X_SCALE = 2.0 / DEFAULT_FRAME_HEIGHT / ASPECT_RATIO; +const float Y_SCALE = 2.0 / DEFAULT_FRAME_HEIGHT; -vec4 get_gl_Position(vec3 point) -{ +void emit_gl_Position(vec3 point) { vec4 result = vec4(point, 1.0); - if (!bool(is_fixed_in_frame)) - { - result.x *= 2.0 / frame_shape.x; - result.y *= 2.0 / frame_shape.y; - float psf = perspective_scale_factor(result.z, focal_distance); - if (psf > 0) - { - result.xy *= psf; - // TODO, what's the better way to do this? - // This is to keep vertices too far out of frame from getting cut. - // TODO This will be done by the clipping plane in the future with the transformation matrix - // result.z += z_shift; - result.z *= (1.0 / 100.0); - } - } - else - { - result.x *= 2.0 / DEFAULT_FRAME_SHAPE.x; - result.y *= 2.0 / DEFAULT_FRAME_SHAPE.y; - } - result.z *= -1; - return result; + // This allow for smooth transitions between objects fixed and unfixed from frame + result = mix(view * result, result, is_fixed_in_frame); + // Essentially a projection matrix + result.x *= X_SCALE; + result.y *= Y_SCALE; + result.z /= focal_distance; + result.w = 1.0 - result.z; + // Flip and scale to prevent premature clipping + result.z *= -0.1; + gl_Position = result; } diff --git a/manim/renderer/shaders/quadratic_bezier_fill/frag.glsl b/manim/renderer/shaders/quadratic_bezier_fill/frag.glsl index e6defdef1a..b992db9e0d 100644 --- a/manim/renderer/shaders/quadratic_bezier_fill/frag.glsl +++ b/manim/renderer/shaders/quadratic_bezier_fill/frag.glsl @@ -1,71 +1,43 @@ #version 330 -#include ../include/camera_uniform_declarations.glsl -uniform vec2 pixel_shape; -uniform float index; +uniform bool winding; in vec4 color; -in float fill_all; // Either 0 or 1e -in float uv_anti_alias_width; - +in float fill_all; in float orientation; in vec2 uv_coords; -in float bezier_degree; - -uniform sampler2D stencil_texture; - -layout(location = 0) out vec4 frag_color; -layout(location = 1) out vec4 stencil_value; - -#define ANTI_ALIASING - -float sdf(){ - if(bezier_degree < 2){ - return abs(uv_coords[1]); - } - 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 -} +out vec4 frag_color; void main() { - gl_FragDepth = gl_FragCoord.z; if (color.a == 0) discard; - - float previous_index = - texture2D(stencil_texture, vec2(gl_FragCoord.x / pixel_shape.x, gl_FragCoord.y / pixel_shape.y)).r; - - // Check if we are behind another fill and if yes discard the current fragment - if (previous_index > index) - { - discard; - } - // If we are on top of a previously drawn fill we need to shift ourselves forward by the index amount to compensate - // for different shifting and avoid z_fighting - if (previous_index < index && previous_index != 0) - { - gl_FragDepth = gl_FragCoord.z - index / 1000.0; - } - stencil_value.rgb = vec3(index); - stencil_value.a = 1.0; frag_color = color; - if (fill_all == 1.0) return; -#ifdef ANTI_ALIASING - float fac = max(0.0, min(1.0, 0.5 - sdf())); - frag_color.a *= fac; // Anti-aliasing -#endif -#ifndef ANTI_ALIASING - frag_color.a *= float(sdf() > 0); // No anti-aliasing -#endif - if (frag_color.a <= 0.0) - { - discard; + // We want negatively oriented triangles to be canceled with positively + // oriented ones. The easiest way to do this is to give them negative alpha, + // and change the blend function to just add them. However, this messes with + // usual blending, so instead the following line is meant to let this canceling + // work even for the normal blending equation: + + // (1 - alpha) * dst + alpha * src + + // We want the effect of blending with a positively oriented triangle followed + // by a negatively oriented one to return to whatever the original frag value + // was. You can work out this will work if the alpha for negative orientations + // is changed to -alpha / (1 - alpha). This has a singularity at alpha = 1, + // so we cap it at a value very close to 1. Effectively, the purpose of this + // cap is to make sure the original fragment color can be recovered even after + // blending with an (alpha = 1) color. + if (winding) { + float a = 0.95 * frag_color.a; + if (orientation < 0) a = -a / (1 - a); + frag_color.a = a; } + + if (bool(fill_all)) return; + + float x = uv_coords.x; + float y = uv_coords.y; + float Fxy = (y - x * x); + if (!winding && orientation < 0) Fxy *= -1; + if (Fxy < 0) discard; } diff --git a/manim/renderer/shaders/quadratic_bezier_fill/geom.glsl b/manim/renderer/shaders/quadratic_bezier_fill/geom.glsl index 0f3bc27dcb..56679e354f 100644 --- a/manim/renderer/shaders/quadratic_bezier_fill/geom.glsl +++ b/manim/renderer/shaders/quadratic_bezier_fill/geom.glsl @@ -1,79 +1,81 @@ #version 330 -layout (triangles) in; -layout (triangle_strip, max_vertices = 5) out; +layout(triangles) in; +layout(triangle_strip, max_vertices = 6) out; -uniform float anti_alias_width; +uniform bool winding; -// Needed for get_gl_Position -// uniform vec2 frame_shape; -// uniform float focal_distance; -// uniform float is_fixed_in_frame; -// uniform float is_fixed_orientation; -// uniform vec3 fixed_orientation_center; -// Needed for finalize_color -uniform vec3 light_source_position; -uniform float gloss; -uniform float shadow; -uniform float reflectiveness; - -in vec3 bp[3]; -in vec3 v_global_unit_normal[3]; +in vec3 verts[3]; in vec4 v_color[3]; +in vec3 v_base_point[3]; in float v_vert_index[3]; +in vec3 v_unit_normal[3]; out vec4 color; out float fill_all; - out float orientation; +// uv space is where the curve coincides with y = x^2 out vec2 uv_coords; -out float bezier_degree; -// Analog of import for manim only -#include ../include/camera_uniform_declarations.glsl +// A quadratic bezier curve with these points coincides with y = x^2 +const vec2 SIMPLE_QUADRATIC[3] = vec2[3]( + vec2(0.0, 0.0), + vec2(0.5, 0), + vec2(1.0, 1.0) + ); + #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_triangle(vec3 points[3], vec4 v_color[3]) { + vec3 unit_normal = v_unit_normal[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); - gl_Position = get_gl_Position(point); - uv_coords = uv_coords_arr[index]; - EmitVertex(); -} + orientation = sign(determinant(mat3( + unit_normal, + points[1] - points[0], + points[2] - points[0] + ))); -void emit_simple_triangle() -{ - for(int i = 0; i < 3; i++) - { - emit_vertex_wrapper(bp[i], i); + for (int i = 0; i < 3; i++) { + uv_coords = SIMPLE_QUADRATIC[i]; + color = finalize_color(v_color[i], points[i], unit_normal); + emit_gl_Position(points[i]); + EmitVertex(); } 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 +void emit_simple_triangle() { + emit_triangle( + vec3[3](verts[0], verts[1], verts[2]), + vec4[3](v_color[0], v_color[1], v_color[2]) ); +} - if(fill_all == 1.0){ - emit_simple_triangle(); - return; - } - - vec3 new_bp[3]; - bezier_degree = get_reduced_control_points(vec3[3](bp[0], bp[1], bp[2]), new_bp); - vec3 local_unit_normal = get_unit_normal(new_bp); - orientation = sign(dot(v_global_unit_normal[0], local_unit_normal)); +void main() { + // Curves are marked as ended when the handle after + // the first anchor is set equal to that anchor + if (verts[0] == verts[1]) return; - if(bezier_degree >= 1){ + if (winding) { + // Emit main triangle + fill_all = 1.0; + emit_triangle( + vec3[3](v_base_point[0], verts[0], verts[2]), + vec4[3](v_color[1], v_color[0], v_color[2]) + ); + // Edge triangle + fill_all = 0.0; + emit_simple_triangle(); + } else { + // In this case, one should fill all if the vertices are + // not in sequential order + fill_all = float( + (v_vert_index[1] - v_vert_index[0]) != 1.0 || + (v_vert_index[2] - v_vert_index[1]) != 1.0 + ); emit_simple_triangle(); } - // Don't emit any vertices for bezier_degree 0 } diff --git a/manim/renderer/shaders/quadratic_bezier_fill/vert.glsl b/manim/renderer/shaders/quadratic_bezier_fill/vert.glsl index 6b182a946d..38dc412ff2 100644 --- a/manim/renderer/shaders/quadratic_bezier_fill/vert.glsl +++ b/manim/renderer/shaders/quadratic_bezier_fill/vert.glsl @@ -1,23 +1,20 @@ #version 330 -#include ../include/camera_uniform_declarations.glsl - in vec3 point; -in vec3 unit_normal; in vec4 color; -in float vert_index; +in vec3 base_point; +in vec3 unit_normal; -out vec3 bp; // Bezier control point +out vec3 verts; // Bezier control point out vec4 v_color; +out vec3 v_base_point; +out vec3 v_unit_normal; out float v_vert_index; -out vec3 v_global_unit_normal; - -// Analog of import for manim only -#include ../include/position_point_into_frame.glsl -void main(){ - bp = position_point_into_frame(point.xyz); - v_global_unit_normal = rotate_point_into_frame(unit_normal); +void main() { + verts = point; v_color = color; - v_vert_index = vert_index; + v_base_point = base_point; + v_unit_normal = unit_normal; + v_vert_index = gl_VertexID; } diff --git a/manim/renderer/shaders/quadratic_bezier_stroke/frag.glsl b/manim/renderer/shaders/quadratic_bezier_stroke/frag.glsl index 9149fa700a..5db8e0e56f 100644 --- a/manim/renderer/shaders/quadratic_bezier_stroke/frag.glsl +++ b/manim/renderer/shaders/quadratic_bezier_stroke/frag.glsl @@ -1,138 +1,65 @@ #version 330 -#include ../include/camera_uniform_declarations.glsl -uniform vec2 pixel_shape; -uniform float index; -uniform float disable_stencil; - in vec2 uv_coords; -in vec2 uv_b2; in float uv_stroke_width; -in vec4 color; in float uv_anti_alias_width; +in vec4 color; -in float has_prev; -in float has_next; -in float bevel_start; -in float bevel_end; -in float angle_from_prev; -in float angle_to_next; -in float bezier_degree; - -uniform sampler2D stencil_texture; - - -layout(location = 0) out vec4 frag_color; -layout(location = 1) out vec4 stencil_value; - -float cross2d(vec2 v, vec2 w){ - return v.x * w.y - w.x * v.y; -} - - -float modify_distance_for_endpoints(vec2 p, float dist, float t){ - float buff = 0.5 * uv_stroke_width - uv_anti_alias_width; - // Check the beginning of the curve - if(t == 0){ - // Clip the start - if(has_prev == 0) return max(dist, -p.x + buff); - // Bevel start - if(bevel_start == 1){ - float a = angle_from_prev; - mat2 rot = mat2( - cos(a), sin(a), - -sin(a), cos(a) - ); - // Dist for intersection of two lines - float bevel_d = max(abs(p.y), abs((rot * p).y)); - // Dist for union of this intersection with the real curve - // intersected with radius 2 away from curve to smooth out - // really sharp corners - return max(min(dist, bevel_d), dist / 2); - } - // Otherwise, start will be rounded off - }else if(t == 1){ - // Check the end of the curve - // TODO, too much code repetition - vec2 v21 = (bezier_degree == 2) ? vec2(1, 0) - uv_b2 : vec2(-1, 0); - float len_v21 = length(v21); - if(len_v21 == 0){ - v21 = -uv_b2; - len_v21 = length(v21); - } - - float perp_dist = dot(p - uv_b2, v21) / len_v21; - if(has_next == 0) return max(dist, -perp_dist + buff); - // Bevel end - if(bevel_end == 1){ - float a = -angle_to_next; - mat2 rot = mat2( - cos(a), sin(a), - -sin(a), cos(a) - ); - vec2 v21_unit = v21 / length(v21); - float bevel_d = max( - abs(cross2d(p - uv_b2, v21_unit)), - abs(cross2d((rot * (p - uv_b2)), v21_unit)) - ); - return max(min(dist, bevel_d), dist / 2); - } - // Otherwise, end will be rounded off - } - return dist; +in float is_linear; + +out vec4 frag_color; + +const float QUICK_DIST_WIDTH = 0.2; + +float dist_to_curve() { + // In the linear case, the curve will have + // been set to equal the x axis + if (bool(is_linear)) return abs(uv_coords.y); + + // Otherwise, find the distance from uv_coords to the curve y = x^2 + float x0 = uv_coords.x; + float y0 = uv_coords.y; + + // This is a quick approximation for computing + // the distance to the curve. + // Evaluate F(x, y) = y - x^2 + // divide by its gradient's magnitude + float Fxy = y0 - x0 * x0; + float approx_dist = abs(Fxy) * inversesqrt(1.0 + 4 * x0 * x0); + if (approx_dist < QUICK_DIST_WIDTH) return approx_dist; + + // Otherwise, solve for the minimal distance. + // The distance squared between (x0, y0) and a point (x, x^2) looks like + // + // (x0 - x)^2 + (y0 - x^2)^2 = x^4 + (1 - 2y0)x^2 - 2x0 * x + (x0^2 + y0^2) + // + // Setting the derivative equal to zero (and rescaling) looks like + // + // x^3 + (0.5 - y0) * x - 0.5 * x0 = 0 + // + // Adapted from https://www.shadertoy.com/view/ws3GD7 + x0 = abs(x0); + float p = (0.5 - y0) / 3.0; // p / 3 in usual Cardano's formula notation + float q = 0.25 * x0; // -q / 2 in usual Cardano's formula notation + float disc = q * q + p * p * p; + float r = sqrt(abs(disc)); + + float x = (disc > 0.0) ? + // 1 root + pow(q + r, 1.0 / 3.0) + pow(abs(q - r), 1.0 / 3.0) * sign(-p) : + // 3 roots + 2.0 * cos(atan(r, q) / 3.0) * sqrt(-p); + + return length(vec2(x0 - x, y0 - x * x)); } - -#include ../include/quadratic_bezier_distance.glsl - - void main() { - // Use the default value as standard output - if(disable_stencil==1.0){ - stencil_value = vec4(0.0); - }else{ - stencil_value.rgb = vec3(index); - stencil_value.a = 1.0; - } - gl_FragDepth = gl_FragCoord.z; - // Get the previous index that was written to this fragment - float previous_index = - texture2D(stencil_texture, vec2(gl_FragCoord.x / pixel_shape.x, gl_FragCoord.y / pixel_shape.y)).r; - // If the index is the same that means we are overlapping with the fill and crossing through so we push the stroke - // forward a tiny bit - if (previous_index < index && previous_index != 0) - { - gl_FragDepth = gl_FragCoord.z - 1.7 * index / 1000.0; - } - if (previous_index == index) - { - gl_FragDepth = gl_FragCoord.z - index / 1000.0; - } - // If the stroke is overlapping with a shape that is of higher index that means it is behind another mobject on the - // same plane so we discard the fragment - if (previous_index > index) - { - // But for stroke transparency we shouldn't discard but move the stroke in front so it is not discarded by the depth test - // TODO: This is highly experimental and should later be rethought and if no good solution is found it should just be a discard; - if (color.a == 1.0) - discard; - else - gl_FragDepth = gl_FragCoord.z + index / 1000.0; - } - if(disable_stencil==1.0){ - gl_FragDepth = gl_FragCoord.z + 4.5 * index / 1000.0; - } - if (uv_stroke_width == 0) - discard; - float dist_to_curve = min_dist_to_curve(uv_coords, uv_b2, bezier_degree); - // An sdf for the region around the curve we wish to color. - float signed_dist = abs(dist_to_curve) - 0.5 * uv_stroke_width; - + if (uv_stroke_width == 0) discard; frag_color = color; + + // sdf for the region around the curve we wish to color. + float signed_dist = dist_to_curve() - 0.5 * uv_stroke_width; + frag_color.a *= smoothstep(0.5, -0.5, signed_dist / uv_anti_alias_width); - if (frag_color.a <= 0.0) - { - discard; - } } diff --git a/manim/renderer/shaders/quadratic_bezier_stroke/geom.glsl b/manim/renderer/shaders/quadratic_bezier_stroke/geom.glsl index 164e479db3..f73114909d 100644 --- a/manim/renderer/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manim/renderer/shaders/quadratic_bezier_stroke/geom.glsl @@ -1,273 +1,204 @@ #version 330 -layout (triangles) in; -layout (triangle_strip, max_vertices = 5) out; - -// Needed for get_gl_Position -// uniform vec2 frame_shape; -// uniform float focal_distance; -// uniform vec3 fixed_orientation_center; +layout(triangles) in; +layout(triangle_strip, max_vertices = 6) out; uniform float anti_alias_width; uniform float flat_stroke; - -//Needed for lighting -uniform vec3 light_source_position; -uniform float gloss; -uniform float shadow; +uniform float pixel_size; uniform float joint_type; -uniform float reflectiveness; -// uniform float is_fixed_in_frame; -// uniform float is_fixed_orientation; -in vec3 bp[3]; -in vec3 prev_bp[3]; -in vec3 next_bp[3]; -in vec3 v_global_unit_normal[3]; +in vec3 verts[3]; -in vec4 v_color[3]; +in vec4 v_joint_product[3]; in float v_stroke_width[3]; +in vec4 v_color[3]; out vec4 color; out float uv_stroke_width; out float uv_anti_alias_width; -out float has_prev; -out float has_next; -out float bevel_start; -out float bevel_end; -out float angle_from_prev; -out float angle_to_next; - -out float bezier_degree; +out float is_linear; out vec2 uv_coords; -out vec2 uv_b2; // Codes for joint types -const float AUTO_JOINT = 0; -const float ROUND_JOINT = 1; -const float BEVEL_JOINT = 2; -const float MITER_JOINT = 3; -const float PI = 3.141592653; - - - -#include ../include/camera_uniform_declarations.glsl -#include ../include/quadratic_bezier_geometry_functions.glsl -#include ../include/get_gl_Position.glsl -#include ../include/get_unit_normal.glsl -#include ../include/finalize_color.glsl - - -void flatten_points(in vec3[3] points, out vec2[3] flat_points){ - for(int i = 0; i < 3; i++){ - float sf = perspective_scale_factor(points[i].z, focal_distance); - flat_points[i] = sf * points[i].xy; - } +const int NO_JOINT = 0; +const int AUTO_JOINT = 1; +const int BEVEL_JOINT = 2; +const int MITER_JOINT = 3; + +// When the cosine of the angle between +// two vectors is larger than this, we +// consider them aligned +const float COS_THRESHOLD = 0.99; + +vec3 unit_normal = vec3(0.0, 0.0, 1.0); + +#include "../include/get_gl_Position.glsl" +#include "../include/get_xyz_to_uv.glsl" +#include "../include/finalize_color.glsl" + +vec3 get_joint_unit_normal(vec4 joint_product) { + vec3 result = (joint_product.w < COS_THRESHOLD) ? + joint_product.xyz : v_joint_product[1].xyz; + float norm = length(result); + return (norm > 1e-5) ? result / norm : vec3(0.0, 0.0, 1.0); } - -float angle_between_vectors(vec2 v1, vec2 v2){ - float v1_norm = length(v1); - float v2_norm = length(v2); - if(v1_norm == 0 || v2_norm == 0) return 0.0; - float dp = dot(v1, v2) / (v1_norm * v2_norm); - float angle = acos(clamp(dp, -1.0, 1.0)); - float sn = sign(cross2d(v1, v2)); - return sn * angle; -} - - -bool find_intersection(vec2 p0, vec2 v0, vec2 p1, vec2 v1, out vec2 intersection){ - // Find the intersection of a line passing through - // p0 in the direction v0 and one passing through p1 in - // the direction p1. - // That is, find a solutoin to p0 + v0 * t = p1 + v1 * s - float det = -v0.x * v1.y + v1.x * v0.y; - if(det == 0) return false; - float t = cross2d(p0 - p1, v1) / det; - intersection = p0 + v0 * t; - return true; +vec4 normalized_joint_product(vec4 joint_product) { + float norm = length(joint_product); + return (norm > 1e-10) ? joint_product / norm : vec4(0.0, 0.0, 0.0, 1.0); } +void create_joint( + vec4 joint_product, + vec3 unit_tan, + float buff, + vec3 static_c0, + out vec3 changing_c0, + vec3 static_c1, + out vec3 changing_c1 +) { + float cos_angle = joint_product.w; + if (abs(cos_angle) > COS_THRESHOLD || int(joint_type) == NO_JOINT) { + // No joint + changing_c0 = static_c0; + changing_c1 = static_c1; + return; + } -void create_joint(float angle, vec2 unit_tan, float buff, - vec2 static_c0, out vec2 changing_c0, - vec2 static_c1, out vec2 changing_c1){ float shift; - if(abs(angle) < 1e-3){ - // No joint - shift = 0; - }else if(joint_type == MITER_JOINT){ - shift = buff * (-1.0 - cos(angle)) / sin(angle); - }else{ + float sin_angle = length(joint_product.xyz) * sign(joint_product.z); + if (int(joint_type) == MITER_JOINT) { + shift = buff * (-1.0 - cos_angle) / sin_angle; + } else { // For a Bevel joint - shift = buff * (1.0 - cos(angle)) / sin(angle); + shift = buff * (1.0 - cos_angle) / sin_angle; } changing_c0 = static_c0 - shift * unit_tan; changing_c1 = static_c1 + shift * unit_tan; } +vec3 get_perp(int index, vec4 joint_product, vec3 point, vec3 tangent, float aaw) { + /* + Perpendicular vectors to the left of the curve + */ + float buff = 0.5 * v_stroke_width[index] + aaw; + // Add correction for sharp angles to prevent weird bevel effects + if (joint_product.w < -0.75) buff *= 4 * (joint_product.w + 1.0); + vec3 normal = get_joint_unit_normal(joint_product); + // Set global unit normal + unit_normal = normal; + // Choose the "outward" normal direction + if (normal.z < 0) normal *= -1; + if (bool(flat_stroke)) { + return buff * normalize(cross(normal, tangent)); + } else { + return buff * normalize(cross(camera_position - point, tangent)); + } +} // This function is responsible for finding the corners of // a bounding region around the bezier curve, which can be -// emitted as a triangle fan -int get_corners(vec2 controls[3], int degree, float stroke_widths[3], out vec2 corners[5]){ - vec2 p0 = controls[0]; - vec2 p1 = controls[1]; - vec2 p2 = controls[2]; - - // Unit vectors for directions between control points - vec2 v10 = normalize(p0 - p1); - vec2 v12 = normalize(p2 - p1); - vec2 v01 = -v10; - vec2 v21 = -v12; - - vec2 p0_perp = vec2(-v01.y, v01.x); // Pointing to the left of the curve from p0 - vec2 p2_perp = vec2(-v12.y, v12.x); // Pointing to the left of the curve from p2 - - // aaw is the added width given around the polygon for antialiasing. - // In case the normal is faced away from (0, 0, 1), the vector to the - // camera, this is scaled up. - float aaw = anti_alias_width; - float buff0 = 0.5 * stroke_widths[0] + aaw; - float buff2 = 0.5 * stroke_widths[2] + aaw; - float aaw0 = (1 - has_prev) * aaw; - float aaw2 = (1 - has_next) * aaw; +// emitted as a triangle fan, with vertices vaguely close +// to control points so that the passage of vert data to +// frag shaders is most natural. +void get_corners( + // Control points for a bezier curve + vec3 p0, + vec3 p1, + vec3 p2, + // Unit tangent vectors at p0 and p2 + vec3 v01, + vec3 v12, + // Anti-alias width + float aaw, + out vec3 corners[6] +) { + bool linear = bool(is_linear); + vec4 jp0 = normalized_joint_product(v_joint_product[0]); + vec4 jp2 = normalized_joint_product(v_joint_product[2]); + vec3 p0_perp = get_perp(0, jp0, p0, v01, aaw); + vec3 p2_perp = get_perp(2, jp2, p2, v12, aaw); + vec3 p1_perp = 0.5 * (p0_perp + p2_perp); + if (linear) { + p1_perp *= (0.5 * v_stroke_width[1] + aaw) / length(p1_perp); + } - vec2 c0 = p0 - buff0 * p0_perp + aaw0 * v10; - vec2 c1 = p0 + buff0 * p0_perp + aaw0 * v10; - vec2 c2 = p2 + buff2 * p2_perp + aaw2 * v12; - vec2 c3 = p2 - buff2 * p2_perp + aaw2 * v12; + // The order of corners should be for a triangle_strip. + vec3 c0 = p0 + p0_perp; + vec3 c1 = p0 - p0_perp; + vec3 c2 = p1 + p1_perp; + vec3 c3 = p1 - p1_perp; + vec3 c4 = p2 + p2_perp; + vec3 c5 = p2 - p2_perp; + // Move the inner middle control point to make + // room for the curve + // float orientation = dot(unit_normal, v_joint_product[1].xyz); + float orientation = v_joint_product[1].z; + if (!linear && orientation >= 0.0) c2 = 0.5 * (c0 + c4); + else if (!linear && orientation < 0.0) c3 = 0.5 * (c1 + c5); // Account for previous and next control points - if(has_prev > 0) create_joint(angle_from_prev, v01, buff0, c0, c0, c1, c1); - if(has_next > 0) create_joint(angle_to_next, v21, buff2, c3, c3, c2, c2); - - // Linear case is the simplest - if(degree == 1){ - // The order of corners should be for a triangle_strip. Last entry is a dummy - corners = vec2[5](c0, c1, c3, c2, vec2(0.0)); - return 4; + if (bool(flat_stroke)) { + create_joint(jp0, v01, length(p0_perp), c1, c1, c0, c0); + create_joint(jp2, -v12, length(p2_perp), c5, c5, c4, c4); } - // Otherwise, form a pentagon around the curve - float orientation = sign(cross2d(v01, v12)); // Positive for ccw curves - if(orientation > 0) corners = vec2[5](c0, c1, p1, c2, c3); - else corners = vec2[5](c1, c0, p1, c3, c2); - // Replace corner[2] with convex hull point accounting for stroke width - find_intersection(corners[0], v01, corners[4], v21, corners[2]); - return 5; -} - -void set_adjascent_info(vec2 c0, vec2 tangent, - int degree, - vec2 adj[3], - out float bevel, - out float angle - ){ - bool linear_adj = (angle_between_vectors(adj[1] - adj[0], adj[2] - adj[1]) < 1e-3); - angle = angle_between_vectors(c0 - adj[1], tangent); - // Decide on joint type - bool one_linear = (degree == 1 || linear_adj); - bool should_bevel = ( - (joint_type == AUTO_JOINT && one_linear) || - joint_type == BEVEL_JOINT - ); - bevel = should_bevel ? 1.0 : 0.0; -} - - -void find_joint_info(vec2 controls[3], vec2 prev[3], vec2 next[3], int degree){ - float tol = 1e-6; - - // Made as floats not bools so they can be passed to the frag shader - has_prev = float(distance(prev[2], controls[0]) < tol); - has_next = float(distance(next[0], controls[2]) < tol); - - if(bool(has_prev)){ - vec2 tangent = controls[1] - controls[0]; - set_adjascent_info( - controls[0], tangent, degree, prev, - bevel_start, angle_from_prev - ); - } - if(bool(has_next)){ - vec2 tangent = controls[1] - controls[2]; - set_adjascent_info( - controls[2], tangent, degree, next, - bevel_end, angle_to_next - ); - angle_to_next *= -1; - } + corners = vec3[6](c0, c1, c2, c3, c4, c5); } - void main() { - // Convert control points to a standard form if they are linear or null - vec3 controls[3]; - vec3 prev[3]; - vec3 next[3]; - bezier_degree = get_reduced_control_points(vec3[3](bp[0], bp[1], bp[2]), controls); - if(bezier_degree == 0.0) return; // Null curve - int degree = int(bezier_degree); - get_reduced_control_points(vec3[3](prev_bp[0], prev_bp[1], prev_bp[2]), prev); - get_reduced_control_points(vec3[3](next_bp[0], next_bp[1], next_bp[2]), next); - - - // Adjust stroke width based on distance from the camera - float scaled_strokes[3]; - for(int i = 0; i < 3; i++){ - float sf = perspective_scale_factor(controls[i].z, focal_distance); - if(bool(flat_stroke)){ - vec3 to_cam = normalize(vec3(0.0, 0.0, focal_distance) - controls[i]); - sf *= abs(dot(v_global_unit_normal[i], to_cam)); - } - scaled_strokes[i] = v_stroke_width[i] * sf; + // Curves are marked as ended when the handle after + // the first anchor is set equal to that anchor + if (verts[0] == verts[1]) return; + + vec3 p0 = verts[0]; + vec3 p1 = verts[1]; + vec3 p2 = verts[2]; + vec3 v01 = normalize(p1 - p0); + vec3 v12 = normalize(p2 - p1); + + vec4 jp1 = normalized_joint_product(v_joint_product[1]); + is_linear = float(jp1.w > COS_THRESHOLD); + + // We want to change the coordinates to a space where the curve + // coincides with y = x^2, between some values x0 and x2. Or, in + // the case of a linear curve just put it on the x-axis + mat4 xyz_to_uv; + float uv_scale_factor; + if (!bool(is_linear)) { + bool too_steep; + xyz_to_uv = get_xyz_to_uv(p0, p1, p2, 2.0, too_steep); + is_linear = float(too_steep); + uv_scale_factor = length(xyz_to_uv[0].xyz); } - // Control points are projected to the xy plane before drawing, which in turn - // gets translated to a uv plane. The z-coordinate information will be remembered - // by what's sent out to gl_Position, and by how it affects the lighting and stroke width - vec2 flat_controls[3]; - vec2 flat_prev[3]; - vec2 flat_next[3]; - flatten_points(controls, flat_controls); - flatten_points(prev, flat_prev); - flatten_points(next, flat_next); - - find_joint_info(flat_controls, flat_prev, flat_next, degree); - - // Corners of a bounding region around curve - vec2 corners[5]; - int n_corners = get_corners(flat_controls, degree, scaled_strokes, corners); - - int index_map[5] = int[5](0, 0, 1, 2, 2); - if(n_corners == 4) index_map[2] = 2; - - // Find uv conversion matrix - mat3 xy_to_uv = get_xy_to_uv(flat_controls[0], flat_controls[1]); - float scale_factor = length(flat_controls[1] - flat_controls[0]); - uv_anti_alias_width = anti_alias_width / scale_factor; - uv_b2 = (xy_to_uv * vec3(flat_controls[2], 1.0)).xy; + float scaled_aaw = anti_alias_width * pixel_size; + vec3 corners[6]; + get_corners(p0, p1, p2, v01, v12, scaled_aaw, corners); // Emit each corner - for(int i = 0; i < n_corners; i++){ - uv_coords = (xy_to_uv * vec3(corners[i], 1.0)).xy; - uv_stroke_width = scaled_strokes[index_map[i]] / scale_factor; - // Apply some lighting to the color before sending out. - // vec3 xyz_coords = vec3(corners[i], controls[index_map[i]].z); - vec3 xyz_coords = vec3(corners[i], controls[index_map[i]].z); - color = finalize_color( - v_color[index_map[i]], - xyz_coords, - v_global_unit_normal[index_map[i]], - light_source_position, - gloss, - shadow - ); - gl_Position = vec4(get_gl_Position(vec3(corners[i], 0.0)).xy, get_gl_Position(controls[index_map[i]]).zw); + float max_sw = max(v_stroke_width[0], v_stroke_width[2]); + for (int i = 0; i < 6; i++) { + float stroke_width = v_stroke_width[i / 2]; + + if (bool(is_linear)) { + float sign = vec2(-1, 1)[i % 2]; + // In this case, we only really care about + // the v coordinate + uv_coords = vec2(0, sign * (0.5 * stroke_width + scaled_aaw)); + uv_anti_alias_width = scaled_aaw; + uv_stroke_width = stroke_width; + } else { + uv_coords = (xyz_to_uv * vec4(corners[i], 1.0)).xy; + uv_stroke_width = uv_scale_factor * stroke_width; + uv_anti_alias_width = uv_scale_factor * scaled_aaw; + } + + color = finalize_color(v_color[i / 2], corners[i], unit_normal); + emit_gl_Position(corners[i]); EmitVertex(); } EndPrimitive(); diff --git a/manim/renderer/shaders/quadratic_bezier_stroke/vert.glsl b/manim/renderer/shaders/quadratic_bezier_stroke/vert.glsl index 4ed9d0a7e2..b5e2871817 100644 --- a/manim/renderer/shaders/quadratic_bezier_stroke/vert.glsl +++ b/manim/renderer/shaders/quadratic_bezier_stroke/vert.glsl @@ -1,34 +1,27 @@ #version 330 -#include ../include/camera_uniform_declarations.glsl +#include "../include/get_gl_Position.glsl" -in vec3 point; -in vec3 prev_point; -in vec3 next_point; -in vec3 unit_normal; +uniform float frame_scale; -in float stroke_width; +in vec3 point; in vec4 color; +in float stroke_width; +in vec4 joint_product; // Bezier control point -out vec3 bp; -out vec3 prev_bp; -out vec3 next_bp; -out vec3 v_global_unit_normal; +out vec3 verts; +out vec4 v_joint_product; out float v_stroke_width; out vec4 v_color; const float STROKE_WIDTH_CONVERSION = 0.01; -#include ../include/position_point_into_frame.glsl - -void main(){ - bp = position_point_into_frame(point); - prev_bp = position_point_into_frame(prev_point); - next_bp = position_point_into_frame(next_point); - v_global_unit_normal = rotate_point_into_frame(unit_normal); - +void main() { + verts = point; v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width; + v_stroke_width *= mix(frame_scale, 1, is_fixed_in_frame); + v_joint_product = joint_product; v_color = color; } diff --git a/manim/renderer/shaders/simple_vert.glsl b/manim/renderer/shaders/simple_vert.glsl index bea9442e6a..e7ec7e4094 100644 --- a/manim/renderer/shaders/simple_vert.glsl +++ b/manim/renderer/shaders/simple_vert.glsl @@ -1,6 +1,6 @@ #version 330 -#include "./include/camera_uniform_declarations.glsl" +#include "./include/get_gl_Position.glsl" in vec3 point; diff --git a/manim/renderer/shaders/surface/vert.glsl b/manim/renderer/shaders/surface/vert.glsl index 7c828e02bf..5787fbb7d1 100644 --- a/manim/renderer/shaders/surface/vert.glsl +++ b/manim/renderer/shaders/surface/vert.glsl @@ -1,7 +1,5 @@ #version 330 -#include "../include/camera_uniform_declarations.glsl" - in vec3 point; in vec3 du_point; in vec3 dv_point; diff --git a/manim/renderer/shaders/textured_surface/vert.glsl b/manim/renderer/shaders/textured_surface/vert.glsl index 47097146ae..c048fb6c16 100644 --- a/manim/renderer/shaders/textured_surface/vert.glsl +++ b/manim/renderer/shaders/textured_surface/vert.glsl @@ -1,6 +1,6 @@ #version 330 -#include "../include/camera_uniform_declarations.glsl" +#include "../include/get_gl_Position.glsl" in vec3 point; in vec3 du_point; diff --git a/manim/renderer/shaders/true_dot/vert.glsl b/manim/renderer/shaders/true_dot/vert.glsl index a69bef9936..f960d08cbb 100644 --- a/manim/renderer/shaders/true_dot/vert.glsl +++ b/manim/renderer/shaders/true_dot/vert.glsl @@ -1,6 +1,6 @@ #version 330 -#include "../include/camera_uniform_declarations.glsl" +#include "../include/get_gl_Position.glsl" in vec3 point; in vec4 color;