shadimage
gammaui
uivoxel-wallsource: ok · hit

Voxel Wall

A dark voxel tunnel with volumetric light rays, drifting cubes, and mouse-driven camera parallax.

npx shadcn add "https://www.gammaui.com/r/voxel-wall.json"
free

Where it goes

stats

Visual style

dark-futuristic

Interaction

animated-counterparallax

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · heavythree
webglthree@react-three/fiber@react-three/postprocessing

Files4 · source cached 10/15/2026, 5:10:06 AM

registry/gammaui/voxel-wall/scene.tsx
"use client"

import { Suspense, useEffect, useLayoutEffect, useRef, useState } from "react"
import { Canvas } from "@react-three/fiber"
import * as THREE from "three"

import CameraRig from "./camera-rig"
import { DustMotes, LightEffects, LightSource, SceneLights } from "./light-rays"
import VoxelWall from "./wall"

interface VoxelWallSceneProps {
  className?: string
  height?: string | number
}

function SceneContents() {
  const sunRef = useRef<THREE.Mesh>(null!)
  const [sun, setSun] = useState<THREE.Mesh | null>(null)

  useLayoutEffect(() => {
    if (sunRef.current) setSun(sunRef.current)
  }, [])

  return (
    <>
      <SceneLights />
      <VoxelWall />
      <LightSource ref={sunRef} />
      <DustMotes />
      <CameraRig />
      {sun && <LightEffects sun={sun} />}
    </>
  )
}

export default function VoxelWallScene({
  className,
  height = "100%",
}: VoxelWallSceneProps) {
  const [mounted, setMounted] = useState(false)

  useEffect(() => {
    setMounted(true)
  }, [])

  return (
    <div
      className={className}
      style={{ width: "100%", height, background: "#000000" }}
    >
      {mounted ? (
        <Canvas
          dpr={[1, 2]}
          shadows
          camera={{ position: [0.15, -2.35, 4.2], fov: 52, near: 0.1, far: 40 }}
          gl={{
            antialias: false,
            alpha: false,
            toneMapping: THREE.ACESFilmicToneMapping,
            toneMappingExposure: 1.05,
          }}
          style={{ width: "100%", height: "100%" }}
        >
          <color attach="background" args={["#000000"]} />
          <fog attach="fog" args={["#000000", 6, 22]} />
          <Suspense fallback={null}>
            <SceneContents />
          </Suspense>
        </Canvas>
      ) : null}
    </div>
  )
}
registry/gammaui/voxel-wall/wall.tsx
"use client"

import { useEffect, useMemo, useRef } from "react"
import { useFrame } from "@react-three/fiber"
import * as THREE from "three"

const SPACING = 0.44
const CUBE = 0.38
const ROOM_W = 9.2
const ROOM_H = 6.4
const ROOM_D = 10.5

const HOLE_CX = 0.55
const HOLE_CY = 0.85
const HOLE_W = 3.4
const HOLE_H = 2.55

interface CubeInstance {
  x: number
  y: number
  z: number
  rotX: number
  rotY: number
  rotZ: number
  scale: number
  jitter: boolean
  phase: number
  driftSpeed: number
  maxDrift: number
}

const LIGHT_TARGET = {
  x: HOLE_CX,
  y: HOLE_CY,
  z: -ROOM_D - 0.35,
}

function mulberry32(seed: number) {
  return () => {
    seed |= 0
    seed = (seed + 0x6d2b79f5) | 0
    let t = Math.imul(seed ^ (seed >>> 15), 1 | seed)
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296
  }
}

function isInHole(
  x: number,
  y: number,
  z: number,
  rand: () => number
): false | true | "debris" {
  const backZ = -ROOM_D
  if (Math.abs(z - backZ) > SPACING * 0.6) return false

  const dx = Math.abs(x - HOLE_CX)
  const dy = Math.abs(y - HOLE_CY)

  const inside = dx < HOLE_W / 2 && dy < HOLE_H / 2
  if (!inside) return false

  const edgeDist = Math.min(HOLE_W / 2 - dx, HOLE_H / 2 - dy)

  if (edgeDist < SPACING * 0.85) {
    if (rand() < 0.42) return true
    if (rand() < 0.72) return "debris"
    return false
  }

  if (edgeDist < SPACING * 1.6 && rand() < 0.38) return "debris"

  return true
}

