p5.env
v0.0.4
Published
Generative environment light for p5.strands
Readme
p5.env: Generative lighting for p5.strands
This is an experiment around replacing image lighting in p5 with something that is lighter computationally and easier to code by hand for non-photoreal workflows.
Adding the library
Add it via a script tag:
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/p5.env.js"></script>Or for OpenProcessing, you can add it directly via CDN:
https://cdn.jsdelivr.net/npm/[email protected]/p5.env.jsBuilder API
myShader = buildEnvMaterial(() => {
envColor.begin()
const l = envLight(baseColor, envColor.dir, envColor.blur)
l.mix(l.circle(...), lightColor)
envColor.set(l.get())
envColor.end()
})envLight(baseColor, dir, blur) creates a builder. dir and blur are captured once and used by all subsequent method calls.
Shape methods (return an SDF result to pass to mix):
l.circle(center, radius)- spherical cap;centeris a unit vec3,radiusin radiansl.capsule(a, b, radius)- capsule between two unit vec3 endpoints,radiusin radiansl.star(center, n, innerRadius, outerRadius, rotation?)- n-pointed star; radii in radians,nis a plain JS integerl.rect(center, size, rotation?)- rectangle;sizeis[halfWidth, halfHeight]as chord lengths (sine of angle, not radians),rotationin radiansl.window(center, size, panes, barWidth)- rectangle subdivided into panes;panesis[nx, ny];sizeandbarWidthare chord lengths (sine of angle, not radians -- for small shapes the difference is negligible)
Color methods (return a scalar/vec3 usable in expressions or as a base color):
l.gradient(center, ...stops)- radial gradient; each stop is{ t, color }wheretis angle fromcenterin radians andcoloris a vec3; spread stops as individual argumentsl.noise(size)- blur-aware fractal noise value;sizeis the angular scale of the largest octavel.noisePlane(planeNormal, h, size, { rotation?, offset? })- projects a planar noise field onto the sphere;his the plane's height,sizeis the noise scale;offsetis a vec2 that shifts the noise coordinate (use for animation)
Builder methods:
l.mix(shape, color)- blendscolorinto the accumulated result using the shape's SDF; returnslfor chainingl.get()- returns the final accumulated color
Panorama
The same envColor hook can also drive a background panorama, so the environment visible on surfaces matches the environment visible in the background.
const envHooks = () => {
envColor.begin()
// ... same hook body as before ...
envColor.end()
}
let envShader, pano
function setup() {
envShader = buildEnvMaterial(envHooks)
pano = buildEnvPanorama(envHooks)
}
function draw() {
pano()
shader(envShader)
sphere(150)
}buildEnvPanorama returns a function. Calling it each frame sets the camera uniforms and applies the panorama as a post-process filter. Pass a blur value in radians to match a rough material's specular blur: pano(blur).
How does it work?
A representation for lighting has to be both expressive, and also cheaply blurrable for different levels of roughness. Since this is intended for generative art, is is not essential that it perfectly sum the energy coming in from different angles; it just has to have that general look. The Phong lighting model in p5.js can cheaply be evaluated at different roughness levels, but isn't quite expressive enough to create scenes with. Image lighting is more expressive -- you can create an image and draw to it with whatever you want -- but all the convolutions required to make the different roughness levels are prohibitively expensive to animate environment lighting.
The solution I'm working with here is based on angular signed distance functions, creating 2D shapes that are mapped onto an infinitely large sphere around a subject.
The angular SDFs here take in a surface normal and return two things:
- distance: the distance in radians to the edge of a shape
- thickness: the radius of the largest circle that can be inscribed within the shape
