shader-glsl
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseShader / GLSL
Shader / GLSL
Write GPU fragment shaders for generative motion, gradients, transitions, and post-processing. Fragment shaders run once per pixel in parallel — the most performant way to do full-screen generative motion.
编写用于生成式动画、渐变、过渡和后期处理的GPU片段着色器。片段着色器以并行方式为每个像素运行一次——这是实现全屏生成式动画的最高效方式。
When to use
使用场景
- Animated gradient, noise, plasma, or aurora backgrounds.
- Image transitions: dissolve, displacement, glitch, ripple, wipe.
- Distortion, chromatic aberration, generative patterns, SDF shapes.
- Post-processing passes over a rendered scene.
- 动画渐变、噪波、等离子或极光背景
- 图像过渡效果:溶解、位移、故障、波纹、擦除
- 畸变、色差、生成式图案、SDF形状
- 渲染场景的后期处理通道
Fragment shader skeleton
片段着色器骨架
Every fragment shader computes a color for one pixel. Normalize coordinates, aspect-correct, then build color.
glsl
precision highp float;
uniform float u_time;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy; // 0..1
vec2 p = uv * 2.0 - 1.0; // -1..1, centered
p.x *= u_resolution.x / u_resolution.y; // aspect-correct
vec3 col = 0.5 + 0.5 * cos(u_time + p.xyx + vec3(0.0, 2.0, 4.0));
gl_FragColor = vec4(col, 1.0);
}The cosine-palette line above (Inigo Quilez palettes) is the fastest route to a good-looking animated gradient: with tunable vec3s.
a + b*cos(2π*(c*t + d))a,b,c,d每个片段着色器都会计算单个像素的颜色。先归一化坐标、校正宽高比,再生成颜色。
glsl
precision highp float;
uniform float u_time;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy; // 0..1
vec2 p = uv * 2.0 - 1.0; // -1..1, centered
p.x *= u_resolution.x / u_resolution.y; // aspect-correct
vec3 col = 0.5 + 0.5 * cos(u_time + p.xyx + vec3(0.0, 2.0, 4.0));
gl_FragColor = vec4(col, 1.0);
}上面的余弦调色板代码(Inigo Quilez调色板)是快速实现美观动画渐变的最佳方案:,其中为可调节的vec3变量。
a + b*cos(2π*(c*t + d))a,b,c,dCore building blocks
核心构建模块
smoothstep + mix are the workhorses. gives a smooth 0→1 ramp; linearly blends. Antialias an edge by the width of one pixel:
smoothstep(e0, e1, x)mix(a, b, t)glsl
float px = fwidth(d); // screen-space derivative
float mask = smoothstep(px, -px, d); // crisp AA edge from SDF distance dHash + value noise (no textures needed):
glsl
float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
float noise(vec2 p){
vec2 i = floor(p), f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f); // smooth interpolation
return mix(mix(hash(i), hash(i + vec2(1,0)), u.x),
mix(hash(i + vec2(0,1)), hash(i + vec2(1,1)), u.x), u.y);
}
float fbm(vec2 p){ // fractal noise, organic
float v = 0.0, a = 0.5;
for (int i = 0; i < 5; i++){ v += a * noise(p); p *= 2.0; a *= 0.5; }
return v;
}Full value/simplex noise and fbm variants are in .
references/glsl-cookbook.mdSDF shapes give resolution-independent crisp geometry. Distance is negative inside, positive outside:
glsl
float sdCircle(vec2 p, float r){ return length(p) - r; }
float sdBox(vec2 p, vec2 b){ vec2 d = abs(p) - b; return length(max(d,0.0)) + min(max(d.x,d.y),0.0); }
// render: float m = smoothstep(fwidth(d), -fwidth(d), d);Domain warping for fluid, marbled looks — feed noise into noise:
glsl
vec2 q = vec2(fbm(p), fbm(p + vec2(5.2, 1.3)));
float n = fbm(p + 4.0 * q + u_time * 0.1);smoothstep + mix是核心工具。生成平滑的0→1过渡曲线;实现线性混合。通过单个像素宽度实现边缘抗锯齿:
smoothstep(e0, e1, x)mix(a, b, t)glsl
float px = fwidth(d); // screen-space derivative
float mask = smoothstep(px, -px, d); // crisp AA edge from SDF distance d哈希函数 + 值噪波(无需纹理):
glsl
float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
float noise(vec2 p){
vec2 i = floor(p), f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f); // smooth interpolation
return mix(mix(hash(i), hash(i + vec2(1,0)), u.x),
mix(hash(i + vec2(0,1)), hash(i + vec2(1,1)), u.x), u.y);
}
float fbm(vec2 p){ // fractal noise, organic
float v = 0.0, a = 0.5;
for (int i = 0; i < 5; i++){ v += a * noise(p); p *= 2.0; a *= 0.5; }
return v;
}完整的值噪波/ simplex噪波及fbm变体可查看。
references/glsl-cookbook.mdSDF形状提供与分辨率无关的清晰几何图形。内部距离为负,外部为正:
glsl
float sdCircle(vec2 p, float r){ return length(p) - r; }
float sdBox(vec2 p, vec2 b){ vec2 d = abs(p) - b; return length(max(d,0.0)) + min(max(d.x,d.y),0.0); }
// render: float m = smoothstep(fwidth(d), -fwidth(d), d);域扭曲实现流畅、大理石纹理效果——将噪波输入到噪波函数中:
glsl
vec2 q = vec2(fbm(p), fbm(p + vec2(5.2, 1.3)));
float n = fbm(p + 4.0 * q + u_time * 0.1);Image transitions
图像过渡效果
Sample two textures and blend per pixel by a progress uniform (0→1).
u_progressDissolve / noise wipe — reveal by thresholding noise:
glsl
float n = noise(uv * 20.0);
float edge = smoothstep(u_progress - 0.05, u_progress, n);
gl_FragColor = mix(texture2D(tex0, uv), texture2D(tex1, uv), 1.0 - edge);Displacement — push UVs using a displacement map before sampling:
glsl
float disp = texture2D(dispTex, uv).r;
vec2 d0 = uv + vec2(disp * u_progress * 0.3, 0.0);
vec2 d1 = uv - vec2(disp * (1.0 - u_progress) * 0.3, 0.0);
gl_FragColor = mix(texture2D(tex0, d0), texture2D(tex1, d1), u_progress);Glitch — block-shift rows by time, split RGB channels (chromatic aberration):
glsl
float row = floor(uv.y * 20.0);
float shift = (hash(vec2(row, floor(u_time * 12.0))) - 0.5) * 0.1 * u_glitch;
vec2 g = uv + vec2(shift, 0.0);
vec3 c;
c.r = texture2D(tex, g + vec2(0.005, 0.0)).r; // channel offset
c.g = texture2D(tex, g).g;
c.b = texture2D(tex, g - vec2(0.005, 0.0)).b;
gl_FragColor = vec4(c, 1.0);采样两张纹理,并根据进度uniform变量(0→1)逐像素混合。
u_progress溶解 / 噪波擦除——通过噪波阈值逐步显示:
glsl
float n = noise(uv * 20.0);
float edge = smoothstep(u_progress - 0.05, u_progress, n);
gl_FragColor = mix(texture2D(tex0, uv), texture2D(tex1, uv), 1.0 - edge);位移过渡——采样前使用位移图偏移UV坐标:
glsl
float disp = texture2D(dispTex, uv).r;
vec2 d0 = uv + vec2(disp * u_progress * 0.3, 0.0);
vec2 d1 = uv - vec2(disp * (1.0 - u_progress) * 0.3, 0.0);
gl_FragColor = mix(texture2D(tex0, d0), texture2D(tex1, d1), u_progress);故障效果——按时间偏移行像素,分离RGB通道(色差):
glsl
float row = floor(uv.y * 20.0);
float shift = (hash(vec2(row, floor(u_time * 12.0))) - 0.5) * 0.1 * u_glitch;
vec2 g = uv + vec2(shift, 0.0);
vec3 c;
c.r = texture2D(tex, g + vec2(0.005, 0.0)).r; // channel offset
c.g = texture2D(tex, g).g;
c.b = texture2D(tex, g - vec2(0.005, 0.0)).b;
gl_FragColor = vec4(c, 1.0);Three.js ShaderMaterial wiring
Three.js ShaderMaterial 连接配置
js
import * as THREE from 'three';
const uniforms = {
u_time: { value: 0 },
u_resolution: { value: new THREE.Vector2(innerWidth, innerHeight) },
u_mouse: { value: new THREE.Vector2(0, 0) },
};
const material = new THREE.ShaderMaterial({
uniforms,
vertexShader: `void main(){ gl_Position = vec4(position, 1.0); }`,
fragmentShader: FRAG_SRC, // your GLSL string
});
// Full-screen triangle/quad: a plane that covers clip space.
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
const scene = new THREE.Scene(); scene.add(mesh);
const camera = new THREE.Camera(); // no projection needed for clip-space quad
const renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
uniforms.u_time.value = clock.getElapsedTime();
renderer.render(scene, camera);
});
addEventListener('resize', () => {
renderer.setSize(innerWidth, innerHeight);
uniforms.u_resolution.value.set(innerWidth, innerHeight);
});For a full-screen pass with a plain camera, write the vertex shader to output directly and skip projection (as above). For shaders applied to real geometry, pass from the vertex shader via .
positionvUvvarying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }js
import * as THREE from 'three';
const uniforms = {
u_time: { value: 0 },
u_resolution: { value: new THREE.Vector2(innerWidth, innerHeight) },
u_mouse: { value: new THREE.Vector2(0, 0) },
};
const material = new THREE.ShaderMaterial({
uniforms,
vertexShader: `void main(){ gl_Position = vec4(position, 1.0); }`,
fragmentShader: FRAG_SRC, // your GLSL string
});
// Full-screen triangle/quad: a plane that covers clip space.
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
const scene = new THREE.Scene(); scene.add(mesh);
const camera = new THREE.Camera(); // no projection needed for clip-space quad
const renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
uniforms.u_time.value = clock.getElapsedTime();
renderer.render(scene, camera);
});
addEventListener('resize', () => {
renderer.setSize(innerWidth, innerHeight);
uniforms.u_resolution.value.set(innerWidth, innerHeight);
});如果要通过普通相机实现全屏通道,编写顶点着色器直接输出并跳过投影(如上所示)。如果将着色器应用于实际几何体,需通过从顶点着色器传递。
positionvarying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }vUvMobile / performance
移动端 / 性能优化
- Declare on mobile when highp is not needed; some effects (large coordinates, deep fbm) require
precision mediump float;.highp - Cap pixel ratio: . Render to a lower-res target and upscale for heavy shaders.
renderer.setPixelRatio(Math.min(devicePixelRatio, 2)) - Loops must have constant bounds in GLSL ES — no dynamic loop counts. Keep fbm octaves ≤ 5–6.
- Avoid /branches in hot paths; prefer
if/mix/step. Minimizesmoothstepcalls; avoid dependent texture reads where possible.texture2D - WebGL2/GLSL ES 3.00 enables , integer ops, and
texelFetch; declaretextureLodand use#version 300 es/in/out.fragColor
- 当不需要highp精度时,在移动端声明;部分效果(大坐标、深层fbm)需要
precision mediump float;精度highp - 限制像素比:。对于复杂着色器,可渲染到低分辨率目标再放大
renderer.setPixelRatio(Math.min(devicePixelRatio, 2)) - GLSL ES中的循环必须有固定边界——不允许动态循环次数。fbm八度数以≤5–6为宜
- 避免在热点路径中使用/分支;优先使用
if/mix/step。尽量减少smoothstep调用;尽可能避免依赖纹理读取texture2D - WebGL2/GLSL ES 3.00支持、整数运算和
texelFetch;需声明textureLod并使用#version 300 es/in/outfragColor
Deliver & verify (standalone HTML)
交付与验证(独立HTML)
Packaged helper ():scripts/freezes thescripts/seek-shot.sh anim.html 0 1.5 3harness and screenshots each moment;?t=Ntiles them for one-glance review. Seescripts/contact-sheet.sh sheet.png frame-*.png.scripts/README.md
For a self-contained shader (gradient/noise background, transition, generative loop) the deliverable is one HTML file that opens directly in a browser — Three.js from a CDN via an importmap, one full-screen quad, one render loop, no build step. A single file is the right tier for a shader; don't reach for a bundler when one file does the job.
Output contract:
- One : importmap pins
.htmlto a CDN; the GLSL string,three, full-screen quad, and render loop in one inlineShaderMaterial.<script type="module"> - The shader is a pure function of uniforms — drive everything from (and
u_timefor transitions). All animation flows through one uniform you can pin.u_progress - Any in-shader randomness already comes from a deterministic — no per-frame seeding needed; just don't feed it wall-clock outside
hash(uv).u_time
Seek/freeze harness — render ONE frame at a fixed time for screenshots. sets (and optionally ) to , renders one frame, and stops the loop — a deterministic still.
?t=Nu_timeu_progressNhtml
<script type="module">
// ... uniforms, material, full-screen quad, renderer ...
const t = new URLSearchParams(location.search).get("t");
function frame(time) {
uniforms.u_time.value = time;
uniforms.u_progress && (uniforms.u_progress.value = Math.min(time, 1)); // transitions
renderer.render(scene, camera);
}
if (t !== null) {
frame(parseFloat(t)); // one fixed frame, no loop
window.__ready = true;
} else {
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => frame(clock.getElapsedTime()));
}
</script>Verify loop — render → freeze → screenshot → check: open at three instants — start, mid, end (, , ; for a transition use 0 / 0.5 / 1) — screenshot each, and check both fidelity (matches the brief) and artifacts: a black/blank canvas = shader compile or parse error (read the console for the GLSL log), banding, NaN blowout (white/garbage pixels from / of negatives), missing texture for transitions (CDN/asset 404). WebGL needs a GPU context; Playwright/Chromium supplies one (swiftshader) headless.
?t=0?t=<mid>?t=<end>u_progresspowlogbash
npx playwright screenshot --wait-for-timeout=600 "file://$PWD/shader.html?t=2.0" frame-mid.pngBefore you finish:
- Canvas renders — not black/blank, no shader-compile or console errors, no CDN 404s.
- freezes a reproducible frame (same N → same pixels;
?t=Nis the only clock).u_time - Screenshotted at start / mid / end (or progress 0/0.5/1) — matches the brief, no banding/NaN/black.
- Disposed and leak-free if embedded in an SPA (,
material.dispose(),geometry.dispose(), stop the loop).renderer.dispose() - honored — freeze
prefers-reduced-motionor slow the animation where motion is decorative.u_time
打包工具():scripts/可冻结scripts/seek-shot.sh anim.html 0 1.5 3测试工具并截取每个时刻的截图;?t=N可将截图拼接成预览图。详见scripts/contact-sheet.sh sheet.png frame-*.png。scripts/README.md
对于独立着色器(渐变/噪波背景、过渡效果、生成式循环),交付物应为可直接在浏览器中打开的单个HTML文件——通过importmap从CDN引入Three.js,包含一个全屏四边形、一个渲染循环,无需构建步骤。单个文件是着色器的最佳交付形式;能通过单个文件实现的功能,无需使用打包工具。
输出规范:
- 单个文件:通过importmap将
.html固定到CDN;GLSL字符串、three、全屏四边形和渲染循环均内联在ShaderMaterial中<script type="module"> - 着色器是uniform变量的纯函数——所有动画均由(过渡效果则加上
u_time)驱动。所有动画逻辑都通过一个可固定的uniform变量控制u_progress - 着色器内的随机性均来自确定性的——无需逐帧播种;只需确保不将
hash(uv)之外的实时时钟输入其中u_time
帧定位/冻结工具——渲染固定时间点的单帧用于截图。可将(可选)设置为,渲染一帧后停止循环——实现确定性静态画面。
?t=Nu_timeu_progressNhtml
<script type="module">
// ... uniforms, material, full-screen quad, renderer ...
const t = new URLSearchParams(location.search).get("t");
function frame(time) {
uniforms.u_time.value = time;
uniforms.u_progress && (uniforms.u_progress.value = Math.min(time, 1)); // transitions
renderer.render(scene, camera);
}
if (t !== null) {
frame(parseFloat(t)); // one fixed frame, no loop
window.__ready = true;
} else {
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => frame(clock.getElapsedTime()));
}
</script>验证流程——渲染→冻结→截图→检查: 在三个时间点打开文件——开始、中间、结束(、、;过渡效果则使用为0/0.5/1)——分别截图,检查保真度(符合需求)和瑕疵:黑色/空白画布表示着色器编译或解析错误(查看控制台的GLSL日志)、色带、NaN溢出(/处理负数导致的白色/乱码像素)、过渡效果缺失纹理(CDN/资源404)。WebGL需要GPU上下文;Playwright/Chromium可提供headless模式的swiftshader上下文。
?t=0?t=<mid>?t=<end>u_progresspowlogbash
npx playwright screenshot --wait-for-timeout=600 "file://$PWD/shader.html?t=2.0" frame-mid.png交付前检查:
- 画布可正常渲染——无黑色/空白、着色器编译或控制台错误、CDN 404
- 可冻结可复现的帧(相同N对应相同像素;
?t=N是唯一时钟源)u_time - 在开始/中间/结束时间点(或进度0/0.5/1)截图——符合需求,无banding/NaN/黑色瑕疵
- 若嵌入SPA中,需无内存泄漏(调用、
material.dispose()、geometry.dispose(),停止循环)renderer.dispose() - 遵循规范——若动画仅为装饰性,冻结
prefers-reduced-motion或减慢动画速度u_time
Quick reference
速查表
| Goal | Primitive |
|---|---|
| Animated gradient | cosine palette |
| Organic texture | |
| Crisp shape | SDF + |
| Fluid / marble | domain warp: noise into noise |
| Reveal transition | threshold noise vs |
| Glitch | row hash shift + RGB channel offset |
| AA edge | |
| 目标 | 核心实现 |
|---|---|
| 动画渐变 | 余弦调色板 |
| 有机纹理 | |
| 清晰形状 | SDF + |
| 流畅/大理石纹理 | 域扭曲:噪波输入到噪波函数 |
| 渐显过渡 | 噪波与 |
| 故障效果 | 行哈希偏移 + RGB通道偏移 |
| 抗锯齿边缘 | 使用 |
Reference files
参考文件
- — Full value and simplex noise + fbm implementations, IQ cosine-palette recipes, the complete SDF shape library with boolean ops and rounding, domain warping, all three image transitions (dissolve/displacement/glitch) as complete shaders, Three.js uniform/texture wiring, GLSL ES 3.00 migration, and mobile precision gotchas.
references/glsl-cookbook.md
- —— 完整的值噪波和simplex噪波+fbm实现、IQ余弦调色板方案、包含布尔运算和圆角的完整SDF形状库、域扭曲、三种完整的图像过渡(溶解/位移/故障)着色器、Three.js uniform/纹理配置、GLSL ES 3.00迁移指南及移动端精度注意事项。
references/glsl-cookbook.md