function pushCube(
  cubes: CubeInstance[],
  x: number,
  y: number,
  z: number,
  rand: () => number,
  edge = false,
  debris = false
) {
  const spread = edge || debris ? 0.22 : 0.06
  const bx = x + (rand() - 0.5) * spread
  const by = y + (rand() - 0.5) * spread
  const bz = z + (rand() - 0.5) * (debris ? 0.5 : spread)

  const dx = LIGHT_TARGET.x - bx
  const dy = LIGHT_TARGET.y - by
  const dz = LIGHT_TARGET.z - bz
  const dist = Math.hypot(dx, dy, dz)

  cubes.push({
    x: bx,
    y: by,
    z: bz,
    rotX: edge || debris ? (rand() - 0.5) * 0.7 : 0,
    rotY: edge || debris ? (rand() - 0.5) * 0.9 : 0,
    rotZ: edge || debris ? (rand() - 0.5) * 0.55 : 0,
    scale:
      CUBE *
      (debris
        ? 0.65 + rand() * 0.45
        : edge
          ? 0.78 + rand() * 0.35
          : 0.92 + rand() * 0.12),
    jitter: edge || debris,
    phase: rand() * Math.PI * 2,
    driftSpeed: debris
      ? 0.18 + rand() * 0.14
      : edge
        ? 0.1 + rand() * 0.07
        : 0.03 + rand() * 0.025,
    maxDrift: debris ? dist * 0.98 : edge ? dist * 0.55 : dist * 0.18,
  })
}

function generateTunnel(): CubeInstance[] {
  const rand = mulberry32(481516)
  const cubes: CubeInstance[] = []

  const halfW = ROOM_W / 2
  const halfH = ROOM_H / 2
  const backZ = -ROOM_D

  for (let z = 0; z >= backZ; z -= SPACING) {
    for (let x = -halfW; x <= halfW; x += SPACING) {
      const hole = isInHole(x, -halfH, z, rand)
      if (hole === true) continue
      if (hole === "debris") {
        pushCube(cubes, x, -halfH, z + rand() * 0.4, rand, true, true)
        continue
      }
      pushCube(cubes, x, -halfH, z, rand, z < backZ + SPACING * 2)
    }
  }

  for (let z = 0; z >= backZ; z -= SPACING) {
    for (let x = -halfW; x <= halfW; x += SPACING) {
      const hole = isInHole(x, halfH, z, rand)
      if (hole === true) continue
      if (hole === "debris") {
        pushCube(cubes, x, halfH, z + rand() * 0.3, rand, true, true)
        continue
      }
      pushCube(cubes, x, halfH, z, rand)
    }
  }

  for (let z = 0; z >= backZ; z -= SPACING) {
    for (let y = -halfH; y <= halfH; y += SPACING) {
      const hole = isInHole(-halfW, y, z, rand)
      if (hole === true) continue
      if (hole === "debris") {
        pushCube(cubes, -halfW, y, z + rand() * 0.35, rand, true, true)
        continue
      }
      pushCube(cubes, -halfW, y, z, rand, z < backZ + SPACING * 3)
    }
  }

  for (let z = 0; z >= backZ; z -= SPACING) {
    for (let y = -halfH; y <= halfH; y += SPACING) {
      if (Math.abs(y) < 0.2 && z > backZ + SPACING * 4 && rand() < 0.18)
        continue
      const hole = isInHole(halfW, y, z, rand)
      if (hole === true) continue
      if (hole === "debris") {
        pushCube(cubes, halfW, y, z + rand() * 0.35, rand, true, true)
        continue
      }
      pushCube(cubes, halfW, y, z, rand, z < backZ + SPACING * 2)
    }
  }

  for (let y = -halfH; y <= halfH; y += SPACING) {
    for (let x = -halfW; x <= halfW; x += SPACING) {
      const hole = isInHole(x, y, backZ, rand)
      if (hole === true) continue
      if (hole === "debris") {
        pushCube(
          cubes,
          x + (rand() - 0.5) * 0.35,
          y + (rand() - 0.5) * 0.35,
          backZ + rand() * 0.8,
          rand,
          true,
          true
        )
        continue
      }

      const nearHole =
        Math.abs(x - HOLE_CX) < HOLE_W / 2 + SPACING * 1.5 &&
        Math.abs(y - HOLE_CY) < HOLE_H / 2 + SPACING * 1.5
      pushCube(cubes, x, y, backZ, rand, nearHole)
    }
  }

  for (let i = 0; i < 28; i++) {
    pushCube(
      cubes,
      HOLE_CX + (rand() - 0.5) * HOLE_W * 1.1,
      HOLE_CY + (rand() - 0.5) * HOLE_H * 1.1,
      backZ + 0.4 + rand() * 2.2,
      rand,
      true,
      true
    )
  }

  return cubes
}

