@pmndrs/vanilla Documentation

repository·main·Indexed 20 days ago

https://github.com/pmndrs/drei-vanilla

A collection of drei-inspired helpers and ready-made abstractions for Three.js in vanilla JavaScript/TypeScript environments. It provides advanced features including soft shadows (PCSS), specialized materials (MeshTransmissionMaterial, MeshReflectorMaterial, MeshDistortMaterial), volumetric lighting, caustics, and scene elements like Clouds, Stars, Sparkles, and a shader-based Grid.

Tokens
20.3K
Snippets
60
Records
87
Agent score
67%

What's inside @pmndrs/vanilla

  1. Integrate Caustics with frontend frameworks

    main

    When using frontend frameworks (like React), standard construction might not handle lifecycle changes or prop updates correctly. Use these exported symbols to bridge the integration:

    • CausticsProjectionMaterial: A material that projects the caustics onto the catcher plane.
    • CausticsMaterial: A material that renders the caustics.
    • createCausticsUpdate: A function that accepts an updateParameters getter to create an update function for your animation loop. This ensures the effect uses the latest state from your framework.
    export function createCausticsUpdate(
      updateParameters: () => {
        params: Omit<CausticsProps, 'color'>
        scene: THREE.Scene
        group: THREE.Group
        camera: THREE.OrthographicCamera
        plane: THREE.Mesh<PlaneGeometry, InstanceInstance<typeof CausticsProjectionMaterial>>
        normalTarget: THREE.WebGLRenderTarget
        normalTargetB: THREE.WebGLRenderTarget
        causticsTarget: THREE.WebGLRenderTarget
        causticsTargetB: THREE.WebGLRenderTarget
        helper?: THREE.CameraHelper | null
      }
    ): (gl: THREE.WebGLRenderer) => void
  2. Load and use Splats

    main

    The Splat abstraction is a declarative wrapper around antimatter15/splat. It supports re-use (multiple splats using the same loaded data), depth sorting, and stream-loading.

    To ensure correct depth sorting between multiple splats, use alphaTest (e.g., { alphaTest: 0.1 }) or alphaHash: true (which may require a TAA pass in post-processing to reduce noise).

    Usage Pattern

    1. Create a SplatLoader with your renderer.
    2. Load .splat files asynchronously using loader.loadAsync(url).
    3. Instantiate Splat objects using the loaded data, the camera, and optional configuration.
    const loader = new SplatLoader(renderer)
    
    const [shoeSplat, plushSplat, kitchenSplat] = await Promise.all([
      loader.loadAsync(`shoe.splat`),
      loader.loadAsync(`plush.splat`),
      loader.loadAsync(`kitchen.splat`),
    ])
    
    const shoe1 = new Splat(shoeSplat, camera, { alphaTest: 0.1 })
    shoe1.position.set(0, 1.6, 2)
    scene.add(shoe1)
    
    // Re-use the same data for a second instance
    const shoe2 = new Splat(shoeSplat, camera, { alphaTest: 0.1 })
    scene.add(shoe2)
    
    // Using alphaHash for better depth sorting (may require TAA)
    const plush = new Splat(plushSplat, camera, { alphaHash: true })
    scene.add(plush)
  3. Use Clouds and individual Cloud segments

    main

    Clouds are implemented using an instanced mesh/particle system. You first create a Clouds container and then add individual Cloud segments to it.

    Workflow:

    1. Create the Clouds group with a texture: new Clouds({ texture: cloudTexture }).
    2. Create a cloud segment: new Cloud().
    3. Add the segment to the group: clouds.add(cloud_0).
    4. If you change parameters on a cloud segment, call cloud_0.updateCloud() to apply changes.
    5. In your animation loop, call clouds.update(camera, clock.getElapsedTime(), clock.getDelta()).
    // create main clouds group
    clouds = new Clouds({ texture: cloudTexture })
    scene.add(clouds)
    
    // create cloud and add it to clouds group
    cloud_0 = new Cloud()
    clouds.add(cloud_0)
    // call "cloud_0.updateCloud()" after changing any cloud parameter to see latest changes
    
    // call in animate loop
    clouds.update(camera, clock.getElapsedTime(), clock.getDelta())
  4. How the Grid update function works

    main

    The update(camera: THREE.Camera) function returned by Grid is essential for visual features that depend on the camera's position relative to the grid plane.

    Specifically, it calculates and updates the worldCamProjPosition and worldPlanePosition uniforms in the grid's shader. This allows the shader to correctly implement:

    • Fading: Calculating distance between the camera and the grid plane.
    • Camera Following: Shifting the grid's world position to follow the camera's projection.
    • Infinite Grid: Adjusting local positions based on camera distance.

    If you do not call update in your animation loop, these effects will not behave correctly as the camera moves.

  5. Custom cloud distribution with the distribute function

    main

    You can override the default random distribution of cloud segments by providing a distribute function to the Cloud constructor. This function is called for each segment and allows you to specify its exact position and volume.

    Function Signature: (cloud: CloudState, index: number) => { point: Vector3; volume?: number }

    • point: A Vector3 where coordinates are typically between -1 and 1.
    • volume: An optional factor (0 to 1) to scale the segment's volume.
    const cloud = new Cloud({
      segments: 30,
      distribute: (state, index) => {
        // Custom logic: place segments in a ring pattern
        const angle = (index / 30) * Math.PI * 2;
        const point = new Vector3(Math.cos(angle), 0, Math.sin(angle));
        return { 
          point, 
          volume: 0.5 
        };
      }
    });
  6. Use the Caustics effect

    main

    The Caustics function creates a light caustic effect by simulating light refraction through objects onto a ground plane. It manages a separate scene, a camera, and several render targets to compute the caustic patterns.

    To use it, call Caustics with a WebGLRenderer and an optional CausticsProps object. You must then manually call the returned update function in your render loop to refresh the caustic textures.

    Integration Steps:

    1. Initialize with Caustics(renderer, props).
    2. Add the returned group to your main scene.
    3. Add the objects you want to refract light to the returned scene.
    4. Call update() inside your animation loop.

    Configuration Options (CausticsProps):

    • frames: Number of frames to render. Set to Infinity for real-time runtime updates. Default: 1.
    • causticsOnly: If true, only the caustics are displayed, skipping the models in the caustic scene. Default: false.
    • backside: If true, includes back faces in the refraction calculation. Default: false.
    • ior: Index of Refraction for front faces. Default: 1.1.
    • backsideIOR: Index of Refraction for back faces (requires backside: true). Default: 1.1.
    • worldRadius: The texel size/scale of the effect. Default: 0.3125.
    • intensity: Intensity of the projected caustics. Default: 0.05.
    • color: Caustics color. Default: white.
    • resolution: Buffer resolution for the FBOs. Default: 2048.
    • lightSource: Position of the light. Can be a THREE.Vector3 or a THREE.Object3D. Default: new THREE.Vector3(1, 1, 1).
    • near: Caustics camera near plane. Default: 0.1.
    • far: Caustics camera far plane. If 0, it is automatically updated. Default: 0.
    import * as THREE from 'three';
    import { Caustics } from '@pmndrs/vanilla';
    
    const renderer = new THREE.WebGLRenderer();
    const caustics = Caustics(renderer, {
      frames: Infinity,
      ior: 1.3,
      intensity: 0.1,
      lightSource: new THREE.Vector3(5, 10, 5)
    });
    
    // 1. Add the caustics group to your main scene
    mainScene.add(caustics.group);
    
    // 2. Add objects that should refract light to the caustics scene
    caustics.scene.add(myRefractiveMesh);
    
    // 3. In your render loop
    function animate() {
      requestAnimationFrame(animate);
      
      // Update the caustics effect
      caustics.update();
      
      renderer.render(mainScene, mainCamera);
    }
  7. Use Stars for a blinking starfield

    main

    The Stars component adds a blinking, shader-based starfield to your scene.

    Usage:

    1. Initialize: const stars = new Stars(starParams).
    2. Add to scene: scene.add(stars).
    3. In the animation loop, call stars.update(elapsedTime).
    const stars = new Stars(starParams)
    scene.add(stars)
    
    // in the update loop
    function animate() {
      stars.update(elapsedTime)
      ...
    }
  8. Use the Billboard abstraction

    main

    A Billboard adds a THREE.Group that always faces the camera. You must call .update(camera) in your animation loop.

    Props

    • follow: Whether to follow the camera, default true
    • lockX, lockY, lockZ: Lock rotation on specific axes, default false
    const billboard = Billboard()
    const mesh = new THREE.Mesh(geometry, material)
    billboard.group.add(mesh)
    
    scene.add(billboard.group)
    
    // call in animate loop
    billboard.update(camera)
  9. Use MeshReflectorMaterial for realistic reflections

    main
    MeshReflectorMaterial allows you to easily add reflections and/or blur to any mesh. It extends THREE.MeshStandardMaterial and accounts for surface roughness to provide realistic effects.