
R3f Shaders
- 982 installs
- 105 repo stars
- Updated January 20, 2026
- enzed/r3f-skills
r3f-shaders is an agent skill that helps developers write custom GLSL shaders with React Three Fiber and drei shaderMaterial for interactive 3D visual effects.
About
r3f-shaders is a React Three Fiber agent skill for creating and iterating custom GLSL shaders inside @react-three/fiber and three.js scenes. The skill walks through shaderMaterial setup, uniform definitions, vertex and fragment shader code, useFrame animation hooks, and extending built-in materials for effects like color shifts or vertex displacement. Developers reach for r3f-shaders when built-in Three.js materials are insufficient and a scene needs bespoke visual effects, post-processing style tweaks, or per-vertex manipulation. The workflow centers on TypeScript React components, drei helpers, and raw GLSL strings wired into a Canvas render tree. r3f-shaders fits 3D web apps, data visualizations, and game-like experiences where shader iteration speed matters more than offline rendering pipelines.
- Creates custom shaderMaterial with uniforms, vertex, and fragment shaders
- Includes live time-based animation via useFrame
- Supports HMR with Material.key for fast shader development
- Demonstrates extending Three.js materials for JSX usage
- Ready-to-use patterns for modifying vertices and writing fragment shaders
R3f Shaders by the numbers
- 982 all-time installs (skills.sh)
- +45 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #371 of 2,277 Frontend Development skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/enzed/r3f-skills --skill r3f-shadersAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 982 |
|---|---|
| repo stars | ★ 105 |
| Security audit | 3 / 3 scanners passed |
| Last updated | January 20, 2026 |
| Repository | enzed/r3f-skills ↗ |
How do you write custom GLSL shaders in React Three Fiber?
Rapidly create and iterate on custom GLSL shaders inside React Three Fiber projects.
Who is it for?
Frontend developers building React Three Fiber scenes who need custom visual effects beyond standard Three.js materials.
Skip if: Developers working in plain Three.js without React, native game engines, or 2D CSS-only UIs with no WebGL pipeline.
When should I use this skill?
The user asks to write, debug, or extend GLSL shaders, shaderMaterial, uniforms, or custom materials in a React Three Fiber project.
What you get
Custom shaderMaterial components, GLSL vertex and fragment programs, animated uniforms, and extended Three.js materials in a React scene.
- shaderMaterial component
- GLSL shader source strings
- Animated uniform-driven effects
Files
React Three Fiber Shaders
Quick Start
import { Canvas, useFrame, extend } from '@react-three/fiber'
import { shaderMaterial } from '@react-three/drei'
import { useRef } from 'react'
import * as THREE from 'three'
// Create custom shader material
const ColorShiftMaterial = shaderMaterial(
// Uniforms
{ time: 0, color: new THREE.Color(0.2, 0.0, 0.1) },
// Vertex shader
`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
// Fragment shader
`
uniform float time;
uniform vec3 color;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(vUv.x + sin(time), vUv.y + cos(time), color.b, 1.0);
}
`
)
// Extend so it can be used as JSX
extend({ ColorShiftMaterial })
function ShaderMesh() {
const materialRef = useRef()
useFrame(({ clock }) => {
materialRef.current.time = clock.elapsedTime
})
return (
<mesh>
<planeGeometry args={[2, 2]} />
{/* key={Material.key} enables HMR for shader development */}
<colorShiftMaterial ref={materialRef} key={ColorShiftMaterial.key} />
</mesh>
)
}
export default function App() {
return (
<Canvas>
<ShaderMesh />
</Canvas>
)
}shaderMaterial (Drei)
The recommended way to create shader materials in R3F.
Basic Pattern
import { shaderMaterial } from '@react-three/drei'
import { extend } from '@react-three/fiber'
import * as THREE from 'three'
// 1. Define the material
const MyShaderMaterial = shaderMaterial(
// Uniforms object
{
time: 0,
color: new THREE.Color(1, 0, 0),
opacity: 1,
map: null,
},
// Vertex shader (GLSL)
`
varying vec2 vUv;
varying vec3 vPosition;
void main() {
vUv = uv;
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
// Fragment shader (GLSL)
`
uniform float time;
uniform vec3 color;
uniform float opacity;
uniform sampler2D map;
varying vec2 vUv;
void main() {
vec4 texColor = texture2D(map, vUv);
gl_FragColor = vec4(color * texColor.rgb, opacity);
}
`
)
// 2. Extend R3F
extend({ MyShaderMaterial })
// 3. Use in component
function MyMesh() {
const materialRef = useRef()
useFrame(({ clock }) => {
materialRef.current.time = clock.elapsedTime
})
return (
<mesh>
<boxGeometry />
{/* key prop enables Hot Module Replacement during development */}
<myShaderMaterial
ref={materialRef}
key={MyShaderMaterial.key}
color="hotpink"
transparent
opacity={0.8}
/>
</mesh>
)
}Hot Module Replacement (HMR)
The key prop on shaderMaterial enables live shader editing without page refresh:
const MyMaterial = shaderMaterial(
{ time: 0 },
vertexShader,
fragmentShader
)
extend({ MyMaterial })
// MyMaterial.key changes when shader code changes
<myMaterial key={MyMaterial.key} />When you edit shader code, the material automatically updates. Without key, you'd need to refresh the page to see changes.
TypeScript Support
import { shaderMaterial } from '@react-three/drei'
import { extend, Object3DNode } from '@react-three/fiber'
import * as THREE from 'three'
// Define uniform types
type WaveMaterialUniforms = {
time: number
amplitude: number
color: THREE.Color
}
const WaveMaterial = shaderMaterial(
{
time: 0,
amplitude: 0.5,
color: new THREE.Color('hotpink'),
} as WaveMaterialUniforms,
// vertex shader
`...`,
// fragment shader
`...`
)
// Extend with proper types
extend({ WaveMaterial })
// Declare for TypeScript
declare module '@react-three/fiber' {
interface ThreeElements {
waveMaterial: Object3DNode<
typeof WaveMaterial & THREE.ShaderMaterial,
typeof WaveMaterial
>
}
}Raw THREE.ShaderMaterial
For full control without Drei helper.
import { useFrame } from '@react-three/fiber'
import { useMemo, useRef } from 'react'
import * as THREE from 'three'
function CustomShaderMesh() {
const materialRef = useRef()
const shaderMaterial = useMemo(() => {
return new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
color: { value: new THREE.Color('cyan') },
resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float time;
uniform vec3 color;
uniform vec2 resolution;
varying vec2 vUv;
void main() {
vec2 st = gl_FragCoord.xy / resolution;
float pattern = sin(st.x * 20.0 + time) * sin(st.y * 20.0 + time);
gl_FragColor = vec4(color * pattern, 1.0);
}
`,
side: THREE.DoubleSide,
transparent: true,
})
}, [])
useFrame(({ clock }) => {
shaderMaterial.uniforms.time.value = clock.elapsedTime
})
return (
<mesh material={shaderMaterial}>
<planeGeometry args={[4, 4, 32, 32]} />
</mesh>
)
}Uniforms
Common Uniform Types
const MyMaterial = shaderMaterial(
{
// Numbers
time: 0,
intensity: 1.5,
// Vectors
resolution: new THREE.Vector2(1920, 1080),
lightPosition: new THREE.Vector3(5, 10, 5),
bounds: new THREE.Vector4(0, 0, 1, 1),
// Color (becomes vec3)
color: new THREE.Color('#ff0000'),
// Matrices
customMatrix: new THREE.Matrix4(),
// Textures
map: null, // sampler2D
cubeMap: null, // samplerCube
// Arrays
positions: [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()],
},
vertexShader,
fragmentShader
)GLSL Declarations
// In shader code
uniform float time;
uniform float intensity;
uniform vec2 resolution;
uniform vec3 lightPosition;
uniform vec3 color; // THREE.Color becomes vec3
uniform vec4 bounds;
uniform mat4 customMatrix;
uniform sampler2D map;
uniform samplerCube cubeMap;
uniform vec3 positions[3];Updating Uniforms
function AnimatedShader() {
const materialRef = useRef()
useFrame(({ clock, mouse, viewport }) => {
// Direct value update
materialRef.current.time = clock.elapsedTime
// Vector update
materialRef.current.resolution.set(viewport.width, viewport.height)
// Color update
materialRef.current.color.setHSL((clock.elapsedTime * 0.1) % 1, 1, 0.5)
// Or via uniforms object (for THREE.ShaderMaterial)
// materialRef.current.uniforms.time.value = clock.elapsedTime
})
return (
<mesh>
<boxGeometry />
<myShaderMaterial ref={materialRef} />
</mesh>
)
}Varyings
Pass data from vertex to fragment shader.
// Vertex shader
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;
varying vec3 vWorldPosition;
void main() {
vUv = uv;
vNormal = normalize(normalMatrix * normal);
vPosition = position;
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
// Fragment shader
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;
varying vec3 vWorldPosition;
void main() {
// Use interpolated values
gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
}Common Shader Patterns
Texture Sampling
import { useTexture } from '@react-three/drei'
function TexturedShaderMesh() {
const texture = useTexture('/textures/color.jpg')
const materialRef = useRef()
return (
<mesh>
<planeGeometry args={[2, 2]} />
<myShaderMaterial ref={materialRef} map={texture} />
</mesh>
)
}
// Shader
const TextureMaterial = shaderMaterial(
{ map: null },
`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
`
uniform sampler2D map;
varying vec2 vUv;
void main() {
vec4 texColor = texture2D(map, vUv);
gl_FragColor = texColor;
}
`
)Vertex Displacement
const WaveMaterial = shaderMaterial(
{ time: 0, amplitude: 0.5, frequency: 2.0 },
`
uniform float time;
uniform float amplitude;
uniform float frequency;
varying vec2 vUv;
void main() {
vUv = uv;
vec3 pos = position;
// Wave displacement
pos.z += sin(pos.x * frequency + time) * amplitude;
pos.z += sin(pos.y * frequency + time) * amplitude;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
`
varying vec2 vUv;
void main() {
gl_FragColor = vec4(vUv, 1.0, 1.0);
}
`
)
extend({ WaveMaterial })
function WavePlane() {
const ref = useRef()
useFrame(({ clock }) => {
ref.current.time = clock.elapsedTime
})
return (
<mesh rotation={[-Math.PI / 2, 0, 0]}>
<planeGeometry args={[10, 10, 64, 64]} />
<waveMaterial ref={ref} />
</mesh>
)
}Fresnel Effect
const FresnelMaterial = shaderMaterial(
{ fresnelColor: new THREE.Color('cyan'), baseColor: new THREE.Color('navy') },
`
varying vec3 vNormal;
varying vec3 vWorldPosition;
void main() {
vNormal = normalize(normalMatrix * normal);
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
`
uniform vec3 fresnelColor;
uniform vec3 baseColor;
varying vec3 vNormal;
varying vec3 vWorldPosition;
void main() {
vec3 viewDirection = normalize(cameraPosition - vWorldPosition);
float fresnel = pow(1.0 - dot(viewDirection, vNormal), 3.0);
gl_FragColor = vec4(mix(baseColor, fresnelColor, fresnel), 1.0);
}
`
)Noise Functions
// Simple random
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453);
}
// Value noise
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
// FBM (Fractal Brownian Motion)
float fbm(vec2 st) {
float value = 0.0;
float amplitude = 0.5;
for (int i = 0; i < 5; i++) {
value += amplitude * noise(st);
st *= 2.0;
amplitude *= 0.5;
}
return value;
}Gradient
// Linear gradient
vec3 gradient = mix(colorA, colorB, vUv.y);
// Radial gradient
float dist = distance(vUv, vec2(0.5));
vec3 radial = mix(centerColor, edgeColor, dist * 2.0);
// Smooth gradient
float t = smoothstep(0.0, 1.0, vUv.y);
vec3 smooth = mix(colorA, colorB, t);Dissolve Effect
const DissolveMaterial = shaderMaterial(
{ progress: 0, noiseScale: 10.0, edgeColor: new THREE.Color('orange') },
`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
`
uniform float progress;
uniform float noiseScale;
uniform vec3 edgeColor;
varying vec2 vUv;
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453);
}
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
void main() {
float n = noise(vUv * noiseScale);
if (n < progress) {
discard;
}
float edge = smoothstep(progress, progress + 0.1, n);
vec3 baseColor = vec3(0.5);
gl_FragColor = vec4(mix(edgeColor, baseColor, edge), 1.0);
}
`
)Extending Built-in Materials
onBeforeCompile
Modify existing material shaders.
import { useRef, useEffect } from 'react'
import { useFrame } from '@react-three/fiber'
import * as THREE from 'three'
function ModifiedStandardMaterial() {
const materialRef = useRef()
const shaderRef = useRef()
useEffect(() => {
if (materialRef.current) {
materialRef.current.onBeforeCompile = (shader) => {
// Add custom uniform
shader.uniforms.time = { value: 0 }
shaderRef.current = shader
// Add uniform declaration
shader.vertexShader = 'uniform float time;\n' + shader.vertexShader
// Modify vertex shader
shader.vertexShader = shader.vertexShader.replace(
'#include <begin_vertex>',
`
#include <begin_vertex>
transformed.y += sin(position.x * 10.0 + time) * 0.1;
`
)
}
}
}, [])
useFrame(({ clock }) => {
if (shaderRef.current) {
shaderRef.current.uniforms.time.value = clock.elapsedTime
}
})
return (
<mesh>
<planeGeometry args={[5, 5, 32, 32]} />
<meshStandardMaterial ref={materialRef} color="green" />
</mesh>
)
}Common Injection Points
// Vertex shader chunks
'#include <begin_vertex>' // After position is calculated
'#include <project_vertex>' // After gl_Position
'#include <beginnormal_vertex>' // Normal calculation start
// Fragment shader chunks
'#include <color_fragment>' // After diffuse color
'#include <output_fragment>' // Final output
'#include <fog_fragment>' // After fog appliedGLSL Built-in Functions
Math Functions
// Basic
abs(x), sign(x), floor(x), ceil(x), fract(x)
mod(x, y), min(x, y), max(x, y), clamp(x, min, max)
mix(a, b, t), step(edge, x), smoothstep(edge0, edge1, x)
// Trigonometry
sin(x), cos(x), tan(x)
asin(x), acos(x), atan(y, x), atan(x)
// Exponential
pow(x, y), exp(x), log(x), sqrt(x)Vector Functions
length(v), distance(p0, p1), dot(x, y), cross(x, y)
normalize(v), reflect(I, N), refract(I, N, eta)Instanced Shaders
import { useRef, useMemo } from 'react'
import { useFrame } from '@react-three/fiber'
import * as THREE from 'three'
function InstancedShaderMesh({ count = 1000 }) {
const meshRef = useRef()
// Create instance attributes
const { offsets, colors } = useMemo(() => {
const offsets = new Float32Array(count * 3)
const colors = new Float32Array(count * 3)
for (let i = 0; i < count; i++) {
offsets[i * 3] = (Math.random() - 0.5) * 20
offsets[i * 3 + 1] = (Math.random() - 0.5) * 20
offsets[i * 3 + 2] = (Math.random() - 0.5) * 20
colors[i * 3] = Math.random()
colors[i * 3 + 1] = Math.random()
colors[i * 3 + 2] = Math.random()
}
return { offsets, colors }
}, [count])
const shaderMaterial = useMemo(() => {
return new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 }
},
vertexShader: `
attribute vec3 offset;
attribute vec3 instanceColor;
varying vec3 vColor;
void main() {
vColor = instanceColor;
vec3 pos = position + offset;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0);
}
`
})
}, [])
useFrame(({ clock }) => {
shaderMaterial.uniforms.time.value = clock.elapsedTime
})
return (
<instancedMesh ref={meshRef} args={[null, null, count]} material={shaderMaterial}>
<boxGeometry args={[0.5, 0.5, 0.5]}>
<instancedBufferAttribute attach="attributes-offset" args={[offsets, 3]} />
<instancedBufferAttribute attach="attributes-instanceColor" args={[colors, 3]} />
</boxGeometry>
</instancedMesh>
)
}External Shader Files
With Vite/Webpack
// shaders/vertex.glsl
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
// shaders/fragment.glsl
uniform float time;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(vUv, sin(time), 1.0);
}
// Component.tsx
import vertexShader from './shaders/vertex.glsl?raw'
import fragmentShader from './shaders/fragment.glsl?raw'
const MyMaterial = shaderMaterial(
{ time: 0 },
vertexShader,
fragmentShader
)Vite Config for GLSL
// vite.config.js
import glsl from 'vite-plugin-glsl'
export default {
plugins: [glsl()]
}Material Properties
<myShaderMaterial
// Rendering
transparent={true}
opacity={1.0}
side={THREE.DoubleSide}
depthTest={true}
depthWrite={true}
// Blending
blending={THREE.NormalBlending}
// NormalBlending, AdditiveBlending, SubtractiveBlending, MultiplyBlending
// Wireframe
wireframe={false}
// Custom uniforms
time={0}
color="hotpink"
/>Debugging Shaders
function DebugShaderMesh() {
const materialRef = useRef()
useEffect(() => {
// Log compiled shaders
if (materialRef.current) {
console.log('Vertex:', materialRef.current.vertexShader)
console.log('Fragment:', materialRef.current.fragmentShader)
}
}, [])
return (
<mesh>
<boxGeometry />
{/* Debug with visual output */}
<shaderMaterial
ref={materialRef}
fragmentShader={`
varying vec2 vUv;
void main() {
// Debug UV
gl_FragColor = vec4(vUv, 0.0, 1.0);
// Debug normals (in vertex: vNormal = normal)
// gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
}
`}
vertexShader={`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`}
/>
</mesh>
)
}Performance Tips
1. Minimize uniforms: Group related values into vectors 2. Avoid conditionals: Use mix/step instead of if/else 3. Precalculate in JS: Move static calculations out of shaders 4. Use textures for lookup: Complex functions as texture lookups 5. Limit overdraw: Avoid unnecessary transparent objects
// Instead of:
if (value > 0.5) {
color = colorA;
} else {
color = colorB;
}
// Use:
color = mix(colorB, colorA, step(0.5, value));See Also
r3f-materials- Built-in material typesr3f-postprocessing- Full-screen shader effectsr3f-textures- Texture sampling in shaders
Related skills
How it compares
Choose r3f-shaders over generic WebGL guides when the stack is React Three Fiber with drei shaderMaterial, not vanilla Three.js or Unity shaders.
FAQ
What libraries does r3f-shaders assume?
r3f-shaders targets @react-three/fiber and @react-three/drei with three.js, wiring shaderMaterial, uniforms, and useFrame hooks inside a Canvas-based React component tree.
When should a developer use r3f-shaders?
r3f-shaders fits custom visual effects, vertex modifications, fragment shader color work, and extending built-in materials when standard Three.js shaders are not enough in React Three Fiber.
Is R3f Shaders safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.