export default function VoxelWall() {
  const meshRef = useRef<THREE.InstancedMesh>(null)
  const instances = useMemo(() => generateTunnel(), [])
  const dummy = useMemo(() => new THREE.Object3D(), [])

  useEffect(() => {
    const mesh = meshRef.current
    if (!mesh) return

    instances.forEach((cube, i) => {
      dummy.position.set(cube.x, cube.y, cube.z)
      dummy.rotation.set(cube.rotX, cube.rotY, cube.rotZ)
      dummy.scale.setScalar(cube.scale)
      dummy.updateMatrix()
      mesh.setMatrixAt(i, dummy.matrix)
    })
    mesh.instanceMatrix.needsUpdate = true
  }, [instances, dummy])

  useFrame(({ clock }) => {
    const mesh = meshRef.current
    if (!mesh) return

    const t = clock.elapsedTime

    instances.forEach((cube, i) => {
      const dx = LIGHT_TARGET.x - cube.x
      const dy = LIGHT_TARGET.y - cube.y
      const dz = LIGHT_TARGET.z - cube.z
      const dist = Math.hypot(dx, dy, dz) || 1

      const cycle = (t * cube.driftSpeed + cube.phase) % 1
      const eased = cycle * cycle * (3 - 2 * cycle)
      const suck = eased * eased
      const pull = suck * cube.maxDrift

      const px = cube.x + (dx / dist) * pull
      const py = cube.y + (dy / dist) * pull
      const pz = cube.z + (dz / dist) * pull

      const wobble = cube.jitter
        ? Math.sin(t * 1.4 + cube.phase) * 0.012 * (1 - suck * 0.6)
        : Math.sin(t * 0.6 + cube.phase) * 0.004 * (1 - suck * 0.4)

      dummy.position.set(
        px + Math.sin(t * 0.7 + cube.phase) * 0.003 * (1 - suck),
        py + wobble,
        pz
      )
      dummy.rotation.set(
        cube.rotX + suck * 0.5 + Math.sin(t + cube.phase) * 0.01,
        cube.rotY + suck * 0.8,
        cube.rotZ + suck * 0.4 + Math.cos(t * 1.1 + cube.phase) * 0.01
      )
      dummy.scale.setScalar(
        cube.scale * (1 - suck * (cube.jitter ? 0.55 : 0.3))
      )
      dummy.updateMatrix()
      mesh.setMatrixAt(i, dummy.matrix)
    })

    mesh.instanceMatrix.needsUpdate = true
  })

  return (
    <instancedMesh
      ref={meshRef}
      args={[undefined, undefined, instances.length]}
      castShadow
      receiveShadow
    >
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="#050505" roughness={0.98} metalness={0.02} />
    </instancedMesh>
  )
}

export const HOLE_CENTER = new THREE.Vector3(HOLE_CX, HOLE_CY, -ROOM_D - 0.35)

export const HOLE_SIZE = {
  width: HOLE_W * 0.95,
  height: HOLE_H * 0.95,
}

export const LIGHT_ORIGIN = new THREE.Vector3(
  HOLE_CX + 0.9,
  HOLE_CY + 1.4,
  -ROOM_D - 2.2
)

export const ROOM_DEPTH = ROOM_D
registry/gammaui/voxel-wall/light-rays.tsx
"use client"

import { forwardRef, useMemo, useRef } from "react"
import { useFrame } from "@react-three/fiber"
import {
  Bloom,
  EffectComposer,
  GodRays,
  Noise,
  Vignette,
} from "@react-three/postprocessing"
import { BlendFunction } from "postprocessing"
import * as THREE from "three"

import { HOLE_CENTER, HOLE_SIZE, LIGHT_ORIGIN, ROOM_DEPTH } from "./wall"

interface LightSourceProps {
  position?: [number, number, number]
}

export const LightSource = forwardRef<THREE.Mesh, LightSourceProps>(
  function LightSource({ position }, ref) {
    useFrame(({ clock }) => {
      if (!ref || typeof ref === "function" || !ref.current) return

      const pulse = 0.96 + Math.sin(clock.elapsedTime * 1.2) * 0.04
      const mat = ref.current.material as THREE.MeshBasicMaterial
      mat.opacity = pulse
    })

    return (
      <mesh
        ref={ref}
        position={position ?? [HOLE_CENTER.x, HOLE_CENTER.y, HOLE_CENTER.z]}
        rotation={[0, 0, -0.08]}
      >
        <planeGeometry
          args={[HOLE_SIZE.width * 1.15, HOLE_SIZE.height * 1.2]}
        />
        <meshBasicMaterial
          color="#ffffff"
          transparent
          opacity={1}
          depthWrite={false}
          toneMapped={false}
        />
      </mesh>
    )
  }
)

