Three.js Shaders
Quick Start
javascript
import * as THREE from "three";
const material = new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, color: { value: new THREE.Color(0xff0000) }, }, vertexShader: ` void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform vec3 color;
void main() { gl_FragColor = vec4(color, 1.0); } `,});
// Update in animation loopmaterial.uniforms.time.value = clock.getElapsedTime();ShaderMaterial vs RawShaderMaterial
ShaderMaterial
Three.js provides built-in uniforms and attributes.
javascript
const material = new THREE.ShaderMaterial({ vertexShader: ` // Built-in uniforms available: // uniform mat4 modelMatrix; // uniform mat4 modelViewMatrix; // uniform mat4 projectionMatrix; // uniform mat4 viewMatrix; // uniform mat3 normalMatrix; // uniform vec3 cameraPosition;
// Built-in attributes available: // attribute vec3 position; // attribute vec3 normal; // attribute vec2 uv;
void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } `,});RawShaderMaterial
Full control - you define everything.
javascript
const material = new THREE.RawShaderMaterial({ uniforms: { projectionMatrix: { value: camera.projectionMatrix }, modelViewMatrix: { value: new THREE.Matrix4() }, }, vertexShader: ` precision highp float;
attribute vec3 position; uniform mat4 projectionMatrix; uniform mat4 modelViewMatrix;
void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` precision highp float;
void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } `,});Uniforms
Uniform Types
javascript
const material = new THREE.ShaderMaterial({ uniforms: { // Numbers floatValue: { value: 1.5 }, intValue: { value: 1 },
// Vectors vec2Value: { value: new THREE.Vector2(1, 2) }, vec3Value: { value: new THREE.Vector3(1, 2, 3) }, vec4Value: { value: new THREE.Vector4(1, 2, 3, 4) },
// Colors (converted to vec3) colorValue: { value: new THREE.Color(0xff0000) },
// Matrices mat3Value: { value: new THREE.Matrix3() }, mat4Value: { value: new THREE.Matrix4() },
// Textures textureValue: { value: texture }, cubeTextureValue: { value: cubeTexture },
// Arrays floatArray: { value: [1.0, 2.0, 3.0] }, vec3Array: { value: [new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 1, 0)], }, },});GLSL Declarations
glsl
// In shaderuniform float floatValue;uniform int intValue;uniform vec2 vec2Value;uniform vec3 vec3Value;uniform vec3 colorValue; // Color becomes vec3uniform vec4 vec4Value;uniform mat3 mat3Value;uniform mat4 mat4Value;uniform sampler2D textureValue;uniform samplerCube cubeTextureValue;uniform float floatArray[3];uniform vec3 vec3Array[2];Updating Uniforms
javascript
// Direct assignmentmaterial.uniforms.time.value = clock.getElapsedTime();
// Vector/Color updatesmaterial.uniforms.position.value.set(x, y, z);material.uniforms.color.value.setHSL(hue, 1, 0.5);
// Matrix updatesmaterial.uniforms.matrix.value.copy(mesh.matrixWorld);Varyings
Pass data from vertex to fragment shader.
javascript
const material = new THREE.ShaderMaterial({ vertexShader: ` varying vec2 vUv; varying vec3 vNormal; varying vec3 vPosition;
void main() { vUv = uv; vNormal = normalize(normalMatrix * normal); vPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` varying vec2 vUv; varying vec3 vNormal; varying vec3 vPosition;
void main() { // Use interpolated values gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0); } `,});Common Shader Patterns
Texture Sampling
javascript
const material = new THREE.ShaderMaterial({ uniforms: { map: { value: texture }, }, vertexShader: ` varying vec2 vUv;
void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform sampler2D map; varying vec2 vUv;
void main() { vec4 texColor = texture2D(map, vUv); gl_FragColor = texColor; } `,});Vertex Displacement
javascript
const material = new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, amplitude: { value: 0.5 }, }, vertexShader: ` uniform float time; uniform float amplitude;
void main() { vec3 pos = position;
// Wave displacement pos.z += sin(pos.x * 5.0 + time) * amplitude; pos.z += sin(pos.y * 5.0 + time) * amplitude;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); } `, fragmentShader: ` void main() { gl_FragColor = vec4(0.5, 0.8, 1.0, 1.0); } `,});Fresnel Effect
javascript
const material = new THREE.ShaderMaterial({ vertexShader: ` 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); } `, fragmentShader: ` varying vec3 vNormal; varying vec3 vWorldPosition;
void main() { // cameraPosition is auto-provided by ShaderMaterial vec3 viewDirection = normalize(cameraPosition - vWorldPosition); float fresnel = pow(1.0 - dot(viewDirection, vNormal), 3.0);
vec3 baseColor = vec3(0.0, 0.0, 0.5); vec3 fresnelColor = vec3(0.5, 0.8, 1.0);
gl_FragColor = vec4(mix(baseColor, fresnelColor, fresnel), 1.0); } `,});Noise-Based Effects
glsl
// Simple noise functionfloat random(vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453);}
// Value noisefloat 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;}
// Usagefloat n = noise(vUv * 10.0 + time);Gradient
glsl
// Linear gradientvec3 color = mix(colorA, colorB, vUv.y);
// Radial gradientfloat dist = distance(vUv, vec2(0.5));vec3 color = mix(centerColor, edgeColor, dist * 2.0);
// Smooth gradient with custom curvefloat t = smoothstep(0.0, 1.0, vUv.y);vec3 color = mix(colorA, colorB, t);Rim Lighting
javascript
const material = new THREE.ShaderMaterial({ vertexShader: ` varying vec3 vNormal; varying vec3 vViewPosition;
void main() { vNormal = normalize(normalMatrix * normal); vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); vViewPosition = mvPosition.xyz; gl_Position = projectionMatrix * mvPosition; } `, fragmentShader: ` varying vec3 vNormal; varying vec3 vViewPosition;
void main() { vec3 viewDir = normalize(-vViewPosition); float rim = 1.0 - max(0.0, dot(viewDir, vNormal)); rim = pow(rim, 4.0);
vec3 baseColor = vec3(0.2, 0.2, 0.8); vec3 rimColor = vec3(1.0, 0.5, 0.0);
gl_FragColor = vec4(baseColor + rimColor * rim, 1.0); } `,});Dissolve Effect
glsl
uniform float progress;uniform sampler2D noiseMap;
void main() { float noise = texture2D(noiseMap, vUv).r;
if (noise < progress) { discard; }
// Edge glow float edge = smoothstep(progress, progress + 0.1, noise); vec3 edgeColor = vec3(1.0, 0.5, 0.0); vec3 baseColor = vec3(0.5);
gl_FragColor = vec4(mix(edgeColor, baseColor, edge), 1.0);}Extending Built-in Materials
onBeforeCompile
Modify existing material shaders.
javascript
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
material.onBeforeCompile = (shader) => { // Add custom uniform shader.uniforms.time = { value: 0 };
// Store reference for updates material.userData.shader = shader;
// Modify vertex shader shader.vertexShader = shader.vertexShader.replace( "#include <begin_vertex>", ` #include <begin_vertex> transformed.y += sin(position.x * 10.0 + time) * 0.1; `, );
// Add uniform declaration shader.vertexShader = "uniform float time;\n" + shader.vertexShader;};
// Update in animation loopif (material.userData.shader) { material.userData.shader.uniforms.time.value = clock.getElapsedTime();}Common Injection Points
javascript
// 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
glsl
// Basicabs(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)
// Trigonometrysin(x), cos(x), tan(x)asin(x), acos(x), atan(y, x), atan(x)radians(degrees), degrees(radians)
// Exponentialpow(x, y), exp(x), log(x), exp2(x), log2(x)sqrt(x), inversesqrt(x)Vector Functions
glsl
// Length and distancelength(v), distance(p0, p1), dot(x, y), cross(x, y)
// Normalizationnormalize(v)
// Reflection and refractionreflect(I, N), refract(I, N, eta)
// Component-wiselessThan(x, y), lessThanEqual(x, y)greaterThan(x, y), greaterThanEqual(x, y)equal(x, y), notEqual(x, y)any(bvec), all(bvec)Texture Functions
glsl
// GLSL 1.0 (default) - use texture2D/textureCubetexture2D(sampler, coord)texture2D(sampler, coord, bias)textureCube(sampler, coord)
// GLSL 3.0 (glslVersion: THREE.GLSL3) - use texture()// texture(sampler, coord) replaces texture2D/textureCube// Also use: out vec4 fragColor instead of gl_FragColor
// Texture size (GLSL 1.30+)textureSize(sampler, lod)Common Material Properties
javascript
const material = new THREE.ShaderMaterial({ uniforms: { /* ... */ }, vertexShader: "/* ... */", fragmentShader: "/* ... */",
// Rendering transparent: true, opacity: 1.0, side: THREE.DoubleSide, depthTest: true, depthWrite: true,
// Blending blending: THREE.NormalBlending, // AdditiveBlending, SubtractiveBlending, MultiplyBlending
// Wireframe wireframe: false, wireframeLinewidth: 1, // Note: >1 has no effect on most platforms (WebGL limitation)
// Extensions extensions: { derivatives: true, // For fwidth, dFdx, dFdy fragDepth: true, // gl_FragDepth drawBuffers: true, // Multiple render targets shaderTextureLOD: true, // texture2DLod },
// GLSL version glslVersion: THREE.GLSL3, // For WebGL2 features});Shader Includes
Using Three.js Shader Chunks
javascript
import { ShaderChunk } from "three";
const fragmentShader = ` ${ShaderChunk.common} ${ShaderChunk.packing}
uniform sampler2D depthTexture; varying vec2 vUv;
void main() { float depth = texture2D(depthTexture, vUv).r; float linearDepth = perspectiveDepthToViewZ(depth, 0.1, 1000.0); gl_FragColor = vec4(vec3(-linearDepth / 100.0), 1.0); }`;External Shader Files
javascript
// With vite/webpackimport vertexShader from "./shaders/vertex.glsl";import fragmentShader from "./shaders/fragment.glsl";
const material = new THREE.ShaderMaterial({ vertexShader, fragmentShader,});Instanced Shaders
javascript
// Instanced attributeconst offsets = new Float32Array(instanceCount * 3);// Fill offsets...geometry.setAttribute("offset", new THREE.InstancedBufferAttribute(offsets, 3));
const material = new THREE.ShaderMaterial({ vertexShader: ` attribute vec3 offset;
void main() { vec3 pos = position + offset; gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); } `, fragmentShader: ` void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } `,});Debugging Shaders
javascript
// Check for compile errorsmaterial.onBeforeCompile = (shader) => { console.log("Vertex Shader:", shader.vertexShader); console.log("Fragment Shader:", shader.fragmentShader);};
// Visual debuggingfragmentShader: ` void main() { // Debug UV gl_FragColor = vec4(vUv, 0.0, 1.0);
// Debug normals gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
// Debug position gl_FragColor = vec4(vPosition * 0.1 + 0.5, 1.0); }`;
// Check WebGL errorsrenderer.debug.checkShaderErrors = true;Performance Tips
- Minimize uniforms: Group related values into vectors
- Avoid conditionals: Use mix/step instead of if/else
- Precalculate: Move calculations to JS when possible
- Use textures: For complex functions, use lookup tables
- Limit overdraw: Avoid transparent objects when possible
glsl
// Instead of:if (value > 0.5) { color = colorA;} else { color = colorB;}
// Use:color = mix(colorB, colorA, step(0.5, value));See Also
threejs-materials- Built-in material typesthreejs-postprocessing- Full-screen shader effectsthreejs-textures- Texture sampling in shaders


