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.
# 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 *
Sphere(1)
Use the "color" parameter to paint your object
Icosahedron(1,'#f55')
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'))
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.obj')
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
BoxFrame(1,.1,'#0B4F6C') + Sphere(.5,'#B80C09')
Box(1,'#0B4F6C') - Sphere(1.2)
Icosahedron(1,'#0B4F6C') & Sphere(1.1)
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
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 [08:59<00:00, 9.14s/it]
Sphere(1) + 'dx .5'
Sphere(1) + '(.1,.2,.3)'
Sphere(1) * .2
Sphere(1) * (1,.2,1)
Box(1) + 'rx pi/4'
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'
Box(1,'#f44').twist(2,'y')
# Create an onion from a box
x = Box(1,'#f44').onion()
# Cut a hole in the onion
x &= Shape('z')
x
sphere = (Sphere(.5,'#f44') + 'dx -.5')
box = (Box(.5,'#4ff') + 'dx +.5')
# Normal union
display(sphere + box)
# Smooth union
display(sphere <<su(.5)>> box)
sphere = Sphere(1.1)
box = Box(1,'#f44')
# Normal difference
display(box - sphere)
# Smooth difference
display(box <<sd(.5)>> sphere)
sphere = Sphere(1)
box = Box(.9,'#f44')
# Normal intersection
display(box & sphere)
# Smooth intersection
display(box <<si(.5)>> sphere)
@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()
# Only run the lines below if you have a very powerful GPU!
#mandelbulb.render(
# resolution = (2480,3508),
# fov = .25,
# max_raymarch_steps = 200,
# iterations = 1000,
# verbose = True
#)
Rendering scene...: 100%|██████████| 1000/1000 [20:02<00:00, 1.20s/it]