A growing collection of useful helpers and fully functional, ready-made abstractions for Threejs. If you make a component that is generic enough to be useful to others, think about making it available here through a PR!
Storybook demos
Storybook code available under .storybook/stories
npm install @pmndrs/vanilla
Basic usage:
import { pcss, ... } from '@pmndrs/vanilla'
Index
Shaders
pcss
type SoftShadowsProps = {
size?: number
samples?: number
focus?: number
}
Injects percent closer soft shadows (pcss) into threes shader chunk.
const reset = pcss({ size: 25, samples: 10, focus: 0 })
The function returns a reset function that can be used to remove the pcss from the shader chunk.
reset(renderer, scene, camera)
Materials
shaderMaterial
Creates a THREE.ShaderMaterial for you with easier handling of uniforms, which are automatically declared as setter/getters on the object and allowed as constructor arguments.
const ColorShiftMaterial = shaderMaterial(
{ time: 0, color: new THREE.Color(0.2, 0.0, 0.1) },
`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
`
uniform float time;
uniform vec3 color;
varying vec2 vUv;
void main() {
gl_FragColor.rgba = vec4(0.5 + 0.3 * sin(vUv.yxx + time) + color, 1.0);
}
`
)
const mesh = new THREE.Mesh(geometry, new ColorShiftMaterial())
TypeScript usage
Uniform types can be inferred from the uniforms
argument or passed as a generic type argument.
type MyMaterialProps = {
time: number,
color: THREE.Color,
map: THREE.Texture | null
}
const MyMaterial = shaderMaterial<MyMaterialProps>(
{
time: 0,
color: new THREE.Color(0.2, 0.0, 0.1)
map: null
},
vertexShader,
fragmentShader
)
const material = new MyMaterial()
material.time
MeshDiscardMaterial
A material that discards fragments. It can be used to render nothing efficiently, but still have a mesh in the scene graph that throws shadows and can be raycast.
const mesh = new THREE.Mesh(geometry, new MeshDiscardMaterial())
MeshTransmissionMaterial
An improved THREE.MeshPhysicalMaterial. It acts like a normal PhysicalMaterial in terms of transmission support, thickness, ior, roughness, etc., but has chromatic aberration, noise-based roughness blur, (primitive) anisotropicBlur support, and unlike the original it can "see" other transmissive or transparent objects which leads to improved visuals.
Although it should be faster than MPM keep in mind that it can still be expensive as it causes an additional render pass of the scene. Low samples and low resolution will make it faster. If you use roughness consider using a tiny resolution, for instance 32x32 pixels, it will still look good but perform much faster.
For performance and visual reasons the host mesh gets removed from the render-stack temporarily. If you have other objects that you don't want to see reflected in the material just add them to the parent mesh as children.
export type MeshTransmissionMaterialProps = {
_transmission?: number
thickness?: number
roughness?: number
chromaticAberration?: number
anisotropicBlur?: number
distortion?: number
distortionScale: number
temporalDistortion: number
}
const material = new MeshTransmissionMaterial({
_transmission: 1,
thickness: 0,
roughness: 0,
chromaticAberration: 0.03,
anisotropicBlur: 0.1,
distortion: 0,
distortionScale: 0.5,
temporalDistortion: 0.0,
})
SpotLight
A Volumetric spotlight.
const material = new SpotLightMaterial({
opacity: 1,
attenuation: 2.5,
anglePower: 12,
spotPosition: new Vector3(0, 0, 0),
lightColor: new Color('white'),
cameraNear: 0,
cameraFar: 1,
depth: null,
resolution: new Vector2(0, 0),
})
Optionally you can provide a depth-buffer which converts the spotlight into a soft particle.
MeshReflectorMaterial
Easily add reflections and/or blur to any mesh. It takes surface roughness into account for a more realistic effect. This material extends from THREE.MeshStandardMaterial and accepts all its props.
AccumulativeShadows
A planar, Y-up oriented shadow-catcher that can accumulate into soft shadows and has zero performance impact after all frames have accumulated. It can be temporal, it will accumulate over time, or instantaneous, which might be expensive depending on how many frames you render.
Refer to storybook code on how to use & what each variable does
Caustics
drei counterpart
Caustics are swirls of light that appear when light passes through transmissive surfaces. This component uses a raymarching technique to project caustics onto a catcher plane. It is based on github/N8python/caustics.
type CausticsProps = {
frames?: number
causticsOnly: boolean
backside: boolean
ior?: number
backsideIOR?: number
worldRadius?: number
intensity?: number
color?: THREE.Color
resolution?: number
lightSource?: <THREE.Vector3>| <THREE.Object3D>
far?: number
}
It will create a transparent plane that blends the caustics of the objects it receives into your scene. It will only render once and not take resources any longer!
Make sure to configure the props above as some can be micro fractional depending on the models (intensity, worldRadius, ior and backsideIOR especially).
The light source can either be defined by Vector3 or by an object3d. Use the latter if you want to control the light source, for instance in order to move or animate it. Runtime caustics with frames set to Infinity
, a low resolution and no backside can be feasible.
let caustics = Caustics(renderer, {
frames: Infinity,
resolution: 1024,
worldRadius: 0.3,
...
})
scene.add(caustics.group)
caustics.scene.add(yourMesh)
caustics.update()
caustics.scene.add(caustics.helper)
Caustics function returns the following
export type CausticsType = {
scene: THREE.Scene
group: THREE.Group
helper: THREE.CameraHelper
params: CausticsProps
update: () => void
normalTarget: THREE.WebGLRenderTarget
normalTargetB: THREE.WebGLRenderTarget
causticsTarget: THREE.WebGLRenderTarget
causticsTargetB: THREE.WebGLRenderTarget
}
Grid
drei counterpart
A y-up oriented, shader-based grid implementation.
export type GridProps = {
args?: Array<number>
cellSize?: number
cellThickness?: number
cellColor?: THREE.ColorRepresentation
sectionSize?: number
sectionThickness?: number
sectionColor?: THREE.ColorRepresentation
followCamera?: boolean
infiniteGrid?: boolean
fadeDistance?: number
fadeStrength?: number
}
Usage
grid = Grid({
args: [10.5, 10.5],
cellSize: 0.6,
cellThickness: 1,
cellColor: new THREE.Color('#6f6f6f'),
sectionSize: 3.3,
sectionThickness: 1.5,
sectionColor: new THREE.Color('#9d4b4b'),
fadeDistance: 25,
fadeStrength: 1,
followCamera: false,
infiniteGrid: true,
})
scene.add(grid.mesh)
grid.update(camera)
Grid function returns the following
export type GridType = {
mesh: THREE.Mesh
update: (camera: THREE.Camera) => void
}