Skip to content

marceloprates/easyshader

Repository files navigation

easyshader

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.

Run in Google Colab

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 *
Sphere(1)

png

Use the "color" parameter to paint your object

Icosahedron(1,'#f55')

png

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'))

png

png

png

png

png

png

png

png

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.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

png

Binary operations

Union:

BoxFrame(1,.1,'#0B4F6C') + Sphere(.5,'#B80C09')

png

Difference:

Box(1,'#0B4F6C') - Sphere(1.2)

png

Intersection:

Icosahedron(1,'#0B4F6C') & Sphere(1.1)

png

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

png

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 [08:59<00:00,  9.14s/it]

png

Transformations

Translation

Sphere(1) + 'dx .5'

png

Sphere(1) + '(.1,.2,.3)'

png

Scale

Sphere(1) * .2

png

Sphere(1) * (1,.2,1)

png

Rotation

Box(1) + 'rx pi/4'

png

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'

png

Other operations

Twist along an axis

Box(1,'#f44').twist(2,'y')

png

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

png

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)

png

png

Smooth difference

sphere = Sphere(1.1)
box = Box(1,'#f44')

# Normal difference
display(box - sphere)

# Smooth difference
display(box <<sd(.5)>> sphere)

png

png

Smooth intersection

sphere = Sphere(1)
box = Box(.9,'#f44')

# Normal intersection
display(box & sphere)

# Smooth intersection
display(box <<si(.5)>> sphere)

png

png

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()

# 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]

png

About

Easy-to-use Signed Distance Field (SDF) library

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages