shader-glsl

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Shader / 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:
a + b*cos(2π*(c*t + d))
with tunable
a,b,c,d
vec3s.
每个片段着色器都会计算单个像素的颜色。先归一化坐标、校正宽高比,再生成颜色。
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调色板)是快速实现美观动画渐变的最佳方案:
a + b*cos(2π*(c*t + d))
,其中
a,b,c,d
为可调节的vec3变量。

Core building blocks

核心构建模块

smoothstep + mix are the workhorses.
smoothstep(e0, e1, x)
gives a smooth 0→1 ramp;
mix(a, b, t)
linearly blends. Antialias an edge by the width of one pixel:
glsl
float px = fwidth(d);                    // screen-space derivative
float mask = smoothstep(px, -px, d);     // crisp AA edge from SDF distance d
Hash + 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.md
.
SDF 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是核心工具。
smoothstep(e0, e1, x)
生成平滑的0→1过渡曲线;
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.md
SDF形状提供与分辨率无关的清晰几何图形。内部距离为负,外部为正:
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
u_progress
(0→1).
Dissolve / 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变量
u_progress
(0→1)逐像素混合。
溶解 / 噪波擦除——通过噪波阈值逐步显示:
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
position
directly and skip projection (as above). For shaders applied to real geometry, pass
vUv
from the vertex shader via
varying 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);
});
如果要通过普通相机实现全屏通道,编写顶点着色器直接输出
position
并跳过投影(如上所示)。如果将着色器应用于实际几何体,需通过
varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }
从顶点着色器传递
vUv

Mobile / performance

移动端 / 性能优化

  • Declare
    precision mediump float;
    on mobile when highp is not needed; some effects (large coordinates, deep fbm) require
    highp
    .
  • Cap pixel ratio:
    renderer.setPixelRatio(Math.min(devicePixelRatio, 2))
    . Render to a lower-res target and upscale for heavy shaders.
  • Loops must have constant bounds in GLSL ES — no dynamic loop counts. Keep fbm octaves ≤ 5–6.
  • Avoid
    if
    /branches in hot paths; prefer
    mix
    /
    step
    /
    smoothstep
    . Minimize
    texture2D
    calls; avoid dependent texture reads where possible.
  • WebGL2/GLSL ES 3.00 enables
    texelFetch
    , integer ops, and
    textureLod
    ; declare
    #version 300 es
    and use
    in
    /
    out
    /
    fragColor
    .
  • 当不需要highp精度时,在移动端声明
    precision mediump float;
    ;部分效果(大坐标、深层fbm)需要
    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
    /
    out
    /
    fragColor

Deliver & verify (standalone HTML)

交付与验证(独立HTML)

Packaged helper (
scripts/
):
scripts/seek-shot.sh anim.html 0 1.5 3
freezes the
?t=N
harness and screenshots each moment;
scripts/contact-sheet.sh sheet.png frame-*.png
tiles them for one-glance review. See
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
    .html
    : importmap pins
    three
    to a CDN; the GLSL string,
    ShaderMaterial
    , full-screen quad, and render loop in one inline
    <script type="module">
    .
  • The shader is a pure function of uniforms — drive everything from
    u_time
    (and
    u_progress
    for transitions). All animation flows through one uniform you can pin.
  • Any in-shader randomness already comes from a deterministic
    hash(uv)
    — no per-frame seeding needed; just don't feed it wall-clock outside
    u_time
    .
