mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
ParametricSurface replaces the role of SurfaceMobject, and instead of tracking normals infinitesimal nudges are tracked
This commit is contained in:
parent
efe15cf2f0
commit
cacfe67fe1
10 changed files with 155 additions and 138 deletions
|
@ -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):
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
);
|
||||
}
|
17
manimlib/shaders/get_rotated_surface_unit_normal_vector.glsl
Normal file
17
manimlib/shaders/get_rotated_surface_unit_normal_vector.glsl
Normal file
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue