drei (@react-three/drei)

repository·master·Indexed 11 days ago

https://github.com/pmndrs/drei

A collection of useful helpers and ready-made abstractions for @react-three/fiber, designed to simplify common tasks in React-based 3D development. It provides components such as AsciiRenderer, Billboard, Clone, ComputedAttribute, Decal, and Edges to streamline the creation of 3D scenes.

Tokens
83.9K
Snippets
283
Records
361
Agent score
93%

What's inside drei

  1. Use the Effects abstraction for post-processing

    master

    The Effects component provides an abstraction around Three.js's EffectComposer. It simplifies the management of post-processing passes by automatically handling the attachment of children to the composer.

    Key Behaviors

    • Automatic Setup: By default, it prepends a render-pass and a gammacorrection-pass to the effect chain.
    • Automatic Attachment: Children of the <Effects /> component are automatically cloned and have the attach property applied to them.
    • Usage Restriction: Only passes or effects should be used as children within the <Effects /> component.
    • Render Target: By default, it creates a render target using HalfFloatType and RGBAFormat, though these can be customized via props.
    import { SSAOPass } from "three-stdlib"
    import { extend, useThree } from "@react-three/fiber"
    import { Effects } from "@react-three/drei"
    
    // Register the pass so it can be used as a JSX element
    extend({ SSAOPass })
    
    function Scene() {
      const { scene, camera } = useThree()
    
      return (
        <Effects multisamping={8} renderIndex={1} disableGamma={false} disableRenderPass={false} disableRender={false}>
          <SSAOPass args={[scene, camera, 100, 100]} kernelRadius={1.2} kernelSize={0} />
        </Effects>
      )
    }
  2. How the Mask component works

    master

    The Mask component uses the stencil buffer to cut out specific areas of the screen. This approach is generally more performant than using createPortal or double renders because it leverages the GPU's stencil buffer rather than re-rendering the scene.

    To use a mask, you follow a two-step process:

    1. Define the mask: Render a <Mask /> component containing the geometry and material that defines the shape of the cutout.
    2. Apply the mask: Use the useMask hook in a different component to retrieve stencil properties, then spread those properties onto a material to apply the cutout effect.
    // 1. Define the mask shape
    <Mask id={1}>
      <planeGeometry />
      <meshBasicMaterial />
    </Mask>
    
    // 2. Apply the mask to content
    const stencil = useMask(1)
    return (
      <mesh>
        <torusKnotGeometry />
        <meshStandardMaterial {...stencil} />
      </mesh>
    )
  3. Film contents into a RenderTarget using PerspectiveCamera

    master

    The PerspectiveCamera can be used to render contents into a Frame Buffer Object (FBO), similar to how CubeCamera works.

    To use this mode, pass a function as a child. This function receives a THREE.Texture as its first argument. The component handles the rendering lifecycle: the meshes returned by your function will be hidden during the FBO render pass to prevent them from interfering with the texture being captured.

    <PerspectiveCamera position={[0, 0, 10]}>
      {(texture) => (
        <mesh geometry={plane}>
          <meshBasicMaterial map={texture} />
        </mesh>
      )}
    </PerspectiveCamera>
  4. Occlude HTML content behind 3D geometry

    master

    You can make HTML elements hide behind 3D objects using the occlude prop.

    1. Standard Occlusion: Pass true or a Ref<Object3D>[] to occlude. When the HTML is hidden, the component sets the opacity prop of the innermost div to 0. You can listen to these changes via the onOcclude callback to trigger custom animations.
    2. Blending Mode: Use occlude="blending" to make the HTML hide behind geometry as if it were a physical part of the 3D scene. This mode works best with rectangular elements, but you can use the geometry prop to provide a custom shape.

    Customizing Occlusion with Materials: You can provide a Three.js material via the material prop to control how the occlusion looks. This is required if you want to enable shadows.

    // Standard occlusion with custom animation
    const [hidden, setHidden] = useState(false)
    
    <Html
      occlude
      onOcclude={setHidden}
      style={{
        transition: 'all 0.5s',
        opacity: hidden ? 0 : 1,
        transform: `scale(${hidden ? 0.5 : 1})`
      }}
    />
    
    // Blending mode (real 3D occlusion)
    <Html occlude="blending" />
    
    // Occlusion with custom material and shadows
    <Html
      occlude
      castShadow
      receiveShadow
      material={
        <meshPhysicalMaterial
          side={DoubleSide}
          opacity={0.1}
        />
      }
    />
  5. Avoid control interference with OrbitControls

    master

    When using TransformControls alongside other camera controls (like OrbitControls or TrackballControls), the controls may interfere with each other (e.g., dragging the gizmo might also rotate the camera).

    To prevent this, use the makeDefault prop on your camera controls. TransformControls will automatically detect this and temporarily disable the default controls while the user is interacting with the transform gizmo.

    <TransformControls mode="translate" />
    <OrbitControls makeDefault />
  6. How PresentationControls differ from OrbitControls

    master
    Unlike OrbitControls, which directly turn the camera, PresentationControls spins the contents of the component. It uses spring-physics for movement, meaning it does not abruptly stop at limits but instead smoothly anticipates the stopping position. It also supports polar zoom and snap-back functionality.
  7. Depth sort multiple Splats

    master

    When rendering multiple <Splat /> components, you must handle depth sorting to ensure they overlap correctly. You have two primary methods:

    1. Using alphaTest

    Set alphaTest to a low value (e.g., 0.1). This is a lightweight approach but may show a slight outline under certain viewing conditions.

    <Splat alphaTest={0.1} src="foo.splat" />
    <Splat alphaTest={0.1} src="bar.splat" />

    2. Using alphaHash

    Enable alphaHash on the splat. This is more robust for depth sorting but can be slower and introduce visual noise. It is recommended to use a TAA (Temporal Anti-Aliasing) pass in post-processing to clean up the noise. You do not need to enable alphaHash on all splats.

    // Using alphaTest for depth sorting
    <Splat alphaTest={0.1} src="foo.splat" />
    <Splat alphaTest={0.1} src="bar.splat" />
    
    // Using alphaHash for depth sorting
    <Splat alphaHash src="foo.splat" />
  8. How View works in @react-three/drei

    master

    The View component allows you to render multiple independent 3D scenes within a single, performant <Canvas>. It uses gl.scissor to cut the viewport into segments, tying each view to a specific HTML element in your DOM tree. This enables 3D content to follow HTML elements as they scroll, resize, or move.

    Key Concepts:

    • DOM Integration: You place <View> components directly into your HTML/DOM graph where you want them to appear. View is an unstyled HTML element (defaulting to a div).
    • The Port: To actually render the content of these views, you must place a <View.Port /> component inside your <Canvas>.
    • Event System: For best results, connect the Canvas event system to a parent element that contains both the <Canvas> and the HTML content using the eventSource prop on the <Canvas>.
    • Performance: If a view's position is static, you can set the frames prop to 1 to avoid the overhead of calling getBoundingClientRect every frame.

    Requirements:

    • @react-three/fiber version ^8.1.0 or newer is required if the canvas/fiber root is not fullscreen.
    return (
      <main ref={container}>
        <h1>Html content here</h1>
        <View style={{ width: 200, height: 200 }}>
          <mesh geometry={foo} />
          <OrbitControls />
        </View>
        <View className="canvas-view">
          <mesh geometry={bar} />
          <CameraControls />
        </View>
        <Canvas eventSource={container}>
          <View.Port />
        </Canvas>
      </main>
    )
  9. Optimize CubeCamera for static vs moving objects

    master

    When using CubeCamera, choose your configuration based on the scene dynamics:

    For Static Objects

    If your objects are not moving, you can render a fixed number of frames to save performance. For example, if you have two static objects that need to reflect each other, setting frames={2} ensures both objects are captured in the reflection pass.

    For Moving Objects

    If objects in the scene are moving, you should allow the camera to render indefinitely by unsetting the frames prop. To maintain performance while rendering every frame, consider reducing the resolution prop.

  10. Use PivotControls as a controlled component

    master

    To use PivotControls as a controlled component, set autoTransform={false}. In this mode, you are responsible for applying the matrix transform yourself. You can use the onDrag callback to retrieve the new matrix and apply it to your object or a foreign object (an object not parented within the PivotControls component).

    const matrix = new THREE.Matrix4()
    return (
      <PivotControls
        ref={ref}
        matrix={matrix}
        autoTransform={false}
        onDrag={({ matrix: matrix_ }) => matrix.copy(matrix_)}
      />
    )
  11. How Drei controls work

    master

    Drei controls are wrappers around THREE.Controls. When damping is enabled, they manage their own updates and automatically remove themselves from the scene when unmounted. They are compatible with the frameloop="demand" canvas flag.

    Crucially, controls are the first effects to run before other useFrame hooks. This execution order ensures that other components can mutate the camera on top of the controls' updates if needed.

  12. Create custom gizmos with useGizmoContext

    master
    The useGizmoContext hook allows you to create your own custom gizmo components that work within the GizmoHelper ecosystem. This is useful if you want to build specialized visualizers that respond to the camera state managed by the helper.