export function DustMotes() {
  const pointsRef = useRef<THREE.Points>(null)

  const positions = useMemo(() => {
    const count = 220
    const arr = new Float32Array(count * 3)
    for (let i = 0; i < count; i++) {
      arr[i * 3] = HOLE_CENTER.x + (Math.random() - 0.5) * HOLE_SIZE.width * 1.6
      arr[i * 3 + 1] =
        HOLE_CENTER.y + (Math.random() - 0.5) * HOLE_SIZE.height * 1.5
      arr[i * 3 + 2] = -ROOM_DEPTH + Math.random() * (ROOM_DEPTH + 2)
    }
    return arr
  }, [])

  const geometry = useMemo(() => {
    const geo = new THREE.BufferGeometry()
    geo.setAttribute("position", new THREE.BufferAttribute(positions, 3))
    return geo
  }, [positions])

  useFrame(({ clock }) => {
    const points = pointsRef.current
    if (!points) return
    points.rotation.z = Math.sin(clock.elapsedTime * 0.06) * 0.015
  })

  return (
    <points ref={pointsRef} geometry={geometry}>
      <pointsMaterial
        size={0.022}
        color="#ffffff"
        transparent
        opacity={0.14}
        depthWrite={false}
        blending={THREE.AdditiveBlending}
        sizeAttenuation
      />
    </points>
  )
}

interface LightEffectsProps {
  sun: THREE.Mesh
}

export function LightEffects({ sun }: LightEffectsProps) {
  return (
    <EffectComposer multisampling={0}>
      <GodRays
        sun={sun}
        blendFunction={BlendFunction.SCREEN}
        samples={80}
        density={0.96}
        decay={0.92}
        weight={0.5}
        exposure={0.42}
        clampMax={1}
        blur
      />
      <Bloom
        luminanceThreshold={0.55}
        luminanceSmoothing={0.35}
        intensity={1.15}
        mipmapBlur
      />
      <Noise opacity={0.07} blendFunction={BlendFunction.OVERLAY} />
      <Vignette eskil={false} offset={0.18} darkness={1.15} />
    </EffectComposer>
  )
}

export function SceneLights() {
  return (
    <>
      <ambientLight intensity={0.008} color="#0a0a0a" />
      <pointLight
        position={[LIGHT_ORIGIN.x, LIGHT_ORIGIN.y, LIGHT_ORIGIN.z]}
        intensity={340}
        color="#ffffff"
        distance={26}
        decay={2}
      />
    </>
  )
}
registry/gammaui/voxel-wall/camera-rig.tsx
"use client"

import { useEffect, useRef } from "react"
import { useFrame, useThree } from "@react-three/fiber"
import * as THREE from "three"

import { HOLE_CENTER } from "./wall"

const BASE_POSITION = new THREE.Vector3(0.15, -2.35, 4.2)
const LOOK_TARGET = new THREE.Vector3(
  HOLE_CENTER.x,
  HOLE_CENTER.y - 0.1,
  HOLE_CENTER.z + 1.5
)

export default function CameraRig() {
  const mouse = useRef({ x: 0, y: 0 })
  const { camera } = useThree()

  useEffect(() => {
    const onPointerMove = (event: PointerEvent) => {
      mouse.current.x = (event.clientX / window.innerWidth) * 2 - 1
      mouse.current.y = -(event.clientY / window.innerHeight) * 2 + 1
    }

    window.addEventListener("pointermove", onPointerMove, { passive: true })
    return () => window.removeEventListener("pointermove", onPointerMove)
  }, [])

  useFrame(({ clock }) => {
    const t = clock.elapsedTime

    const swayX = Math.sin(t * 0.18) * 0.05 + Math.cos(t * 0.14) * 0.03
    const swayY = Math.cos(t * 0.16) * 0.025 + Math.sin(t * 0.11) * 0.018
    const parallaxX = mouse.current.x * 0.18
    const parallaxY = mouse.current.y * 0.1

    camera.position.x = THREE.MathUtils.lerp(
      camera.position.x,
      BASE_POSITION.x + swayX + parallaxX,
      0.05
    )
    camera.position.y = THREE.MathUtils.lerp(
      camera.position.y,
      BASE_POSITION.y + swayY + parallaxY * 0.35,
      0.05
    )
    camera.position.z = THREE.MathUtils.lerp(
      camera.position.z,
      BASE_POSITION.z + Math.sin(t * 0.12) * 0.06,
      0.04
    )

    const lookAt = LOOK_TARGET.clone()
    lookAt.x += parallaxX * 0.12
    lookAt.y += parallaxY * 0.08
    camera.lookAt(lookAt)
  })

  return null
}

More from gammaui

Browse all components in this library →