diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 9676b67f..0803f1b9 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -174,6 +174,7 @@ class Camera(object): moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA, moderngl.ONE, moderngl.ONE ) + self.ctx.multisample = True # Testing self.background_fbo = None def init_light_source(self): diff --git a/manimlib/imports.py b/manimlib/imports.py index ae5a3e7a..a7fd7c6b 100644 --- a/manimlib/imports.py +++ b/manimlib/imports.py @@ -50,7 +50,7 @@ from manimlib.mobject.svg.text_mobject import * from manimlib.mobject.three_dimensions import * from manimlib.mobject.types.image_mobject import * from manimlib.mobject.types.point_cloud_mobject import * -from manimlib.mobject.types.surface_mobject import * +from manimlib.mobject.types.surface import * from manimlib.mobject.types.vectorized_mobject import * from manimlib.mobject.mobject_update_utils import * from manimlib.mobject.value_tracker import * diff --git a/manimlib/mobject/three_dimensions.py b/manimlib/mobject/three_dimensions.py index 2bd88339..9f974bd9 100644 --- a/manimlib/mobject/three_dimensions.py +++ b/manimlib/mobject/three_dimensions.py @@ -1,6 +1,6 @@ from manimlib.constants import * from manimlib.mobject.geometry import Square -from manimlib.mobject.types.surface_mobject import ParametricSurface +from manimlib.mobject.types.surface import ParametricSurface from manimlib.mobject.types.vectorized_mobject import VGroup @@ -8,7 +8,7 @@ from manimlib.mobject.types.vectorized_mobject import VGroup class Sphere(ParametricSurface): CONFIG = { - "resolution": (96, 48), + "resolution": (100, 50), "radius": 1, "u_range": (0, TAU), "v_range": (0, PI), @@ -18,10 +18,19 @@ class Sphere(ParametricSurface): return self.radius * np.array([ np.cos(u) * np.sin(v), np.sin(u) * np.sin(v), - np.cos(v) + -np.cos(v) ]) +class Clyinder(ParametricSurface): + CONFIG = { + + } + + def func(self, u, v): + pass + + class Cube(VGroup): CONFIG = { "fill_opacity": 0.75, diff --git a/manimlib/mobject/types/surface_mobject.py b/manimlib/mobject/types/surface.py similarity index 54% rename from manimlib/mobject/types/surface_mobject.py rename to manimlib/mobject/types/surface.py index 1b08bd8a..c572f7f9 100644 --- a/manimlib/mobject/types/surface_mobject.py +++ b/manimlib/mobject/types/surface.py @@ -7,96 +7,32 @@ from manimlib.constants import * from manimlib.mobject.mobject import Mobject from manimlib.utils.color import color_to_rgba from manimlib.utils.images import get_full_raster_image_path +from manimlib.utils.space_ops import normalize_along_axis -class SurfaceMobject(Mobject): +class ParametricSurface(Mobject): CONFIG = { + "u_range": (0, 1), + "v_range": (0, 1), + "resolution": (100, 100), "color": GREY, - "opacity": 1, + "opacity": 1.0, "gloss": 0.3, + # For du and dv steps. Much smaller and numerical error + # can crop up in the shaders. + "epsilon": 1e-5, "render_primative": moderngl.TRIANGLES, "vert_shader_file": "surface_vert.glsl", "frag_shader_file": "surface_frag.glsl", "shader_dtype": [ ('point', np.float32, (3,)), - ('normal', np.float32, (3,)), + ('du_point', np.float32, (3,)), + ('dv_point', np.float32, (3,)), ('color', np.float32, (4,)), ('gloss', np.float32, (1,)), ] } - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def init_points(self): - self.points = np.zeros((0, self.dim)) - self.normals = np.zeros((0, self.dim)) - - def init_colors(self): - self.set_color(self.color, self.opacity) - - def set_points(self, points, normals=None): - self.points = np.array(points) - if normals is None: - v01 = points[1:-1] - points[:-2] - v02 = points[2:] - points[:-2] - crosses = np.cross(v01, v02) - crosses[1::2] *= -1 # Because of reversed orientation of every other triangle in the strip - self.normals = np.vstack([ - crosses, - crosses[-1:].repeat(2, 0) # Repeat last entry twice - ]) - else: - self.normals = np.array(normals) - - def set_color(self, color, opacity): - # TODO, allow for multiple colors - rgba = color_to_rgba(color, opacity) - self.rgbas = np.array([rgba]) - - def set_gloss(self, gloss, family=True): - self.gloss = gloss - if family: - for sm in self.submobjects: - sm.set_gloss(gloss, family) - return self - - def apply_function(self, function, **kwargs): - # Apply it to infinitesimal neighbors to preserve normals - pass - - def rotate(self, axis, angle, **kwargs): - # Account for normals - pass - - def stretch(self, factor, dim, **kwargs): - # Account for normals - pass - - def get_shader_data(self): - data = self.get_blank_shader_data_array(len(self.points)) - data["point"] = self.points - data["normal"] = self.normals - data["color"] = self.rgbas - data["gloss"] = self.gloss - return data - - -class ParametricSurface(SurfaceMobject): - CONFIG = { - "u_range": (0, 1), - "v_range": (0, 1), - "resolution": (100, 100), - "surface_piece_config": {}, - "fill_color": BLUE_D, - "fill_opacity": 1.0, - "checkerboard_colors": [BLUE_D, BLUE_E], - "stroke_color": LIGHT_GREY, - "stroke_width": 0.5, - "should_make_jagged": False, - "pre_function_handle_to_anchor_scale_factor": 0.00001, - } - def __init__(self, function=None, **kwargs): if function is None: self.uv_func = self.func @@ -104,30 +40,28 @@ class ParametricSurface(SurfaceMobject): self.uv_func = function super().__init__(**kwargs) + def func(self, u, v): + raise Exception("Not implemented") + def init_points(self): - epsilon = 1e-6 # For differentials nu, nv = self.resolution u_range = np.linspace(*self.u_range, nu + 1) v_range = np.linspace(*self.v_range, nv + 1) - # List of three grids, [Pure uv values, those nudged by du, those nudged by dv] - uv_grids = [ - np.array([[[u + du, v + dv] for v in v_range] for u in u_range]) - for (du, dv) in [(0, 0), (epsilon, 0), (0, epsilon)] - ] - point_grid, points_nudged_du, points_nudged_dv = [ - np.apply_along_axis(lambda p: self.uv_func(*p), 2, uv_grid) - for uv_grid in uv_grids - ] - normal_grid = np.cross( - (points_nudged_du - point_grid) / epsilon, - (points_nudged_dv - point_grid) / epsilon, - ) - - self.set_points( - self.get_triangle_ready_array_from_grid(point_grid), - self.get_triangle_ready_array_from_grid(normal_grid), - ) + # Get three lists: + # - Points generated by pure uv values + # - Those generated by values nudged by du + # - Those generated by values nudged by dv + point_lists = [] + for (du, dv) in [(0, 0), (self.epsilon, 0), (0, self.epsilon)]: + uv_grid = np.array([[[u + du, v + dv] for v in v_range] for u in u_range]) + point_grid = np.apply_along_axis(lambda p: self.uv_func(*p), 2, uv_grid) + point_lists.append(self.get_triangle_ready_array_from_grid(point_grid)) + # Rather than tracking normal vectors, the points list will hold on to the + # infinitesimal nudged values alongside the original values. This way, one + # can perform all the manipulations they'd like to the surface, and normals + # are still easily recoverable. + self.points = np.vstack(point_lists) def get_triangle_ready_array_from_grid(self, grid): # Given a grid, say of points or normals, this returns an Nx3 array @@ -146,17 +80,66 @@ class ParametricSurface(SurfaceMobject): arr[5::6] = grid[+1:, +1:].reshape((nu * nv, dim)) # Bottom right return arr - def func(self, u, v): - raise Exception("Not implemented") + def init_colors(self): + self.set_color(self.color, self.opacity) + + def get_surface_points_and_nudged_points(self): + k = len(self.points) // 3 + return self.points[:k], self.points[k:2 * k], self.points[2 * k:] + + def get_unit_normals(self): + s_points, du_points, dv_points = self.get_surface_points_and_nudged_points() + normals = np.cross( + (du_points - s_points) / self.epsilon, + (dv_points - s_points) / self.epsilon, + ) + return normalize_along_axis(normals, 1) + + def set_color(self, color, opacity=1.0, family=True): + # TODO, allow for multiple colors + rgba = color_to_rgba(color, opacity) + self.rgbas = np.array([rgba]) + if family: + for submob in self.submobjects: + submob.set_color(color, opacity) + + def set_opacity(self, opacity, family=True): + self.rgbas[:, 3] = opacity + if family: + for sm in self.submobjects: + sm.set_opacity(opacity, family) + return self + + def set_gloss(self, gloss, family=True): + self.gloss = gloss + if family: + for sm in self.submobjects: + sm.set_gloss(gloss, family) + return self + + def get_shader_data(self): + s_points, du_points, dv_points = self.get_surface_points_and_nudged_points() + data = self.get_blank_shader_data_array(len(s_points)) + data["point"] = s_points + data["du_point"] = du_points + data["dv_point"] = dv_points + self.fill_in_shader_color_info(data) + return data + + def fill_in_shader_color_info(self, data): + data["color"] = self.rgbas + data["gloss"] = self.gloss + return data -class TexturedSurfaceMobject(SurfaceMobject): +class TexturedSurface(ParametricSurface): CONFIG = { "vert_shader_file": "textured_surface_vert.glsl", "frag_shader_file": "textured_surface_frag.glsl", "shader_dtype": [ ('point', np.float32, (3,)), - ('normal', np.float32, (3,)), + ('du_point', np.float32, (3,)), + ('dv_point', np.float32, (3,)), ('im_coords', np.float32, (2,)), ('opacity', np.float32, (1,)), ('gloss', np.float32, (1,)), @@ -165,25 +148,25 @@ class TexturedSurfaceMobject(SurfaceMobject): def __init__(self, uv_surface, filename, **kwargs): if not isinstance(uv_surface, ParametricSurface): - raise Exception("uv_surface must be of type Paramet") + raise Exception("uv_surface must be of type ParametricSurface") path = get_full_raster_image_path(filename) self.image = Image.open(path) self.texture_path = path + self.uv_surface = uv_surface super().__init__(**kwargs) - self.set_points( - uv_surface.points, - uv_surface.normals, - ) - self.opacity = uv_surface.rgbas[:, 3] - self.gloss = uv_surface.gloss - + def init_points(self): + self.points = self.uv_surface.points # Init im_coords - nu, nv = uv_surface.resolution + nu, nv = self.uv_surface.resolution u_range = np.linspace(0, 1, nu + 1) - v_range = np.linspace(0, 1, nv + 1) # Upsidedown? + v_range = np.linspace(1, 0, nv + 1) # Reverse y-direction uv_grid = np.array([[[u, v] for v in v_range] for u in u_range]) - self.im_coords = uv_surface.get_triangle_ready_array_from_grid(uv_grid) + self.im_coords = self.uv_surface.get_triangle_ready_array_from_grid(uv_grid) + + def init_colors(self): + self.opacity = self.uv_surface.rgbas[:, 3] + self.gloss = self.uv_surface.gloss def set_opacity(self, opacity, family=True): self.opacity = opacity @@ -192,10 +175,7 @@ class TexturedSurfaceMobject(SurfaceMobject): sm.set_opacity(opacity, family) return self - def get_shader_data(self): - data = self.get_blank_shader_data_array(len(self.points)) - data["point"] = self.points - data["normal"] = self.normals + def fill_in_shader_color_info(self, data): data["im_coords"] = self.im_coords data["opacity"] = self.opacity data["gloss"] = self.gloss diff --git a/manimlib/shaders/add_light.glsl b/manimlib/shaders/add_light.glsl index 44aab967..036dc778 100644 --- a/manimlib/shaders/add_light.glsl +++ b/manimlib/shaders/add_light.glsl @@ -1,8 +1,7 @@ vec4 add_light(vec4 raw_color, vec3 point, vec3 unit_normal, vec3 light_coords, float gloss){ if(gloss == 0.0) return raw_color; - // TODO, do we actually want this? For VMobjects its nice to just choose whichever unit normal - // is pointing towards the camera. + // TODO, do we actually want this? It effectively treats surfaces as two-sided if(unit_normal.z < 0){ unit_normal *= -1; } @@ -15,8 +14,9 @@ vec4 add_light(vec4 raw_color, vec3 point, vec3 unit_normal, vec3 light_coords, 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 mult = ((dp2 + 2.0) / 3.0); return vec4( - ((dp2 + 3.0) / 4.0) * mix(raw_color.rgb, vec3(1.0), shine), + mult * mix(raw_color.rgb, vec3(1.0), shine), raw_color.a ); } \ No newline at end of file diff --git a/manimlib/shaders/get_rotated_surface_unit_normal_vector.glsl b/manimlib/shaders/get_rotated_surface_unit_normal_vector.glsl new file mode 100644 index 00000000..81444d97 --- /dev/null +++ b/manimlib/shaders/get_rotated_surface_unit_normal_vector.glsl @@ -0,0 +1,17 @@ +// Only inlucde in an environment with to_screen_space defined + +vec3 get_rotated_surface_unit_normal_vector(vec3 point, vec3 du_point, vec3 dv_point){ + // normal = get_unit_normal(point, du_point, dv_point); + // return normalize((to_screen_space * vec4(normal, 0.0)).xyz); + vec3 cp = cross( + (du_point - point), + (dv_point - point) + ); + if(length(cp) == 0){ + // Instead choose a normal to just dv_point - point in the direction of point + vec3 v2 = dv_point - point; + cp = cross(cross(v2, point), v2); + } + // The zero is deliberate, as we only want to rotate and not shift + return normalize((to_screen_space * vec4(cp, 0.0)).xyz); +} \ No newline at end of file diff --git a/manimlib/shaders/position_point_into_frame.glsl b/manimlib/shaders/position_point_into_frame.glsl index 91818735..efaa28da 100644 --- a/manimlib/shaders/position_point_into_frame.glsl +++ b/manimlib/shaders/position_point_into_frame.glsl @@ -1,9 +1,7 @@ // Must be used in an environment with the following uniforms: // uniform mat4 to_screen_space; -// uniform float focal_distance; vec3 position_point_into_frame(vec3 point){ - // Apply the pre-computed to_screen_space matrix. - vec4 new_point = to_screen_space * vec4(point, 1); - return new_point.xyz; + // Simply apply the pre-computed to_screen_space matrix. + return (to_screen_space * vec4(point, 1)).xyz; } diff --git a/manimlib/shaders/surface_frag.glsl b/manimlib/shaders/surface_frag.glsl index 6e71b36a..736e145d 100644 --- a/manimlib/shaders/surface_frag.glsl +++ b/manimlib/shaders/surface_frag.glsl @@ -1,8 +1,19 @@ #version 330 +// in vec4 v_color; +// out vec4 frag_color; + +uniform vec3 light_source_position; + +in vec3 xyz_coords; +in vec3 v_normal; in vec4 v_color; +in float v_gloss; + out vec4 frag_color; +#INSERT add_light.glsl + void main() { - frag_color = v_color; + frag_color = add_light(v_color, xyz_coords, normalize(v_normal), light_source_position, v_gloss); } \ No newline at end of file diff --git a/manimlib/shaders/surface_vert.glsl b/manimlib/shaders/surface_vert.glsl index d8ed38b8..d4591ac4 100644 --- a/manimlib/shaders/surface_vert.glsl +++ b/manimlib/shaders/surface_vert.glsl @@ -6,26 +6,26 @@ uniform mat4 to_screen_space; uniform float focal_distance; uniform vec3 light_source_position; -// uniform sampler2D Texture; - in vec3 point; -in vec3 normal; -// in vec2 im_coords; +in vec3 du_point; +in vec3 dv_point; in vec4 color; in float gloss; -// out vec2 v_im_coords; +out vec3 xyz_coords; +out vec3 v_normal; out vec4 v_color; +out float v_gloss; // These lines will get replaced #INSERT position_point_into_frame.glsl #INSERT get_gl_Position.glsl -#INSERT add_light.glsl +#INSERT get_rotated_surface_unit_normal_vector.glsl void main(){ - vec3 xyz_coords = position_point_into_frame(point); - vec3 unit_normal = normalize(position_point_into_frame(normal)); - // v_im_coords = im_coords; - v_color = add_light(color, xyz_coords, unit_normal, light_source_position, gloss); + xyz_coords = position_point_into_frame(point); + v_normal = get_rotated_surface_unit_normal_vector(point, du_point, dv_point); + v_color = color; + v_gloss = gloss; gl_Position = get_gl_Position(xyz_coords); } \ No newline at end of file diff --git a/manimlib/shaders/textured_surface_vert.glsl b/manimlib/shaders/textured_surface_vert.glsl index 1b44b1f1..a8c4ca57 100644 --- a/manimlib/shaders/textured_surface_vert.glsl +++ b/manimlib/shaders/textured_surface_vert.glsl @@ -9,7 +9,8 @@ uniform vec3 light_source_position; // uniform sampler2D Texture; in vec3 point; -in vec3 normal; +in vec3 du_point; +in vec3 dv_point; in vec2 im_coords; in float opacity; in float gloss; @@ -20,14 +21,14 @@ out vec2 v_im_coords; out float v_opacity; out float v_gloss; - // These lines will get replaced #INSERT position_point_into_frame.glsl #INSERT get_gl_Position.glsl +#INSERT get_rotated_surface_unit_normal_vector.glsl void main(){ xyz_coords = position_point_into_frame(point); - v_normal = position_point_into_frame(normal); + v_normal = get_rotated_surface_unit_normal_vector(point, du_point, dv_point); v_im_coords = im_coords; v_opacity = opacity; v_gloss = gloss;