These demos are real, you can click them! They contain the full code, too. 📦
lamina
let's you create materials with a declarative, system of layers. Layers make it incredibly easy to stack and blend effects. This approach was first made popular by the Spline team.
import { LayerMaterial, Base, Depth } from 'lamina'
function GradientSphere() {
return (
<Sphere>
<LayerMaterial>
<Base color="#ffffff" alpha={1} mode="normal" />
<Depth
colorA="#810000"
colorB="#ffd0d0"
alpha={1}
mode="multiply"
near={0}
far={2}
origin={[1, 1, 1]}
/>
</LayerMaterial>
</Sphere>
)
}
Show Vanilla example
Lamina can be used with vanilla Three.js. Each layer is just a class.
import { LayerMaterial, Base, Depth } from 'lamina/vanilla'
const geometry = new THREE.SphereGeometry(1, 128, 64)
const material = new LayerMaterial({
layers: [
new Base({
color: '#d9d9d9',
alpha: 1,
mode: 'normal',
}),
new Depth({
colorA: '#002f4b',
colorB: '#f2fdff',
alpha: 1,
mode: 'multiply',
near: 0,
far: 2,
origin: new THREE.Vector3(1, 1, 1),
}),
],
})
const mesh = new THREE.Mesh(geometry, material)
Here are the layers that laminia currently provides
Name | Function |
---|---|
Base |
Flat color |
Depth |
Depth based gradient |
Fresnel |
Fresnel shading (strip or rim-lights) |
Noise |
White, perlin or simplex noise |
Normals |
Visualize vertex normals |
Texture |
Image texture |
Name | Function |
---|---|
normal |
opaque |
switch |
skip layer |
add |
prev layer + current |
subtract |
prev layer - current |
multiply |
prev layer * current |
divide |
prev laywer / current |
addsub |
prev layer > 0.5 ? prev layer + current : prev layer - current |
lighten |
lighter pixels only |
darken |
darker pixels only |
screen |
... |
overlay |
... |
softlight |
... |
You can write your own layers by extending the Abstract
class.
class CustomLayer extends Abstract {
// Name of your layer
name: string = 'CustomLayer'
// Default blend mode
mode: BlendMode = 'normal'
// Give it an ID
protected uuid: string = Abstract.genID()
// Define your own uniforms
uniforms: {
[key: string]: IUniform<any>
}
constructor(props?: CustomLayerProps) {
super()
const { customUniform } = props || {}
// Make your uniforms unique in the layer
// stack by appending the ID of the layer to it.
this.uniforms = {
[`u_${this.uuid}_customUniform`]: {
value: customUniform ?? defaultValue,
},
// We recommend having an alpha defined
[`u_${this.uuid}_alpha`]: {
value: 1,
},
}
}
// Return a shader chunk that describes your variable
// in the Fragment shader.
getFragmentVariables() {
return /* glsl */ `
// Lets assume this is a color
uniform vec3 u_${this.uuid}_customUniform;
uniform float u_${this.uuid}_alpha;
`
}
// Return an shader chunk with your layer's implementation.
// Parameter `e` is the result of the previous layer.
// `sc_blend` is a blending function.
//
// vec4 e = vec4(0.);
// ...
// e = sc_blend(previousLayer(e), e, prevBlendMode);
// e = sc_blend(currentLayer(e), e, currentBlendMode);
// ...
// gl_FragColor = e;
//
// List of blend modes: https://github.com/pmndrs/lamina/blob/7246a43d411dd2d4c069c134a843a3f0bf40623a/src/types.ts#L3
getFragmentBody(e: string) {
return /* glsl */ `
// Make sure to create unique local variables
// by appending the UUID to them
vec3 f_${this.uuid}_color = u_${this.uuid}_customUniform;
${e} = ${this.getBlendMode(
BlendModes[this.mode] as number,
e,
`vec4(u_${this.uuid}_color, u_${this.uuid}_alpha)`
)};
`
}
// Optionals
// Return a shader chunk that describes your variables
// in the vertex shader.
// Mostly used to pass varyings to the Fragment shader
getVertexVariables(): string {
return /* glsl */ `
varying vec2 v_${this.uuid}_uv;
`
}
// Return an shader chunk with your layer's of the vertex shader.
// Mostly used to assign varyings to values.
getVertexBody(): string {
return `
v_${this.uuid}_uv = uv;
`
}
// Setters and getters for uniforms
set alpha(v) {
this.uniforms[`u_${this.uuid}_alpha`].value = v
}
get alpha() {
return this.uniforms[`u_${this.uuid}_alpha`].value
}
set customUniform(v) {
this.uniforms[`u_${this.uuid}_customUniform`].value = v
}
get customUniform() {
return this.uniforms[`u_${this.uuid}_customUniform`].value
}
// ...
}