Skip to content

Instantly share code, notes, and snippets.

@gastonmorixe
Created March 5, 2025 10:25
Show Gist options
  • Save gastonmorixe/c56e63a1b2b8e4274a410110057c6beb to your computer and use it in GitHub Desktop.
Save gastonmorixe/c56e63a1b2b8e4274a410110057c6beb to your computer and use it in GitHub Desktop.
from manimlib import *
import random
class ShadowMappingIllustration(Scene):
def construct(self):
# -------------------------------------------------------------
# INTRO TITLE
# -------------------------------------------------------------
title = Text("Shadow Mapping: Two-Pass Technique")
title.scale(1.2)
title.to_edge(UP)
self.play(Write(title))
self.wait()
intro_text = Text("A powerful real-time shadow technique", font_size=30)
intro_text.next_to(title, DOWN)
self.play(FadeIn(intro_text))
self.wait(2)
self.play(FadeOut(intro_text))
# -------------------------------------------------------------
# SCENE SETUP (2D Conceptual)
# -------------------------------------------------------------
# Light source (conceptually at top-left)
light_dot = Dot(LEFT * 4 + UP * 2, color=YELLOW)
light_label = Text("Light")
light_label.set_color(YELLOW)
light_label.next_to(light_dot, UP)
# Camera (conceptually at bottom-right)
camera_dot = Dot(RIGHT * 4 + DOWN, color=GREEN)
camera_label = Text("Camera")
camera_label.set_color(GREEN)
camera_label.next_to(camera_dot, DOWN)
# Ground plane line
ground_line = Line(LEFT * 6 + DOWN * 2, RIGHT * 6 + DOWN * 2).set_color(GREY)
# An object in the scene: a polygon
shape = Polygon(
np.array([-1, -1, 0]),
np.array([-1.5, 0, 0]),
np.array([-0.5, 1, 0]),
np.array([0.5, 1, 0]),
np.array([1, -0.5, 0]),
color=BLUE,
)
shape.set_fill(BLUE, opacity=0.4)
# Put everything on screen
self.play(
FadeIn(light_dot),
FadeIn(light_label),
FadeIn(camera_dot),
FadeIn(camera_label),
ShowCreation(ground_line),
ShowCreation(shape),
)
self.wait()
# -------------------------------------------------------------
# THE PROBLEM OF SHADOWS (Introduction)
# -------------------------------------------------------------
prob_title = Text("The Challenge of Shadow Generation")
prob_title.scale(0.8)
prob_title.to_edge(UP).shift(DOWN * 0.8)
self.play(ReplacementTransform(title, prob_title))
self.wait()
# Add shadow line explanation
problem_text = Text(
"How do we determine which points are in shadow?", font_size=24
)
problem_text.to_corner(UL).shift(DOWN * 2.5 + RIGHT * 1.5)
self.play(Write(problem_text))
self.wait()
# Show the core shadow question
light_ray1 = Arrow(
light_dot.get_center(), shape.get_center(), buff=0.1, color=YELLOW
)
light_ray2 = DashedLine(
shape.get_center(),
ground_line.point_from_proportion(0.7),
color=YELLOW_D,
dash_length=0.1,
)
shadow_point = Dot(ground_line.point_from_proportion(0.7), color=RED)
self.play(ShowCreation(light_ray1))
self.play(ShowCreation(light_ray2), FadeIn(shadow_point))
shadow_q = Text("Is this point in shadow?", font_size=24, color=RED)
shadow_q.next_to(shadow_point, DOWN)
self.play(Write(shadow_q))
self.wait(2)
# Clean up
self.play(
FadeOut(shadow_q),
FadeOut(light_ray1),
FadeOut(light_ray2),
FadeOut(shadow_point),
FadeOut(problem_text),
)
# -------------------------------------------------------------
# STEP 1: RENDER FROM LIGHT'S PERSPECTIVE (Shadow Map Creation)
# -------------------------------------------------------------
step1_title = Text("Step 1: Render from Light's Perspective")
step1_title.scale(0.8)
step1_title.to_edge(UP).shift(DOWN * 0.8)
self.play(ReplacementTransform(prob_title, step1_title))
self.wait()
# We'll create an arrow showing the light's viewpoint
light_view_arrow = Arrow(
start=light_dot.get_center() + RIGHT * 0.5,
end=shape.get_center(),
buff=0,
color=YELLOW,
)
light_view_label = Text("Light View")
light_view_label.scale(0.7)
light_view_label.set_color(YELLOW)
light_view_label.next_to(light_view_arrow, UP)
self.play(ShowCreation(light_view_arrow), FadeIn(light_view_label))
self.wait()
# Mathematical explanation of light space transform
light_matrix = Tex(r"M_{light} = P_{light} \cdot V_{light}")
light_matrix.scale(0.7)
light_matrix.to_corner(UR)
light_matrix.shift(LEFT * 2 + DOWN * 0.5)
self.play(FadeIn(light_matrix))
self.wait()
# Show a "Depth Map" box on the side
shadow_map_box = Rectangle(width=2.5, height=2, color=WHITE)
shadow_map_box.shift(LEFT * 3.5 + DOWN * 2.5)
shadow_map_label = Text("Shadow Map\n(Depth Texture)")
shadow_map_label.scale(0.6)
shadow_map_label.move_to(shadow_map_box.get_center())
self.play(ShowCreation(shadow_map_box), FadeIn(shadow_map_label))
self.wait()
# Conceptual arrow: "Scene -> Depth Map"
arrow_to_shadowmap = Arrow(
start=shape.get_center() + DOWN * 0.5,
end=shadow_map_box.get_top(),
buff=0.1,
color=WHITE,
)
self.play(ShowCreation(arrow_to_shadowmap))
self.wait()
# Describe the depth map mathematically
depth_eq = Tex(r"depth(x,y) = z_{light}")
depth_eq.scale(0.6)
depth_eq.next_to(shadow_map_box, DOWN)
self.play(Write(depth_eq))
self.wait(2)
# -------------------------------------------------------------
# STEP 2: RENDER FROM CAMERA & DEPTH COMPARISON
# -------------------------------------------------------------
step2_title = Text("Step 2: Render from Camera & Compare Depth")
step2_title.scale(0.8)
step2_title.to_edge(UP).shift(DOWN * 0.8)
self.play(ReplacementTransform(step1_title, step2_title))
self.wait()
# Indicate camera's perspective
camera_view_arrow = Arrow(
start=camera_dot.get_center() + LEFT * 0.5,
end=shape.get_center(),
buff=0,
color=GREEN,
)
camera_view_label = Text("Camera View")
camera_view_label.scale(0.7)
camera_view_label.set_color(GREEN)
camera_view_label.next_to(camera_view_arrow, DOWN)
self.play(ShowCreation(camera_view_arrow), FadeIn(camera_view_label))
self.wait()
# Add a "depth compare" label
compare_label = Text("Compare fragment depth\nwith Shadow Map depth")
compare_label.scale(0.7)
compare_label.next_to(shadow_map_box, RIGHT, buff=0.5).shift(UP * 0.3)
self.play(FadeIn(compare_label))
self.wait()
# Mathematical formula for the comparison
compare_eq = Tex(
r"\text{is\_shadowed} = \begin{cases} true & \text{if } depth_{current} > depth_{map} \\ false & \text{otherwise} \end{cases}"
)
compare_eq.scale(0.6)
compare_eq.next_to(compare_label, DOWN, buff=0.5)
self.play(Write(compare_eq))
self.wait()
# A final "Shadowed" region (conceptual)
shadow_region = Polygon(
np.array([0.5, -2, 0]),
np.array([2, -2, 0]),
np.array([2, -1, 0]),
np.array([0.5, -1, 0]),
color=BLACK,
)
shadow_region.set_fill(BLACK, opacity=0.4)
self.play(FadeIn(shadow_region))
self.wait()
# -------------------------------------------------------------
# TRANSITION TO 3D
# -------------------------------------------------------------
transition_title = Text("From 2D to 3D")
transition_title.scale(0.8)
transition_title.to_edge(UP).shift(DOWN * 0.8)
self.play(
ReplacementTransform(step2_title, transition_title),
FadeOut(light_matrix),
FadeOut(depth_eq),
FadeOut(compare_eq),
FadeOut(compare_label),
FadeOut(camera_view_label),
FadeOut(camera_view_arrow),
FadeOut(light_view_label),
FadeOut(light_view_arrow),
FadeOut(arrow_to_shadowmap),
FadeOut(shadow_map_label),
FadeOut(shadow_map_box),
FadeOut(shadow_region),
)
self.wait()
transition_text = Text("Moving to a full 3D implementation...")
transition_text.scale(0.7)
transition_text.next_to(transition_title, DOWN)
self.play(Write(transition_text))
self.wait(2)
# Fade out 2D elements
self.play(
FadeOut(light_dot),
FadeOut(light_label),
FadeOut(camera_dot),
FadeOut(camera_label),
FadeOut(ground_line),
FadeOut(shape),
FadeOut(transition_text),
)
self.wait()
# Final text to transition to next scene
next_scene = Text("Next: 3D Shadow Mapping Implementation")
next_scene.scale(0.9)
self.play(ReplacementTransform(transition_title, next_scene))
self.wait(2)
self.play(FadeOut(next_scene))
self.wait()
class ShadowMapping3D(Scene):
def construct(self):
# Set up the scene
title = Text("3D Shadow Mapping")
title.to_edge(UP)
self.play(Write(title))
self.wait()
# Create a more advanced 3D demonstration using perspective drawings
# --------------------------------
# STEP 1: SHOW THE BASIC 3D SETUP
# --------------------------------
# Create a perspective grid to represent 3D space
grid_h_lines = VGroup(
*[
Line(
LEFT * 5 + UP * (i * 0.5 - 2),
RIGHT * 5 + UP * (i * 0.5 - 2),
stroke_width=1,
color=GREY,
)
for i in range(9)
]
)
grid_v_lines = VGroup(
*[
Line(
LEFT * (i - 5) + UP * 2,
LEFT * (i - 5) + DOWN * 2,
stroke_width=1,
color=GREY,
)
for i in range(11)
]
)
# Create a ground plane with perspective
ground_corners = [
LEFT * 4 + DOWN * 2, # Bottom left
RIGHT * 4 + DOWN * 2, # Bottom right
RIGHT * 4 + DOWN * 1, # Top right (perspective)
LEFT * 4 + DOWN * 1, # Top left (perspective)
]
ground = Polygon(
*ground_corners, fill_color=GREY, fill_opacity=0.3, stroke_color=GREY
)
# Add axes labels for clarity
x_axis = Arrow(ORIGIN, RIGHT * 3, color=RED)
y_axis = Arrow(ORIGIN, UP * 2, color=GREEN)
z_axis = Arrow(ORIGIN, OUT * 2 + LEFT * 1, buff=0, color=BLUE) # Perspective
axes_labels = VGroup(
Text("X", font_size=24, color=RED).next_to(x_axis, RIGHT),
Text("Y", font_size=24, color=GREEN).next_to(y_axis, UP),
Text("Z", font_size=24, color=BLUE).next_to(
z_axis.get_end(), LEFT, buff=0.1
),
)
# Create 3D objects
# Cube (drawn with perspective)
cube_center = LEFT * 2 + UP * 0
cube_size = 1.2
cube_corners = [
# Front face (closer to viewer)
cube_center + LEFT * cube_size / 2 + DOWN * cube_size / 2,
cube_center + RIGHT * cube_size / 2 + DOWN * cube_size / 2,
cube_center + RIGHT * cube_size / 2 + UP * cube_size / 2,
cube_center + LEFT * cube_size / 2 + UP * cube_size / 2,
# Back face (with perspective - smaller and shifted)
cube_center
+ LEFT * cube_size / 2.5
+ DOWN * cube_size / 2.5
+ OUT * cube_size / 1.2,
cube_center
+ RIGHT * cube_size / 2.5
+ DOWN * cube_size / 2.5
+ OUT * cube_size / 1.2,
cube_center
+ RIGHT * cube_size / 2.5
+ UP * cube_size / 2.5
+ OUT * cube_size / 1.2,
cube_center
+ LEFT * cube_size / 2.5
+ UP * cube_size / 2.5
+ OUT * cube_size / 1.2,
]
# Draw cube faces
cube_front = Polygon(
cube_corners[0],
cube_corners[1],
cube_corners[2],
cube_corners[3],
fill_color=BLUE,
fill_opacity=0.5,
stroke_color=BLUE_E,
)
cube_top = Polygon(
cube_corners[3],
cube_corners[2],
cube_corners[6],
cube_corners[7],
fill_color=BLUE_D,
fill_opacity=0.6,
stroke_color=BLUE_E,
)
cube_right = Polygon(
cube_corners[1],
cube_corners[5],
cube_corners[6],
cube_corners[2],
fill_color=BLUE_A,
fill_opacity=0.4,
stroke_color=BLUE_E,
)
cube = VGroup(cube_front, cube_top, cube_right)
# Sphere (represented by circle with shading)
sphere_center = RIGHT * 2.5 + UP * 0.5
sphere = Circle(
radius=0.8, fill_color=RED, fill_opacity=0.6, stroke_color=RED_E
)
sphere.move_to(sphere_center)
# Add a dot in the center and a line to the ground to show 3D position
sphere_center_dot = Dot(sphere_center, color=RED_E)
sphere_ground_line = DashedLine(
sphere_center,
[sphere_center[0], sphere_center[1] - 1.5, 0],
color=GREY,
dash_length=0.1,
)
# Light source (positioned at upper left with perspective)
light_pos = LEFT * 3.5 + UP * 3 + OUT * 2
light = Dot(light_pos, color=YELLOW, radius=0.2)
light_glow = Circle(
radius=0.4, fill_color=YELLOW, fill_opacity=0.3, stroke_width=0
)
light_glow.move_to(light_pos)
light_label = Text("Light Source", font_size=24, color=YELLOW)
light_label.next_to(light_glow, UP)
# Camera (positioned at lower right with perspective)
camera_pos = RIGHT * 4 + UP * 1.5 + OUT * 1
camera = Dot(camera_pos, color=GREEN, radius=0.15)
camera_body = (
Triangle(
stroke_color=GREEN, stroke_width=2, fill_color=GREEN, fill_opacity=0.4
)
.scale(0.4)
.rotate(45 * DEGREES)
)
camera_body.move_to(camera_pos)
camera_label = Text("Camera", font_size=24, color=GREEN)
camera_label.next_to(camera_body, RIGHT)
# Add 3D setup elements to scene
self.play(
ShowCreation(grid_h_lines), ShowCreation(grid_v_lines), ShowCreation(ground)
)
self.wait(0.5)
self.play(
ShowCreation(x_axis),
ShowCreation(y_axis),
ShowCreation(z_axis),
Write(axes_labels),
)
self.wait()
# Animate 3D objects appearing
self.play(ShowCreation(cube))
self.play(
ShowCreation(sphere),
ShowCreation(sphere_center_dot),
ShowCreation(sphere_ground_line),
)
self.wait()
# Add light and camera
self.play(FadeIn(light), FadeIn(light_glow), Write(light_label))
self.play(FadeIn(camera), FadeIn(camera_body), Write(camera_label))
self.wait(2)
# Explain the setup
setup_title = Text("3D Scene Setup with Light and Camera", font_size=30)
setup_title.next_to(title, DOWN)
self.play(Write(setup_title))
self.wait(2)
# -------------------------------------------
# STEP 2: TRANSITION TO LIGHT'S PERSPECTIVE
# -------------------------------------------
# Create a "light POV" frame that we'll transition to
light_pov_frame = Rectangle(
width=7, height=5, stroke_color=YELLOW, stroke_width=3
)
light_pov_frame.to_edge(DOWN, buff=0.5)
light_pov_label = Text("Light's Perspective", color=YELLOW, font_size=28)
light_pov_label.next_to(light_pov_frame, UP)
step1_text = Text("Step 1: Render from Light's Perspective", font_size=28)
step1_text.next_to(title, DOWN)
self.play(
ReplacementTransform(setup_title, step1_text),
ShowCreation(light_pov_frame),
Write(light_pov_label),
)
self.wait()
# Draw rays from light to objects to show what the light "sees"
light_ray1 = Arrow(light_pos, cube_center, color=YELLOW, buff=0.1)
light_ray2 = Arrow(light_pos, sphere_center, color=YELLOW, buff=0.1)
self.play(ShowCreation(light_ray1), ShowCreation(light_ray2))
self.wait()
# Create a "light view" of the scene (simplified for clarity)
light_view_cube = Square(
side_length=0.8, stroke_color=BLUE, fill_color=BLUE, fill_opacity=0.5
)
light_view_sphere = Circle(
radius=0.5, stroke_color=RED, fill_color=RED, fill_opacity=0.5
)
# Position light view objects
light_view_cube.move_to(light_pov_frame.get_center() + LEFT * 1.5 + DOWN * 0.5)
light_view_sphere.move_to(light_pov_frame.get_center() + RIGHT * 1.5 + UP * 0.5)
# Create a depth buffer visualization
depth_label = Text("Depth Values", font_size=20, color=YELLOW)
depth_label.move_to(light_pov_frame.get_top() + DOWN * 0.5)
depth_values = VGroup(
Tex("d_{cube} = 4.2", color=BLUE), Tex("d_{sphere} = 5.7", color=RED)
).arrange(RIGHT, buff=1)
depth_values.next_to(depth_label, DOWN)
# Animate the light view appearing in the frame
self.play(FadeIn(light_view_cube), FadeIn(light_view_sphere))
self.play(Write(depth_label), Write(depth_values))
self.wait(2)
# Create a "shadow map" texture
shadow_map = Rectangle(width=2, height=1.5)
shadow_map.set_fill(BLACK, opacity=0.2)
shadow_map.set_stroke(YELLOW)
shadow_map.to_corner(UL).shift(DOWN + RIGHT)
shadow_map_label = Text("Shadow Map\n(Depth Texture)", font_size=20)
shadow_map_label.next_to(shadow_map, DOWN)
# Create visualization of shadow map with depth values
# Using a more basic approach since ImageMobject might not be available
shadow_map_viz = Rectangle(
width=shadow_map.get_width() * 0.9, height=shadow_map.get_height() * 0.9
)
shadow_map_viz.set_fill(color=GREY_E, opacity=0.8)
shadow_map_viz.move_to(shadow_map)
# Add some dots to represent depth values
depth_dots = VGroup(
*[
Dot(
shadow_map_viz.get_center()
+ np.array(
[random.uniform(-0.7, 0.7), random.uniform(-0.5, 0.5), 0]
),
radius=0.03,
color=GREY_D,
)
for _ in range(20)
]
)
self.play(
ShowCreation(shadow_map),
FadeIn(shadow_map_viz),
*[FadeIn(dot) for dot in depth_dots],
Write(shadow_map_label),
)
self.wait(2)
# ----------------------------------------
# STEP 3: CAMERA PERSPECTIVE AND COMPARISON
# ----------------------------------------
step2_text = Text("Step 2: Render from Camera's Perspective", font_size=28)
step2_text.next_to(title, DOWN)
# Create camera POV frame similar to light POV
camera_pov_frame = Rectangle(
width=7, height=5, stroke_color=GREEN, stroke_width=3
)
camera_pov_frame.to_edge(DOWN, buff=0.5)
camera_pov_label = Text("Camera's Perspective", color=GREEN, font_size=28)
camera_pov_label.next_to(camera_pov_frame, UP)
self.play(
ReplacementTransform(step1_text, step2_text),
ReplacementTransform(light_pov_frame, camera_pov_frame),
ReplacementTransform(light_pov_label, camera_pov_label),
FadeOut(light_view_cube),
FadeOut(light_view_sphere),
FadeOut(depth_label),
FadeOut(depth_values),
)
self.wait()
# Draw rays from camera to objects
camera_ray1 = Arrow(camera_pos, cube_center, color=GREEN, buff=0.1)
camera_ray2 = Arrow(camera_pos, sphere_center, color=GREEN, buff=0.1)
self.play(ShowCreation(camera_ray1), ShowCreation(camera_ray2))
self.wait()
# Create camera view objects
camera_view_cube = Square(
side_length=1, stroke_color=BLUE, fill_color=BLUE, fill_opacity=0.5
)
camera_view_sphere = Circle(
radius=0.65, stroke_color=RED, fill_color=RED, fill_opacity=0.5
)
# Position camera view objects
camera_view_cube.move_to(camera_pov_frame.get_center() + LEFT * 2)
camera_view_sphere.move_to(camera_pov_frame.get_center() + RIGHT * 1.5)
self.play(FadeIn(camera_view_cube), FadeIn(camera_view_sphere))
self.wait()
# -------------------------------------------
# STEP 4: SHADOW COMPARISON AND CALCULATION
# -------------------------------------------
step3_text = Text("Step 3: Compare Depths to Determine Shadows", font_size=28)
step3_text.next_to(title, DOWN)
self.play(ReplacementTransform(step2_text, step3_text))
self.wait()
# Create shadow rays to show shadow projection
shadow_ray1 = DashedLine(
cube_center,
[cube_center[0] * 1.2, cube_center[1] * 1.2, -2],
color=YELLOW_E,
dash_length=0.1,
)
shadow_ray2 = DashedLine(
sphere_center,
[sphere_center[0] * 1.1, sphere_center[1] * 1.1, -2],
color=YELLOW_E,
dash_length=0.1,
)
self.play(ShowCreation(shadow_ray1), ShowCreation(shadow_ray2))
self.wait()
# Create shadows on the ground
shadow1 = Square(
side_length=1, fill_color=BLACK, fill_opacity=0.6, stroke_width=0
)
shadow1.move_to([cube_center[0] * 1.2, cube_center[1] * 1.2, -1.95])
shadow1.stretch(0.2, 1) # Flatten to look like it's on the ground
shadow2 = Circle(radius=0.6, fill_color=BLACK, fill_opacity=0.6, stroke_width=0)
shadow2.move_to([sphere_center[0] * 1.1, sphere_center[1] * 1.1, -1.95])
shadow2.stretch(0.15, 1) # Flatten to look like it's on the ground
self.play(FadeIn(shadow1), FadeIn(shadow2))
self.wait(2)
# Explain the shadow comparison process
comparison_text = VGroup(
Text("For each fragment in camera view:", font_size=22),
Text("1. Transform to light space", font_size=22),
Text("2. Compare with stored depth", font_size=22),
Text("3. If fragment's depth > stored depth → shadow", font_size=22),
).arrange(DOWN, aligned_edge=LEFT, buff=0.3)
comparison_text.next_to(camera_pov_frame, RIGHT).shift(UP)
self.play(Write(comparison_text), run_time=3)
self.wait(2)
# Show comparison example for a specific point
point_in_shadow = Dot(camera_view_sphere.get_center() + DOWN * 0.3, color=RED)
point_transform_eq = Tex(r"P_{light} = M_{light} \cdot P_{world}", font_size=26)
point_transform_eq.next_to(comparison_text, DOWN, buff=0.5)
depth_compare_eq = Tex(r"6.2 > 5.7 \Rightarrow \text{in shadow}", font_size=26)
depth_compare_eq.next_to(point_transform_eq, DOWN, buff=0.3)
depth_compare_eq.set_color(RED)
self.play(FadeIn(point_in_shadow), Write(point_transform_eq))
self.wait()
self.play(Write(depth_compare_eq))
self.wait(2)
# Apply shadowing to the camera view
camera_view_shadow = Circle(
radius=0.65, fill_color=BLACK, fill_opacity=0.7, stroke_width=0
)
camera_view_shadow.move_to(camera_view_sphere.get_center())
camera_view_shadow.scale(0.8) # Slightly smaller to show edge
self.play(FadeIn(camera_view_shadow))
self.wait(2)
# -----------------------
# CONCLUSION
# -----------------------
conclusion_text = Text(
"Shadow Mapping: Dynamic Shadow Generation in Real-time", font_size=28
)
conclusion_text.to_edge(DOWN)
self.play(Write(conclusion_text))
self.wait(2)
# Final fade out
self.play(*[FadeOut(mob) for mob in self.mobjects])
self.wait()
class CompleteDemo(Scene):
def construct(self):
title = Text("Advanced Shadow Mapping Techniques")
title.scale(1.2)
self.play(Write(title))
self.wait()
# Create techniques text more manually since we need to avoid VGroup
advanced_techniques = [
Text("• Percentage Closer Filtering (PCF)", font_size=30),
Text("• Variance Shadow Maps (VSM)", font_size=30),
Text("• Cascaded Shadow Maps (CSM)", font_size=30),
Text("• Moment Shadow Mapping", font_size=30),
Text("• Screen Space Shadows", font_size=30),
]
# Position the texts manually
for i, text in enumerate(advanced_techniques):
text.move_to(UP * (1.5 - i * 0.7))
text.align_to(LEFT * 3, LEFT)
# Fade in each technique
for technique in advanced_techniques:
self.play(FadeIn(technique))
self.wait(0.5)
self.wait()
# Show some benefits
benefits = Text(
"Benefits:\n"
+ "• Reduced shadow acne\n"
+ "• Better quality soft shadows\n"
+ "• More efficient for large scenes\n"
+ "• Higher quality at screen edges",
font_size=24,
)
benefits.to_corner(DR)
self.play(Write(benefits))
self.wait(2)
# Final thank you
thanks = Text("Thank you for watching!")
thanks.scale(1.5)
self.play(
FadeOut(title),
*[FadeOut(tech) for tech in advanced_techniques],
FadeOut(benefits),
run_time=1,
)
self.play(Write(thanks))
self.wait(2)
self.play(FadeOut(thanks))
self.wait()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment