cloudai-x/threejs-skills/threejs-shaders

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Custom GLSL vertex and fragment shaders: uniforms, varyings, shader chunks, extending built-in materials, instanced attributes, plus the same effects as node materials with TSL on WebGPU. Use when writing a shader, a custom material or effect, moving vertices in a shader, or porting GLSL to TSL.

Install

npx vskill@latest install cloudai-x/threejs-skills/threejs-shaders

SKILL.md

Three.js Shaders

Targets three.js r186. Examples use WebGLRenderer; the WebGPU and TSL section uses WebGPURenderer.

Quick Start

javascript
import * as THREE from "three";
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 2);
const material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
color: { value: new THREE.Color(0x0066ff) }, // hex converted to the linear working space
},
vertexShader: /* glsl */ `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: /* glsl */ `
uniform float time;
uniform vec3 color;
varying vec2 vUv;
void main() {
float wave = sin(vUv.x * 10.0 + time * 2.0) * 0.5 + 0.5;
gl_FragColor = vec4(color * wave, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
`,
});
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
scene.add(mesh);
const timer = new THREE.Timer();
timer.connect(document);
renderer.setAnimationLoop(animate);
function animate() {
timer.update();
material.uniforms.time.value = timer.getElapsed();
renderer.render(scene, camera);
}

ShaderMaterial vs RawShaderMaterial

Every ShaderMaterial compiles as GLSL ES 3.00 — three.js always prepends #version 300 es (WebGL 1 support is gone). texture(), textureLod(), textureSize() and dFdx/fwidth work with no flags; texture2D, textureCube, attribute, varying and gl_FragColor are #define aliases, so most GLSL ES 1.00 shaders compile unchanged (not ones that name a variable texture, write gl_FragData or keep an #extension line, which fails because three.js puts declarations before your source; delete it, the features are core). Leave glslVersion unset: setting THREE.GLSL3 only removes the gl_FragColor alias, forcing you to declare your own output variable. ShaderMaterial injects the usual declarations:

javascript
const material = new THREE.ShaderMaterial({
vertexShader: /* glsl */ `
// Declared for you: uniforms modelMatrix, modelViewMatrix, projectionMatrix,
// viewMatrix, normalMatrix, cameraPosition; attributes position, normal, uv,
// instanceMatrix (InstancedMesh) and color (vertexColors: true)
varying vec3 vNormal;
void main() {
vNormal = normalize(normalMatrix * normal); // view-space normal
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: /* glsl */ `
varying vec3 vNormal;
void main() {
gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
`,
});

RawShaderMaterial gets only #define SHADER_TYPE, #define SHADER_NAME and your defines (plus #version 300 es once glslVersion is set): no declarations and no precision statement. Always set glslVersion: THREE.GLSL3 (without it the shader is GLSL ES 1.00, which has no texture()), write precision highp float;, use in/out and declare your own output. Declare the matrices you need in the shader and never pass them through uniforms: the renderer sets modelMatrix, modelViewMatrix, projectionMatrix, viewMatrix, normalMatrix and cameraPosition for any program that declares them, and a same-named entry in uniforms is either overwritten every draw (modelMatrix, modelViewMatrix, normalMatrix) or replaces the camera's value (projectionMatrix, viewMatrix, cameraPosition).

javascript
const material = new THREE.RawShaderMaterial({
glslVersion: THREE.GLSL3,
vertexShader: /* glsl */ `
precision highp float;
in vec3 position;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: /* glsl */ `
precision highp float;
out highp vec4 fragColor;
void main() {
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`,
});

Uniforms

javascript
const texture = await new THREE.TextureLoader().loadAsync("textures/diffuse.jpg");
texture.colorSpace = THREE.SRGBColorSpace; // a color image: decoded to linear when sampled
const material = new THREE.ShaderMaterial({
uniforms: {
floatValue: { value: 1.5 },
vec3Value: { value: new THREE.Vector3(1, 2, 3) },
colorValue: { value: new THREE.Color(0xff0000) }, // hex converted to the linear working space
mat4Value: { value: new THREE.Matrix4() },
mapValue: { value: texture },
},
// vertexShader and fragmentShader as usual
});

The matching GLSL declarations — sample the texture with texture(mapValue, vUv):

glsl
uniform float floatValue;
uniform vec3 vec3Value;
uniform vec3 colorValue; // a THREE.Color arrives as vec3
uniform mat4 mat4Value;
uniform sampler2D mapValue;

Update a uniform by assigning .value from the frame loop (material.uniforms.floatValue.value = 2.0;); vectors and colors mutate in place (material.uniforms.vec3Value.value.set(1, 2, 3);). Never name a uniform position, normal, uv, modelViewMatrix or cameraPosition — three.js already declares those in the vertex shader, and the redeclaration is a compile error.

Output Color and Tone Mapping

THREE.Color converts hex input from sRGB to the linear working color space, so a color uniform holds linear values. A ShaderMaterial writes its output as-is: tone mapping and the output color space conversion happen only if the fragment shader ends with the two includes:

glsl
gl_FragColor = vec4(color, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>

tonemapping_fragment applies renderer.toneMapping (a no-op with NoToneMapping); colorspace_fragment encodes to renderer.outputColorSpace (SRGBColorSpace by default). Without them, mid-tone colors render darker than the same hex on a built-in material. Both chunks write gl_FragColor, so they require glslVersion unset. An 8-bit RGBA texture with texture.colorSpace = THREE.SRGBColorSpace decodes to linear on sampling, so sampled colors combine correctly with the includes in place. A VideoTexture does not: three.js uploads it without sRGB decoding and decodes it only inside built-in materials, so decode it yourself with sRGBTransferEOTF(texture(videoMap, vUv)) (the ShaderMaterial prefix declares that function). Rendering into a render target, as inside an EffectComposer, both includes leave the color linear and OutputPass encodes it at the end.

Common Shader Patterns

Vertex Displacement
javascript
const material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
amplitude: { value: 0.2 },
},
vertexShader: /* glsl */ `
uniform float time;
uniform float amplitude;
void main() {
vec3 pos = position;
pos.z += sin(pos.x * 5.0 + time) * amplitude;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: /* glsl */ `
void main() {
gl_FragColor = vec4(0.5, 0.8, 1.0, 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
`,
});
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2, 64, 64), material); // displacement needs subdivisions
Fresnel

Use one space consistently. World space with cameraPosition:

javascript
const material = new THREE.ShaderMaterial({
vertexShader: /* glsl */ `
varying vec3 vWorldNormal;
varying vec3 vWorldPosition;
void main() {
vWorldNormal = normalize(mat3(modelMatrix) * normal); // world space, uniform scale assumed
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: /* glsl */ `
varying vec3 vWorldNormal;
varying vec3 vWorldPosition;
void main() {
vec3 viewDirection = normalize(cameraPosition - vWorldPosition);
float fresnel = pow(1.0 - max(dot(viewDirection, vWorldNormal), 0.0), 3.0);
gl_FragColor = vec4(mix(vec3(0.0, 0.0, 0.5), vec3(0.5, 0.8, 1.0), fresnel), 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}
`,
});

normalMatrix * normal is view space — pair it with the view-space direction to the camera, never with cameraPosition. For rim lighting keep both in view space: in the vertex shader vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); vViewDir = -mvPosition.xyz; vNormal = normalMatrix * normal;, in the fragment shader float rim = pow(1.0 - max(dot(normalize(vViewDir), normalize(vNormal)), 0.0), 4.0);.

Dissolve
glsl
uniform float progress;
uniform sampler2D noiseMap;
varying vec2 vUv;
void main() {
float noise = texture(noiseMap, vUv).r;
if (noise < progress) discard;
float edge = smoothstep(progress, progress + 0.1, noise);
gl_FragColor = vec4(mix(vec3(1.0, 0.5, 0.0), vec3(0.5), edge), 1.0);
#include <tonemapping_fragment>
#include <colorspace_fragment>
}

Instanced Meshes and Custom Attributes

Three.js declares position, normal, uv, instanceMatrix (for an InstancedMesh), instanceColor (once setColorAt has been called) and color (with vertexColors: true). Anything else — a custom per-vertex attribute, uv1 to uv3, tangent — you declare yourself with attribute (the alias for in).

javascript
const instanceCount = 100;
const geometry = new THREE.PlaneGeometry(0.2, 0.2);
const offsets = new Float32Array(instanceCount * 3);
// fill offsets with one vec3 per instance
geometry.setAttribute("aOffset", new THREE.InstancedBufferAttribute(offsets, 3));
const material = new THREE.ShaderMaterial({
vertexShader: /* glsl */ `
attribute vec3 aOffset;
void main() {
// instanceMatrix is declared by three.js; the shader must apply it
gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position + aOffset, 1.0);
}
`,
fragmentShader: /* glsl */ `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`,
});
const mesh = new THREE.InstancedMesh(geometry, material, instanceCount);
mesh.frustumCulled = false; // the bounds come from instanceMatrix and ignore aOffset

For a per-vertex attribute use geometry.setAttribute("aOffset", new THREE.BufferAttribute(new Float32Array(geometry.attributes.position.count * 3), 3)); — the shader declaration is the same.

Extending Built-in Materials

onBeforeCompile rewrites the shader strings of a built-in material. It runs once per program, before includes are resolved.

javascript
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
material.onBeforeCompile = (shader) => {
shader.uniforms.time = { value: 0 };
material.userData.shader = shader; // keep the reference for updates
shader.vertexShader = "uniform float time;\n" + shader.vertexShader;
shader.vertexShader = shader.vertexShader.replace(
"#include <begin_vertex>",
`
#include <begin_vertex>
transformed.y += sin(position.x * 10.0 + time) * 0.1;
`,
);
};
// in the frame loop, with timer.update() first as in the Quick Start:
// if (material.userData.shader) material.userData.shader.uniforms.time.value = timer.getElapsed();

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
1,846
Repository forks
204
7-day trend score
38

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 21 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/cloudai-x/threejs-skills/threejs-shaders/badge)](https://verified-skill.com/skills/cloudai-x/threejs-skills/threejs-shaders)

Something wrong with this skill or its page? Tell us.