easyshader: Examples
easyshader is a tool for rendering 3D scenes, exporting .ply files for 3D printing and creating animations, powered by Signed Distance Fields (SDFs) and written in Python/Taichi.
It was created to enable drawing 3D shapes using a very concise syntax, and is packed with 3D primitives, transformations and smooth operators.
Basic usage
# If you're running this from Google Colab or you simply don't have easyshader installed, you can install it by running: #%pip install git+https://github.com/marceloprates/easyshader from easyshader import *
[Taichi] version 1.7.3, llvm 15.0.7, commit 5ec301be, osx, python 3.9.6
[I 07/12/26 12:07:52.718 15408805] [shell.py:_shell_pop_print@23] Graphical python shell detected, using wrapped sys.stdout
[Taichi] Starting on arch=metal
objc[4260]: Class GLFWWindowDelegate is implemented in both /Users/marceloprates/Projects/active/art/easyshader/.venv/lib/python3.9/site-packages/taichi/_lib/core/taichi_python.cpython-39-darwin.so (0x1179485e8) and /Users/marceloprates/Projects/active/art/easyshader/.venv/lib/python3.9/site-packages/open3d/cpu/pybind.cpython-39-darwin.so (0x1468eada8). This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.
objc[4260]: Class GLFWApplicationDelegate is implemented in both /Users/marceloprates/Projects/active/art/easyshader/.venv/lib/python3.9/site-packages/taichi/_lib/core/taichi_python.cpython-39-darwin.so (0x1179485c0) and /Users/marceloprates/Projects/active/art/easyshader/.venv/lib/python3.9/site-packages/open3d/cpu/pybind.cpython-39-darwin.so (0x1468eae20). This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.
objc[4260]: Class GLFWContentView is implemented in both /Users/marceloprates/Projects/active/art/easyshader/.venv/lib/python3.9/site-packages/taichi/_lib/core/taichi_python.cpython-39-darwin.so (0x117948638) and /Users/marceloprates/Projects/active/art/easyshader/.venv/lib/python3.9/site-packages/open3d/cpu/pybind.cpython-39-darwin.so (0x1468eae48). This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.
objc[4260]: Class GLFWWindow is implemented in both /Users/marceloprates/Projects/active/art/easyshader/.venv/lib/python3.9/site-packages/taichi/_lib/core/taichi_python.cpython-39-darwin.so (0x1179486b0) and /Users/marceloprates/Projects/active/art/easyshader/.venv/lib/python3.9/site-packages/open3d/cpu/pybind.cpython-39-darwin.so (0x1468eaec0). This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed.
/Users/marceloprates/Projects/active/art/easyshader/.venv/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
Use the "color" parameter to paint your object
easyshader primitives
You can choose from the following primitives:
- Box
- BoxFrame
- Callable
- Cone
- Cyllinder
- Icosahedron
- Iterable
- Line
- Number
- Octahedron
- Shape
- Sphere
- Torus
for obj in [Sphere(1), Cyllinder(1,1), Cone(1,2), Torus(1,.2), Box(1), BoxFrame(1,.1), Icosahedron(1), Octahedron(1)]: display(obj.paint('#0B4F6C'))
Exporting to .ply for usage in Blender or 3D printing
Export your creations to polygon meshes for 3d printing or rendering on external apps (e.g. Blender)
#Icosahedron(1).to_mesh(simplify = 20, save_path='icosahedron.ply')Color your creations using functions defined over x,y,z and a color palette:
palette = ['#B80C09','#0B4F6C','#01BAEF','#FBFBFF','#040F16'] x = Box(.8,'palette(6*(x+y+z))',palette = palette) x = x.isometric() x
Binary operations
Union:
BoxFrame(1,.1,'#0B4F6C') + Sphere(.5,'#B80C09')
Difference:
Box(1,'#0B4F6C') - Sphere(1.2)
Intersection:
Icosahedron(1,'#0B4F6C') & Sphere(1.1)
Examples
A coffee cup!
x = Sphere(1, 'palette(6*(x+y+z))', palette = palette) x = x.twist(4) x &= Cyllinder(.5,.5) x -= Cyllinder(.4,.5) + 'dy .1' x += (Torus(.3,.05) & Shape('-x')) + 'dx .5' x = x.isometric() x += 'rx -pi/3' x
Create videos!
Use the 't' (time) variable to control the animation
x = BoxFrame(1,.1,'palette(6*t + 6*(x+y+z))',palette = palette) x += '.1*sin(t)' x += 'ry t' x += 'rx t' x.animate(frames = 60, framerate = 15, iterations = 1000)
Animating..: 100%|██████████| 59/59 [06:53<00:00, 7.01s/it]
Transformations
Translation
Scale
Rotation
Advanced transformations
You can use x,y,z (and the time parameter, t) as variables in transformations such as translation, rotation, scale
BoxFrame(1,.1,'#f44') + 'dx .2*y'
Other operations
Twist along an axis
Box(1,'#f44').twist(2,'y')
Create an "onion" shape
# Create an onion from a box x = Box(1,'#f44').onion() # Cut a hole in the onion x &= Shape('z') x
Smooth operators
Smooth union
sphere = (Sphere(.5,'#f44') + 'dx -.5') box = (Box(.5,'#4ff') + 'dx +.5') # Normal union display(sphere + box) # Smooth union display(sphere <<su(.5)>> box)
Smooth difference
sphere = Sphere(1.1) box = Box(1,'#f44') # Normal difference display(box - sphere) # Smooth difference display(box <<sd(.5)>> sphere)
Smooth intersection
sphere = Sphere(1) box = Box(.9,'#f44') # Normal intersection display(box & sphere) # Smooth intersection display(box <<si(.5)>> sphere)
Use custom taichi functions!
@ti.func def mandelbulb_fn(p,max_it,k): z, dr, r = p, 1., 0. for i in range(max_it): r, steps = z.norm(), i if r > 4.0: break; # convert to polar coordinates theta = ti.acos(z.z/r) phi = ti.atan2(z.y,z.x) dr = r**2 * 3 * dr + 1.0 # scale and rotate the point zr = r**3 theta = theta*3 phi = phi*3 # convert back to cartesian coordinates z = zr*ti.Vector([ ti.sin(theta)*ti.cos(phi), ti.sin(phi)*ti.sin(theta), ti.cos(theta) ]) z+=p out = ti.log(r)*r/dr if k==1: out = r return out # Create mandelbulb shape mandelbulb = Shape( # Call 'mandelbulb_fn' to compute the SDF sdf = '.2*mandelbulb_fn(p,10,0)', #color = 'palette(200*mandelbulb_fn(1.1*p))', color = 'palette(12*mandelbulb_fn(p,10,1))', palette = ['#0C0F0A', '#FBFF12', '#FF206E', '#41EAD4', '#FFFFFF'], # Pass 'mandelbulb_fn' as a keyword argument mandelbulb_fn = mandelbulb_fn, ) mandelbulb = mandelbulb.isometric() # Render (for poster-quality output, use resolution=(2480,3508)) import time t0 = time.time() img = mandelbulb.render( resolution=(640, 904), fov=.25, max_raymarch_steps=200, iterations=200, verbose=False ) print(f"Rendered in {time.time()-t0:.0f}s") img































