# Stencil Mask

Using the stencil mask to cut out areas of the screen.

Demo: https://pmndrs.github.io/examples/examples/stencil-mask
Source: https://github.com/pmndrs/examples/tree/main/examples/stencil-mask
Scaffold: npx degit pmndrs/examples/examples/stencil-mask
Published: 2022-05-11
Authors: Paul Henschel
Tags: stencil, mask
Ported from: https://codesandbox.io/s/z3f2mw
Dependencies: @react-three/drei@10.7.8, @react-three/fiber@9.6.1, leva@^0.10.1, react@19.2.8, react-dom@19.2.8, three@0.165.0
Binary files, in the repository but not below: src/assets/target-stand.gltf

## src/App.tsx

```tsx
import { Suspense, useRef, useState } from "react";
import { Canvas, useFrame, type ThreeElements } from "@react-three/fiber";
import {
  Mask,
  useMask,
  TransformControls,
  Float,
  Environment,
  OrbitControls,
  MeshDistortMaterial,
  ContactShadows,
  useGLTF,
} from "@react-three/drei";
import { useControls } from "leva";
import * as THREE from "three";

// From the poimandres market (https://market.pmnd.rs/), vendored locally since the original CDN is offline.
import targetModel from "./assets/target-stand.gltf?url";

function MaskedContent({
  invert,
  ...props
}: ThreeElements["group"] & { invert: boolean }) {
  /* The useMask hook has to refer to the mask id defined below, the content
   * will then be stamped out.
   */
  const stencil = useMask(1, invert);
  const group = useRef<THREE.Group>(null!);
  const [hovered, hover] = useState(false);
  useFrame((state) => (group.current.rotation.y = state.clock.elapsedTime / 2));
  return (
    <group {...props}>
      <mesh position={[-0.75, 0, 0]} scale={1} ref={group}>
        <torusKnotGeometry args={[0.6, 0.2, 128, 64]} />
        <meshNormalMaterial {...stencil} />
      </mesh>
      <mesh
        position={[0.75, 0, 0]}
        onPointerOver={() => hover(true)}
        onPointerOut={() => hover(false)}
      >
        <sphereGeometry args={[0.8, 64, 64]} />
        <meshStandardMaterial
          {...stencil}
          color={hovered ? "orange" : "white"}
        />
      </mesh>
    </group>
  );
}

function Target(props: Omit<ThreeElements["primitive"], "object">) {
  const { scene } = useGLTF(targetModel);
  return <primitive object={scene} {...props} />;
}

export function App() {
  const { invert, colorWrite, depthWrite } = useControls({
    invert: false,
    colorWrite: true,
    depthWrite: false,
  });
  return (
    <Canvas camera={{ position: [0, 0, 5] }} gl={{ stencil: true }}>
      <hemisphereLight intensity={Math.PI} groundColor="red" />
      <Suspense fallback={null}>
        <Float floatIntensity={5} rotationIntensity={2} speed={10}>
          {/* Mask sets the shape of the area that is shown, and cuts everything else out.
           * This is valid only for meshes that use useMask with the same id, everything else
           * is not affected.
           */}
          <Mask
            id={1}
            colorWrite={colorWrite}
            depthWrite={depthWrite}
            position={[-1.1, 0, 0]}
          >
            <ringGeometry args={[0.5, 1, 64]} />
          </Mask>
        </Float>

        <TransformControls position={[1.1, 0, 0]}>
          {/* You can build compound-masks using the same id. Masks are otherwise the same as
           *  meshes, you can deform or transition them any way you like
           */}
          <Mask id={1} colorWrite={colorWrite} depthWrite={depthWrite}>
            <planeGeometry args={[2, 2, 128, 128]} />
            <MeshDistortMaterial distort={0.5} radius={1} speed={10} />
          </Mask>
        </TransformControls>

        <MaskedContent invert={invert} />
        <Target position={[0, -1, -3]} scale={1.5} />
        <ContactShadows
          frames={1}
          scale={10}
          position={[0, -1, 0]}
          blur={8}
          opacity={0.55}
        />
        <Environment preset="city" />
        <OrbitControls makeDefault />
      </Suspense>
    </Canvas>
  );
}
```

## src/index.tsx

```tsx
import { createRoot } from "react-dom/client";
import "./styles.css";
import { App } from "./App";

createRoot(document.getElementById("root")!).render(<App />);
```

## src/styles.css

```css
* {
  box-sizing: border-box;
}

html,
body,
#root {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

body {
  background: white;
}
```

## src/vite-env.d.ts

```ts
/// <reference types="vite/client" />
```
