# Shader 3 - Mouse-Interactive Glass Shader

> A full-screen shader background with glass refraction, circular shapes, mouse-following interaction, and configurable color.

A shadcn/ui block from the [@shadcnblocks](https://shadcnblocks.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/shadcnblocks/shader3).

- Page, with a live preview: https://shadcn.exchange/r/shadcnblocks/shader3
- Screenshot: https://shadcn.exchange/shot/shadcnblocks/shader3/light.png
- Registry item: https://shadcnblocks.com/r/shader3.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/shadcnblocks/shader3.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@react-three/fiber`, `cn`, `three`

## Files

### `block/shader3/shader3.tsx`

```tsx
"use client";

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

interface ShaderPlaneProps {
  vertexShader: string;
  fragmentShader: string;
  uniforms: { [key: string]: { value: unknown } };
}

const ShaderPlane = ({
  vertexShader,
  fragmentShader,
  uniforms,
}: ShaderPlaneProps) => {
  const meshRef = useRef<THREE.Mesh>(null);
  const { size } = useThree();
  const mousePos = useRef({ x: 0, y: 0 });
  const targetPos = useRef({ x: 0, y: 0 });

  useEffect(() => {
    const handleMouseMove = (event: MouseEvent) => {
      targetPos.current.x = (event.clientX / window.innerWidth) * 2 - 1;
      targetPos.current.y = -(event.clientY / window.innerHeight) * 2 + 1;
    };

    window.addEventListener("mousemove", handleMouseMove);
    return () => window.removeEventListener("mousemove", handleMouseMove);
  }, []);

  useFrame((state) => {
    if (meshRef.current) {
      const material = meshRef.current.material as THREE.ShaderMaterial;
      material.uniforms.u_time.value = state.clock.elapsedTime * 0.5;
      material.uniforms.u_resolution.value.set(size.width, size.height, 1.0);

      const lerpFactor = 0.025;
      mousePos.current.x +=
        (targetPos.current.x - mousePos.current.x) * lerpFactor;
      mousePos.current.y +=
        (targetPos.current.y - mousePos.current.y) * lerpFactor;
      material.uniforms.u_mouse.value.set(
        mousePos.current.x,
        mousePos.current.y,
        0.0,
        0.0,
      );
    }
  });

  return (
    <mesh ref={meshRef}>
      <planeGeometry args={[2, 2]} />
      <shaderMaterial
        vertexShader={vertexShader}
        fragmentShader={fragmentShader}
        uniforms={uniforms}
        side={THREE.FrontSide}
        depthTest={false}
        depthWrite={false}
      />
    </mesh>
  );
};

interface ShaderBackgroundProps {
  vertexShader?: string;
  fragmentShader?: string;
  uniforms?: { [key: string]: { value: unknown } };
  className?: string;
  color?: string;
}

const Shader3 = ({
  vertexShader = `
    varying vec2 vUv;
    void main() {
      vUv = uv;
    gl_Position = vec4(position, 1.0);
    }
  `,
  fragmentShader = `
    precision highp float;
    const float CIRCLE_SCALE = 1.0;

    varying vec2 vUv;
    uniform float u_time;
    uniform vec3 u_resolution;
    uniform vec4 u_mouse;
    uniform vec3 u_color;
    uniform sampler2D u_channel0;

    #define R u_resolution
    #define PI 3.141528

    const float refractIndex = 0.3;

    vec3 hsv(float h,float s,float v){return ((clamp(abs(fract(h+vec3(0.,.666,.333))*6.-3.)-1.,0.,1.)-1.)*s+1.)*v;}
    vec3 rgb2hsv(vec3 c){
      vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);
      vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
      vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
      float d = q.x - min(q.w, q.y);
      float e = 1.0e-10;
      return vec3(abs(q.z + (q.w - q.y) / (6.0*d + e)), d / (q.x + e), q.x);
    }

    float map(float val, float inA, float inB, float outA, float outB) {
      return (val - inA) / (inB - inA) * (outB - outA) + outA;
    }

    float fresnel(vec3 direction, vec3 normal, float power, bool invert) {
        vec3 halfDirection = normalize( normal + direction );
        float cosine = dot( halfDirection, direction );
        float product = max( cosine, 0.0 );
        float factor = invert ? 1.0 - pow( product, power ) : pow( product, power );
        return factor;
    }
    vec4 remapShadows(vec4 color) {
      float factor = 10.;
      return vec4(
        pow(color.x, factor),
        pow(color.y, factor),
        pow(color.z, factor),
        color.w
      );
    }
    float specular(vec3 eyeVector, vec3 worldNormal, vec3 light, float shininess, float diffuseness) {
      vec3 normal = worldNormal;
      vec3 lightVector = normalize(-light);
      vec3 halfVector = normalize(eyeVector + lightVector);
      float NdotL = dot(normal, lightVector);
      float NdotH =  dot(normal, halfVector);
      float kDiffuse = max(0.0, NdotL);
      float NdotH2 = NdotH * NdotH;
      float kSpecular = pow(NdotH2, shininess);
      return kSpecular + kDiffuse * diffuseness;
    }

    vec4 sampleBackground(vec3 normal, sampler2D bg) {
      float phi = acos(normal.y);
      float sinPhi = sin(phi);
      float theta =
        abs(sinPhi) > 0.0001
          ? acos(normal.x / sinPhi)
          : 0.;
      vec2 coord = vec2(
        map(theta, 0., PI, 0., 1.),
        map(phi, 0., PI, 1., 0.)
      );

      return texture(bg, coord);
    }

    float lambert(vec3 normal, vec3 lightPos){
      return max(dot(normal, lightPos), 0.05);
    }

    float hash12(vec2 p)
    {
      vec3 p3  = fract(vec3(p.xyx) * .1031);
        p3 += dot(p3, p3.yzx + 33.33);
        return fract((p3.x + p3.y) * p3.z);
    }

    vec2 globalPos(vec2 pos){
        vec2 mover = u_mouse.xy * 0.25;
        return pos-mover + hash12(gl_FragCoord.xy + u_time * 0.1)*.0125;
    }

    float circle(vec2 pos,float lo, float hi){
        return smoothstep(lo,hi,length(globalPos(pos)));
    }

    vec3 gradient(float d){
        float a = smoothstep(0.0, 1.0, d);
        vec3 colA = mix(vec3(0.05,0.05,0.05),vec3(.4,.6,.6),smoothstep(0.05,0.05,0.05) * d);
        vec3 colB = mix(u_color * 0.06, u_color * 0.9, a);
        return mix(colA, colB, d);
    }

    vec3 coloredRim(vec2 pos, float lamVal) {
        float rimRadius = 0.035 * CIRCLE_SCALE + lamVal * 0.05 * CIRCLE_SCALE;
        float innerRadius = 0.03 * CIRCLE_SCALE + lamVal * 0.025 * CIRCLE_SCALE;
        float dist = length(globalPos(pos));

        float rimMask = smoothstep(innerRadius, rimRadius, dist) * (1.0 - smoothstep(rimRadius, rimRadius + 0.05, dist));

        vec3 hsvColor = rgb2hsv(u_color);
        float baseHue = hsvColor.x;
        float sat = hsvColor.y;
        float hue = baseHue + dist * 2.0;
        vec3 rimColor = hsv(hue, sat, 1.0);

        return rimColor * rimMask;
    }

    float sdfCircle(vec2 pos, float r) {
        return length(globalPos(pos)) - r;
    }
    vec2 sdfNormal(vec2 pos, float r) {
        vec2 e = vec2(1.0/R.x, 1.0/R.y) * 1.5;
        float dx = sdfCircle(pos + vec2(e.x, 0.0), r) - sdfCircle(pos - vec2(e.x, 0.0), r);
        float dy = sdfCircle(pos + vec2(0.0, e.y), r) - sdfCircle(pos - vec2(0.0, e.y), r);
        vec2 n = normalize(vec2(dx, dy));
        return n;
    }
    vec3 shadeAt(vec2 pos, float lamVal) {
        float dLocal = 1. - circle(pos, 0.05 * CIRCLE_SCALE, .3 * CIRCLE_SCALE + lamVal * 0.025 * CIRCLE_SCALE);
        return gradient(dLocal);
    }
    vec3 refractSDFColor(vec2 pos, float lamVal) {
    float shiftPxR = 2.0;
    float shiftPxG = 0.0;
    float shiftPxB = -2.0;

    float r = 0.175 * CIRCLE_SCALE + lamVal * 0.6125 * CIRCLE_SCALE;
    vec2 n2 = normalize(sdfNormal(pos, r));

    vec2 px = 1.0 / R.xy;
    vec3 cR = shadeAt(pos + n2 * px * shiftPxR, lamVal);
    vec3 cG = shadeAt(pos + n2 * px * shiftPxG, lamVal);
    vec3 cB = shadeAt(pos + n2 * px * shiftPxB, lamVal);
    return vec3(cR.r, cG.g, cB.b);
    }

    void mainImage( out vec4 fragColor, in vec2 fragCoord )
    {
        vec2 uv = fragCoord / R.xy;
        vec2 p = (fragCoord/R.xy-.5)*vec2(R.x/R.y,1.)*2.;

        float angle = PI / 4.0;
        float cosA = cos(angle);
        float sinA = sin(angle);
        vec2 rotatedP = vec2(
            p.x * cosA - p.y * sinA,
            p.x * sinA + p.y * cosA
        );

        vec3 rd = vec3(0.0, 0.0, -1.0);
        float rod_x = fract(rotatedP.x * 2.) * 2.0 - 1.0;
        float rod_z = sqrt(1.0 - rod_x*rod_x);
        vec3 n = vec3(rod_x, 0.0, -rod_z);

        vec3 refracted_ray = mix(n, rd, refractIndex);
        float dist = 0.8;
        float z_dist = dist / (refracted_ray.z  - 0.8);
        vec3 pos = vec3(p, 0.0) + z_dist*refracted_ray;
        vec2 subPos = vec2(pos.xy * pos.z);

        vec3 lpos = normalize(vec3(0., 0., -1.0));
        vec3 lcol = vec3(1.);
        float lambert = pow(lambert(n,lpos),2.);
        vec3 reflectedDir = normalize(reflect(rd, n));
        float fresnel = fresnel(rd, n, 8.,true);
        float fresnelStrength = 0.595;

        float d = 1.-circle(subPos,0.05 * CIRCLE_SCALE,.3 * CIRCLE_SCALE + lambert * 0.0125 *CIRCLE_SCALE);
        vec3 color = gradient(d);

        vec3 rimColor = coloredRim(subPos, lambert);
        color += rimColor * 0.65;

        float g = 1.0 - abs(n.z);
        g = g * 0.8 / (g * 0.8 - g + 1.0);
        float glass = 1.-((1.0 - 0.3 * g) - g * hash12(gl_FragCoord.xy + d));
        vec3 refrCol = refractSDFColor(subPos, lambert);
        color = mix(color, refrCol, 0.65);
        color += lambert * color * 0.05 * d;
        color += color * glass * d * 0.05;
        color = clamp(color,0.,1.);

        color = pow(color,vec3(1./1.222));

        fragColor = vec4(vec3(color),1.0);
    }

    void main() {
      vec4 fragColor;
      vec2 fragCoord = vUv * u_resolution.xy;
      mainImage(fragColor, fragCoord);
      gl_FragColor = fragColor;
    }
  `,
  uniforms = {},
  className,
  color = "#bbffcc",
}: ShaderBackgroundProps) => {
  const shaderUniforms = useMemo(
    () => ({
      u_time: { value: 0 },
      u_resolution: { value: new THREE.Vector3(1, 1, 1) },
      u_mouse: { value: new THREE.Vector4(0, 0, 0, 0) },
      u_color: { value: new THREE.Color(color) },
      ...uniforms,
    }),
    [uniforms, color],
  );

  return (
    <section
      className={cn(
        "relative h-svh max-h-[1200px] min-h-[600px] w-full overflow-hidden",
        className,
      )}
    >
      <Canvas>
        <ShaderPlane
          vertexShader={vertexShader}
          fragmentShader={fragmentShader}
          uniforms={shaderUniforms}
        />
      </Canvas>
    </section>
  );
};

export { Shader3 };
```
