Semester 1, Week 12 Development - 62firelight/manimRT-490 GitHub Wiki
- Changed refraction implementation to use the relatively compressed approach from Ray Tracing in One Weekend (Source)
- As a user I want to...
- get a refracted ray as it passes through another surface with a different refractive index so I can illustrate refraction and how it uses Snell's Law
- illustrate total internal refraction so I can demonstrate what commonly happens in mediums like water
I recorded some test footage which can be found at this link.
Some potential areas of improvement for future videos:
- Speaking style - I recorded this video in a pretty informal way. I read the script in one go and went with the take that had the least mistakes. Maybe a more confident manner of speaking would be better? Something else to consider is that I forgot that my mic settings were set too low when recording. As a result, I had to apply the "Amplify" effect to make myself louder in Audacity, which may have introduced some weird subtle sounds. Also, I should find a way to manage scrolling down the script that I'm reading from without making too much background noise when recording.
- Speed of animations - The animation for the ray is a little fast. I tried to slow this down during video editing but the results didn't look very smooth (most likely due to how the video is 15 fps). Either recording the video at a higher frame-rate or extending the animation in the Manim code could help with slowing down the ray animation. I think every other animation is fine for the most part though.
- Frequency of animations - There are moments of "dead air" where it's just me talking over a still picture, like when I talk about the camera's 2D grid being an image plane. Perhaps some more animations would be good to add here to increase the "punchiness" of the video? I don't want to overdo the animations though, so it seems like balancing animation and commentary will be an important thing to manage when making these videos.
- Rendering speed for rays - There's a point near the end of the video where multiple rays are being shot out of the camera. Admittedly, this was a last-minute addition that I made to show off that you could shoot rays out of all of the pixels in the camera, but I didn't really think through how well it would integrate into the video. More importantly, the time to render these rays was a bit on the longer side, taking 5 and a half minutes to render at 480p 15fps. It's something to keep an eye on for the future, as I don't think the animations that I'm making will involve a ton of rays.
- Ability to colour in pixels - Some early feedback indicated that it would be great to have the ability to colour in pixels on the camera. For example, the pixel for the corresponding ray that hits the red sphere could turn red when I say so. I agree wholeheartedly with this, and have actually got an idea for how to implement it (hint: it involves a recent addition to ManimRT -- planes).
- Efficiently creating animations - It'd be a great idea to have a template (and coding style) for these animations. For example, having an axes with labels really helps me know where to put my objects. Having separate add and play statements for each object is also a very good idea, because I can comment out any objects that I may or may not need in my scene. It helps a lot for keeping frames consistent between the mini video files that Manim creates as well, so I don't have to manually insert or remove the names of objects from an add or play statement.
More refraction testing because you can never be too sure...
Both of the directions for the red rays in the image below were verified to be the same using a print statement.
Code (click to reveal)
from manim import *
from manim_rt.Arc3D import Arc3D
from manim_rt.RTPlane import RTPlane
from manim_rt.RTSphere import RTSphere
from manim_rt.Ray3D import Ray3D
class RefractedRayTest(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=89 * DEGREES, theta=-180 * DEGREES, zoom=1.75)
# Axes for easier placement of objects
axes = ThreeDAxes()
labels = axes.get_axis_labels()
# Planes (maybe the air plane should be a different colour)
water_plane = RTPlane(refractive_index=1.33)
air_plane = RTPlane([0, -1, -1], refractive_index=1)
# Incident ray
ray_start = [-1, 1, 0.5]
ray = Ray3D(ray_start, ORIGIN - ray_start, 1, color=RED)
# Calculate intersection so we can actually get the refracted ray
hit_points = water_plane.get_intersection(ray)
# Unit normal for n1
unit_normal_n1 = Ray3D(hit_points[0], ray.get_unit_normal(0), color=GREEN)
# Angle between unit normal for n1 and incident ray
angle_n1 = Arc3D(ray, unit_normal_n1)
angle_n1_text = MathTex("\\theta_1").next_to(angle_n1.get_center() + 0.1 * UP, OUT)
# Unit normal for n2
unit_normal_n2 = Ray3D(hit_points[0], [0, 0, -1], color=ORANGE)
# Our first refracted ray travelling from air into water
first_refracted_ray = ray.get_refracted_ray(water_plane, color=BLUE, distance=1.5)
# Angle between unit normal for n2 and the first refracted ray
angle_n2 = Arc3D(unit_normal_n2, first_refracted_ray)
angle_n2_text = MathTex("\\theta_2").next_to(angle_n2.get_center() + 0.2 * DOWN, IN)
# Show refractive indices
n1_text = MathTex("n = 1").next_to(unit_normal_n1.get_center(), DOWN, buff=0.35)
n2_text = MathTex("n = 1.33").next_to(first_refracted_ray.get_center(), DOWN, buff=0.75)
# Calculate intersection for refracted ray so we can show the second refracted ray
refracted_ray_hit_points = air_plane.get_intersection(first_refracted_ray)
# Our second refracted ray travelling from water to air
# The direction of this ray should be the same as the previous incident ray
second_refracted_ray = first_refracted_ray.get_refracted_ray(air_plane, refractive_index=1.33, distance=2, color=RED)
# Show last refractive index
n3_text = MathTex("n = 1").next_to(second_refracted_ray.get_center(), IN, buff=0.5)
# Objects
self.add(axes, labels, water_plane, air_plane, ray, first_refracted_ray, unit_normal_n1, unit_normal_n2, angle_n1, angle_n2, second_refracted_ray)
# Text
self.add_fixed_orientation_mobjects(angle_n1_text, angle_n2_text, n1_text, n2_text, n3_text)
Making this image required me to make a change to my code for calculating the refracted ray. Without going into too much detail, a square root of a negative number was the main culprit.
There's no practical reason to have this many rays in the image, but I thought it would look cool.
Code (click to reveal)
from manim import *
from manim_rt.Arc3D import Arc3D
from manim_rt.RTPlane import RTPlane
from manim_rt.RTSphere import RTSphere
from manim_rt.Ray3D import Ray3D
class TotalInternalRefractionTest(ThreeDScene):
def construct(self):
self.set_camera_orientation(phi=88 * DEGREES, theta=-180 * DEGREES, zoom=1.75)
# Axes for easier placement of objects
axes = ThreeDAxes()
labels = axes.get_axis_labels()
# Planes
bottom_air_plane = RTPlane(y_scale=4, refractive_index=1)
top_air_plane = RTPlane(translation=[0, 0, 0.5], y_scale=4, refractive_index=1)
# Incident ray
ray_start = [-1, 2.5, 0.5]
ray = Ray3D(ray_start, np.subtract([0, 1.5, 0], ray_start), 1, color=RED)
# Calculate intersection so we can actually get the refracted ray
bottom_air_plane.get_intersection(ray)
# First (of many) refracted rays that get reflected instead due to Total Internal Reflection
first_refracted_ray = ray.get_refracted_ray(bottom_air_plane, color=GREEN, distance=1.5, refractive_index=1.33)
# Calculate more refracted rays!
top_air_plane.get_intersection(first_refracted_ray)
second_refracted_ray = first_refracted_ray.get_refracted_ray(top_air_plane, color=RED, distance=1.5, refractive_index=1.33)
bottom_air_plane.get_intersection(second_refracted_ray)
third_refracted_ray = second_refracted_ray.get_refracted_ray(bottom_air_plane, color=GREEN, distance=1.5, refractive_index=1.33)
top_air_plane.get_intersection(third_refracted_ray)
fourth_refracted_ray = third_refracted_ray.get_refracted_ray(top_air_plane, color=RED, distance=1.8, refractive_index=1.33)
bottom_air_plane.get_intersection(fourth_refracted_ray)
fifth_refracted_ray = fourth_refracted_ray.get_refracted_ray(bottom_air_plane, color=GREEN, distance=1.5, refractive_index=1.33)
top_air_plane.get_intersection(fifth_refracted_ray)
sixth_refracted_ray = fifth_refracted_ray.get_refracted_ray(top_air_plane, color=RED, distance=1.9, refractive_index=1.33)
# Objects
self.add(bottom_air_plane, top_air_plane, ray, first_refracted_ray, second_refracted_ray, third_refracted_ray, fourth_refracted_ray, fifth_refracted_ray, sixth_refracted_ray)
# Text
n1_text = MathTex("n_1 = 1.33").next_to(ray.get_center(), UP, 1.05)
n2_top_text = MathTex("n_2 = 1").next_to(ORIGIN, OUT, 1)
n2_bottom_text = MathTex("n_2 = 1").next_to(ORIGIN, IN, 0.5)
self.add_fixed_orientation_mobjects(n1_text, n2_top_text, n2_bottom_text)
The image above shows that TIR has a small issue when it comes to finding the right length of the refracted ray. You can see some of the refracted rays intersecting or missing the plane by a little bit. The main reason why this happens is because I'm currently using handpicked values for the visible distance of the refracted ray, increasing or decreasing it a little bit to make it look like it is hitting the plane.
It would be nice to have a way to automatically calculate the right visible distance for the ray so that the ray stops at the right point every time.
Another thing that would be nice would be a way to recalculate the visible distance for the ray without having to create a completely new ray. For example, it'd be great if I could get a longer version of a ray that is too short when rendered.
- Any more feedback for the test footage?
- When it comes to the abstract and introduction of the interim report, how do I not repeat myself?
- Ray tracing, Ray-tracing or Ray-Tracing? What way to write it for consistency?
- I'm a bit worried about the evaluation. Any more thoughts about it?
- Any feedback that can be given on this really rough outline I made for the report:
Outline (click to reveal)
===Start of Outline===
Summary of the major aspects of the report
Introduce the project
Not sure if I should write anything in this in-between section
Stuff about ray-tracing (how it originated and basic rundown of the algorithm)
Stuff about Manim (origin from 3blue1brown and increasing community support over the years)
Brief section about other Manim plug-ins like ManimML and Manim Slides?
Not sure if I should write anything in this in-between section
Introducing concepts for ray-object intersections
Talk about concepts for ray-object intersections (primary ray, intersecting a unit sphere and how to handle transformed spheres)
Talk about the initial version of ManimRT which functioned as a wrapper for Manim rather than integrating into the source code
How I implemented the theory into ManimRT (method I used, issues I ran into, how I overcame the issues, etc)
Introduce how lighting is done in a ray tracer
Talk about concepts for ray-tracing lighting (Phong Illumination Model, shadow rays and how to handle reflections)
How I implemented the theory into ManimRT (method I used, issues I ran into, how I overcame the issues, etc)
Introducing refraction and how painful it can be to implement
Refraction concepts (what happens when a light crosses into another medium and Total Internal Refraction)
How I implemented the theory into ManimRT (method I used, issues I ran into, how I overcame the issues, etc)
Maybe talk about how I've included supplementary material in the form of videos (for example, test footage and the lessons I learned from it)
Go over what will be done in semester 2 (more testing for ManimRT, demonstration videos and the most important part, the evaluation)
===End of Outline===
For the rest of this semester, I think it would be a good idea to focus on polishing the existing ManimRT code (seeing how I've put it off for so long) before proceeding too far with video editing or the interim report.
From highest priority to lowest priority:
- Check TODO comments in code and potentially make changes based on those
- Start on interim report/presentation
- Develop full animation video for illustrating ray-object intersections
- Add a way to colour in pixels on the camera
- Add docstrings to created classes and their methods
- Add option to initialize camera grid with x value and aspect ratio
- Generate spheres that intersect the ray at different points (2 intersection points + 1 intersection + no intersection)