Seek/freeze harness — render ONE frame at a fixed time for screenshots.
?t=N
sets
u_time
(and optionally
u_progress
) to
N
, renders one frame, and stops the loop — a deterministic still.
html
<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 (
?t=0
,
?t=<mid>
,
?t=<end>
; for a transition use
u_progress
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
pow
/
log
of negatives), missing texture for transitions (CDN/asset 404). WebGL needs a GPU context; Playwright/Chromium supplies one (swiftshader) headless.
bash
npx playwright screenshot --wait-for-timeout=600 "file://$PWD/shader.html?t=2.0" frame-mid.png
Before you finish:
  1. Canvas renders — not black/blank, no shader-compile or console errors, no CDN 404s.
  2. ?t=N
    freezes a reproducible frame (same N → same pixels;
    u_time
    is the only clock).
  3. Screenshotted at start / mid / end (or progress 0/0.5/1) — matches the brief, no banding/NaN/black.
  4. Disposed and leak-free if embedded in an SPA (
    material.dispose()
    ,
    geometry.dispose()
    ,
    renderer.dispose()
    , stop the loop).
  5. prefers-reduced-motion
    honored — freeze
    u_time
    or slow the animation where motion is decorative.
打包工具
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,包含一个全屏四边形、一个渲染循环,无需构建步骤。单个文件是着色器的最佳交付形式;能通过单个文件实现的功能,无需使用打包工具。
输出规范:
  • 单个
    .html
    文件:通过importmap将
    three
    固定到CDN;GLSL字符串、
    ShaderMaterial
    、全屏四边形和渲染循环均内联在
    <script type="module">
  • 着色器是uniform变量的纯函数——所有动画均由
    u_time
    (过渡效果则加上
    u_progress
    )驱动。所有动画逻辑都通过一个可固定的uniform变量控制
  • 着色器内的随机性均来自确定性的
    hash(uv)
    ——无需逐帧播种;只需确保不将
    u_time
    之外的实时时钟输入其中
帧定位/冻结工具——渲染固定时间点的单帧用于截图
?t=N
可将
u_time
(可选
u_progress
)设置为
N
,渲染一帧后停止循环——实现确定性静态画面。
html
<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>
验证流程——渲染→冻结→截图→检查: 在三个时间点打开文件——开始、中间、结束(
?t=0
?t=<mid>
?t=<end>
;过渡效果则使用
u_progress
为0/0.5/1)——分别截图,检查保真度(符合需求)和瑕疵黑色/空白画布表示着色器编译或解析错误(查看控制台的GLSL日志)、色带、NaN溢出(
pow
/
log
处理负数导致的白色/乱码像素)、过渡效果缺失纹理(CDN/资源404)。WebGL需要GPU上下文;Playwright/Chromium可提供headless模式的swiftshader上下文。
bash
npx playwright screenshot --wait-for-timeout=600 "file://$PWD/shader.html?t=2.0" frame-mid.png
交付前检查:
  1. 画布可正常渲染——无黑色/空白、着色器编译或控制台错误、CDN 404
  2. ?t=N
    可冻结可复现的帧(相同N对应相同像素;
    u_time
    是唯一时钟源)
  3. 在开始/中间/结束时间点(或进度0/0.5/1)截图——符合需求,无banding/NaN/黑色瑕疵
  4. 若嵌入SPA中,需无内存泄漏(调用
    material.dispose()
    geometry.dispose()
    renderer.dispose()
    ,停止循环)
  5. 遵循
    prefers-reduced-motion
    规范——若动画仅为装饰性,冻结
    u_time
    或减慢动画速度

Quick reference

速查表

GoalPrimitive
Animated gradientcosine palette
a + b*cos(...)
Organic texture
fbm(uv * scale + time)
Crisp shapeSDF +
smoothstep(fwidth(d), -fwidth(d), d)
Fluid / marbledomain warp: noise into noise
Reveal transitionthreshold noise vs
u_progress
Glitchrow hash shift + RGB channel offset
AA edge
fwidth(d)
for screen-space width
目标核心实现
动画渐变余弦调色板
a + b*cos(...)
有机纹理
fbm(uv * scale + time)
清晰形状SDF +
smoothstep(fwidth(d), -fwidth(d), d)
流畅/大理石纹理域扭曲:噪波输入到噪波函数
渐显过渡噪波与
u_progress
的阈值对比
故障效果行哈希偏移 + RGB通道偏移
抗锯齿边缘使用
fwidth(d)
获取屏幕空间宽度

Reference files

参考文件

  • references/glsl-cookbook.md
    — 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迁移指南及移动端精度注意事项。