mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Improvements to surfaces, with textured surfaces available
This commit is contained in:
parent
4c33b99d39
commit
2b931dc7a2
7 changed files with 195 additions and 88 deletions
|
@ -1,93 +1,24 @@
|
||||||
from manimlib.constants import *
|
from manimlib.constants import *
|
||||||
from manimlib.mobject.geometry import Square
|
from manimlib.mobject.geometry import Square
|
||||||
from manimlib.mobject.types.surface_mobject import SurfaceMobject
|
from manimlib.mobject.types.surface_mobject import ParametricSurface
|
||||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||||
|
|
||||||
|
|
||||||
class ParametricSurface(SurfaceMobject):
|
|
||||||
CONFIG = {
|
|
||||||
"u_range": (0, 1),
|
|
||||||
"v_range": (0, 1),
|
|
||||||
"resolution": (32, 32),
|
|
||||||
"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
|
|
||||||
else:
|
|
||||||
self.uv_func = function
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
|
|
||||||
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, v] 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),
|
|
||||||
)
|
|
||||||
|
|
||||||
# self.points = point_grid[indices]
|
|
||||||
|
|
||||||
def get_triangle_ready_array_from_grid(self, grid):
|
|
||||||
# Given a grid, say of points or normals, this returns an Nx3 array
|
|
||||||
# whose rows are elements from this grid in such such a way that successive
|
|
||||||
# triplets of points form triangles covering the grid.
|
|
||||||
nu = grid.shape[0] - 1
|
|
||||||
nv = grid.shape[1] - 1
|
|
||||||
dim = grid.shape[2]
|
|
||||||
arr = np.zeros((nu * nv * 6, dim))
|
|
||||||
# To match the triangles covering this surface
|
|
||||||
arr[0::6] = grid[:-1, :-1].reshape((nu * nv, 3)) # Top left
|
|
||||||
arr[1::6] = grid[+1:, :-1].reshape((nu * nv, 3)) # Bottom left
|
|
||||||
arr[2::6] = grid[:-1, +1:].reshape((nu * nv, 3)) # Top right
|
|
||||||
arr[3::6] = grid[:-1, +1:].reshape((nu * nv, 3)) # Top right
|
|
||||||
arr[4::6] = grid[+1:, :-1].reshape((nu * nv, 3)) # Bottom left
|
|
||||||
arr[5::6] = grid[+1:, +1:].reshape((nu * nv, 3)) # Bottom right
|
|
||||||
return arr
|
|
||||||
|
|
||||||
def func(self, u, v):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Sphere, cylinder, cube, prism
|
# Sphere, cylinder, cube, prism
|
||||||
|
|
||||||
class Sphere(ParametricSurface):
|
class Sphere(ParametricSurface):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"resolution": (12, 24),
|
"resolution": (96, 48),
|
||||||
"radius": 1,
|
"radius": 1,
|
||||||
"u_range": (0, PI),
|
"u_range": (0, TAU),
|
||||||
"v_range": (0, TAU),
|
"v_range": (0, PI),
|
||||||
}
|
}
|
||||||
|
|
||||||
def func(self, u, v):
|
def func(self, u, v):
|
||||||
return self.radius * np.array([
|
return self.radius * np.array([
|
||||||
np.cos(v) * np.sin(u),
|
np.cos(u) * np.sin(v),
|
||||||
np.sin(v) * np.sin(u),
|
np.sin(u) * np.sin(v),
|
||||||
np.cos(u)
|
np.cos(v)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import moderngl
|
import moderngl
|
||||||
|
|
||||||
# from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from manimlib.constants import *
|
from manimlib.constants import *
|
||||||
from manimlib.mobject.mobject import Mobject
|
from manimlib.mobject.mobject import Mobject
|
||||||
from manimlib.utils.color import color_to_rgba
|
from manimlib.utils.color import color_to_rgba
|
||||||
|
from manimlib.utils.images import get_full_raster_image_path
|
||||||
|
|
||||||
|
|
||||||
class SurfaceMobject(Mobject):
|
class SurfaceMobject(Mobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"color": GREY,
|
"color": GREY,
|
||||||
"opacity": 1,
|
"opacity": 1,
|
||||||
"gloss": 1.0,
|
"gloss": 0.3,
|
||||||
"render_primative": moderngl.TRIANGLES,
|
"render_primative": moderngl.TRIANGLES,
|
||||||
# "render_primative": moderngl.TRIANGLE_STRIP,
|
|
||||||
"vert_shader_file": "surface_vert.glsl",
|
"vert_shader_file": "surface_vert.glsl",
|
||||||
"frag_shader_file": "surface_frag.glsl",
|
"frag_shader_file": "surface_frag.glsl",
|
||||||
"shader_dtype": [
|
"shader_dtype": [
|
||||||
|
@ -22,7 +22,6 @@ class SurfaceMobject(Mobject):
|
||||||
('normal', np.float32, (3,)),
|
('normal', np.float32, (3,)),
|
||||||
('color', np.float32, (4,)),
|
('color', np.float32, (4,)),
|
||||||
('gloss', np.float32, (1,)),
|
('gloss', np.float32, (1,)),
|
||||||
# ('im_coords', np.float32, (2,)),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +54,13 @@ class SurfaceMobject(Mobject):
|
||||||
rgba = color_to_rgba(color, opacity)
|
rgba = color_to_rgba(color, opacity)
|
||||||
self.rgbas = np.array([rgba])
|
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):
|
def apply_function(self, function, **kwargs):
|
||||||
# Apply it to infinitesimal neighbors to preserve normals
|
# Apply it to infinitesimal neighbors to preserve normals
|
||||||
pass
|
pass
|
||||||
|
@ -74,3 +80,123 @@ class SurfaceMobject(Mobject):
|
||||||
data["color"] = self.rgbas
|
data["color"] = self.rgbas
|
||||||
data["gloss"] = self.gloss
|
data["gloss"] = self.gloss
|
||||||
return data
|
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
|
||||||
|
else:
|
||||||
|
self.uv_func = function
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_triangle_ready_array_from_grid(self, grid):
|
||||||
|
# Given a grid, say of points or normals, this returns an Nx3 array
|
||||||
|
# whose rows are elements from this grid in such such a way that successive
|
||||||
|
# triplets of points form triangles covering the grid.
|
||||||
|
nu, nv, dim = grid.shape
|
||||||
|
nu -= 1
|
||||||
|
nv -= 1
|
||||||
|
arr = np.zeros((nu * nv * 6, dim))
|
||||||
|
# To match the triangles covering this surface
|
||||||
|
arr[0::6] = grid[:-1, :-1].reshape((nu * nv, dim)) # Top left
|
||||||
|
arr[1::6] = grid[+1:, :-1].reshape((nu * nv, dim)) # Bottom left
|
||||||
|
arr[2::6] = grid[:-1, +1:].reshape((nu * nv, dim)) # Top right
|
||||||
|
arr[3::6] = grid[:-1, +1:].reshape((nu * nv, dim)) # Top right
|
||||||
|
arr[4::6] = grid[+1:, :-1].reshape((nu * nv, dim)) # Bottom left
|
||||||
|
arr[5::6] = grid[+1:, +1:].reshape((nu * nv, dim)) # Bottom right
|
||||||
|
return arr
|
||||||
|
|
||||||
|
def func(self, u, v):
|
||||||
|
raise Exception("Not implemented")
|
||||||
|
|
||||||
|
|
||||||
|
class TexturedSurfaceMobject(SurfaceMobject):
|
||||||
|
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,)),
|
||||||
|
('im_coords', np.float32, (2,)),
|
||||||
|
('opacity', np.float32, (1,)),
|
||||||
|
('gloss', np.float32, (1,)),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, uv_surface, filename, **kwargs):
|
||||||
|
if not isinstance(uv_surface, ParametricSurface):
|
||||||
|
raise Exception("uv_surface must be of type Paramet")
|
||||||
|
path = get_full_raster_image_path(filename)
|
||||||
|
self.image = Image.open(path)
|
||||||
|
self.texture_path = path
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.set_points(
|
||||||
|
uv_surface.points,
|
||||||
|
uv_surface.normals,
|
||||||
|
)
|
||||||
|
self.opacity = uv_surface.rgbas[:, 3]
|
||||||
|
self.gloss = uv_surface.gloss
|
||||||
|
|
||||||
|
# Init im_coords
|
||||||
|
nu, nv = uv_surface.resolution
|
||||||
|
u_range = np.linspace(0, 1, nu + 1)
|
||||||
|
v_range = np.linspace(0, 1, nv + 1) # Upsidedown?
|
||||||
|
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)
|
||||||
|
|
||||||
|
def set_opacity(self, opacity, family=True):
|
||||||
|
self.opacity = opacity
|
||||||
|
if family:
|
||||||
|
for sm in self.submobjects:
|
||||||
|
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
|
||||||
|
data["im_coords"] = self.im_coords
|
||||||
|
data["opacity"] = self.opacity
|
||||||
|
data["gloss"] = self.gloss
|
||||||
|
return data
|
||||||
|
|
|
@ -13,11 +13,10 @@ vec4 add_light(vec4 raw_color, vec3 point, vec3 unit_normal, vec3 light_coords,
|
||||||
vec3 to_light = light_coords - point;
|
vec3 to_light = light_coords - point;
|
||||||
vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal);
|
vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal);
|
||||||
float dot_prod = dot(normalize(light_reflection), normalize(to_camera));
|
float dot_prod = dot(normalize(light_reflection), normalize(to_camera));
|
||||||
// float shine = gloss * exp(-3 * pow(1 - dot_prod, 2));
|
float shine = gloss * exp(-3 * pow(1 - dot_prod, 2));
|
||||||
float shine = 2 * gloss * exp(-1 * pow(1 - dot_prod, 2));
|
|
||||||
float dp2 = dot(normalize(to_light), unit_normal);
|
float dp2 = dot(normalize(to_light), unit_normal);
|
||||||
return vec4(
|
return vec4(
|
||||||
mix(0.5, 1.0, max(dp2, 0)) * mix(raw_color.rgb, vec3(1.0), shine),
|
((dp2 + 3.0) / 4.0) * mix(raw_color.rgb, vec3(1.0), shine),
|
||||||
raw_color.a
|
raw_color.a
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -1,10 +1,6 @@
|
||||||
#version 330
|
#version 330
|
||||||
|
|
||||||
// uniform sampler2D Texture;
|
|
||||||
|
|
||||||
// in vec2 v_im_coords;
|
|
||||||
in vec4 v_color;
|
in vec4 v_color;
|
||||||
|
|
||||||
out vec4 frag_color;
|
out vec4 frag_color;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
|
@ -17,7 +17,7 @@ in float gloss;
|
||||||
// out vec2 v_im_coords;
|
// out vec2 v_im_coords;
|
||||||
out vec4 v_color;
|
out vec4 v_color;
|
||||||
|
|
||||||
// Analog of import for manim only
|
// These lines will get replaced
|
||||||
#INSERT position_point_into_frame.glsl
|
#INSERT position_point_into_frame.glsl
|
||||||
#INSERT get_gl_Position.glsl
|
#INSERT get_gl_Position.glsl
|
||||||
#INSERT add_light.glsl
|
#INSERT add_light.glsl
|
||||||
|
|
20
manimlib/shaders/textured_surface_frag.glsl
Normal file
20
manimlib/shaders/textured_surface_frag.glsl
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
uniform sampler2D Texture;
|
||||||
|
uniform vec3 light_source_position;
|
||||||
|
|
||||||
|
in vec3 xyz_coords;
|
||||||
|
in vec3 v_normal;
|
||||||
|
in vec2 v_im_coords;
|
||||||
|
in float v_opacity;
|
||||||
|
in float v_gloss;
|
||||||
|
|
||||||
|
out vec4 frag_color;
|
||||||
|
|
||||||
|
#INSERT add_light.glsl
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 im_color = texture(Texture, v_im_coords);
|
||||||
|
frag_color = add_light(im_color, xyz_coords, normalize(v_normal), light_source_position, v_gloss);
|
||||||
|
frag_color.a = v_opacity;
|
||||||
|
}
|
35
manimlib/shaders/textured_surface_vert.glsl
Normal file
35
manimlib/shaders/textured_surface_vert.glsl
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
uniform float aspect_ratio;
|
||||||
|
uniform float anti_alias_width;
|
||||||
|
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 float opacity;
|
||||||
|
in float gloss;
|
||||||
|
|
||||||
|
out vec3 xyz_coords;
|
||||||
|
out vec3 v_normal;
|
||||||
|
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
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
xyz_coords = position_point_into_frame(point);
|
||||||
|
v_normal = position_point_into_frame(normal);
|
||||||
|
v_im_coords = im_coords;
|
||||||
|
v_opacity = opacity;
|
||||||
|
v_gloss = gloss;
|
||||||
|
gl_Position = get_gl_Position(xyz_coords);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue