mcpField
S
shader-glsl

shader-glsl

github.com/iart-ai创意与媒体451.5k 次安装18 周前更新
代码仓库

关于 shader-glsl

这是一项用于编写 GLSL GPU 片段着色器的技能,适用于生成视觉效果、图像转场和后期处理。内容包括着色器技巧、Three.js 配置、性能建议,以及渲染和检查可复现帧的流程。

主要功能

  1. 01生成动画渐变、噪声和图案
  2. 02绘制 SDF 形状与抗锯齿边缘
  3. 03制作溶解、位移和故障转场
  4. 04配置 Three.js ShaderMaterial
  5. 05移动设备与着色器性能建议

适用场景

  1. 01制作动画背景
  2. 02构建图像转场
  3. 03添加后期处理效果
  4. 04在固定时间检查着色器帧

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/iart-ai/webgl-animation-skills --skill shader-glsl

安装后在 Claude Code 中输入/shader-glsl确认已加载。

附带文件README.mdreferences/glsl-cookbook.md

详细介绍

着色器 / GLSL

编写 GPU 片元着色器,用于生成动态效果、渐变、转场和后期处理。片元着色器会并行地为每个像素运行一次,是实现全屏生成式动态效果的最高性能方式。

适用场景

  • 动态渐变、噪声、等离子或极光背景。
  • 图像转场:溶解、位移、故障、波纹、擦除。
  • 扭曲、色差、生成式图案、SDF 形状。
  • 对渲染场景执行后期处理。

片元着色器骨架

每个片元着色器都会计算一个像素的颜色。先将坐标归一化并校正宽高比,再构建颜色。

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。

核心构件

smoothstep + mix 是最常用的函数。smoothstep(e0, e1, x) 会生成从 0 到 1 的平滑渐变;mix(a, b, t) 会进行线性混合。按一个像素的宽度对边缘进行抗锯齿处理:

float px = fwidth(d);                    // screen-space derivative
float mask = smoothstep(px, -px, d);     // crisp AA edge from SDF distance d

哈希 + 值噪声(无需纹理):

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;
}

完整的值噪声、单纯形噪声和 fbm 变体见 references/glsl-cookbook.md。

SDF 形状可生成与分辨率无关的清晰几何图形。距离值在内部为负,在外部为正:

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);

域扭曲可实现流动、大理石纹理效果——将噪声输入另一个噪声:

vec2 q = vec2(fbm(p), fbm(p + vec2(5.2, 1.3)));
float n = fbm(p + 4.0 * q + u_time * 0.1);

图像转场

采样两个纹理,并根据进度 uniform u_progress(0→1)逐像素混合。

溶解 / 噪声擦除——通过对噪声进行阈值处理来显示图像:

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:

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 通道(色差):

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 接线

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。

移动设备 / 性能

  • 在不需要 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。

交付与验证(独立 HTML)

打包的辅助脚本(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"> 中。
  • 着色器是 uniforms 的纯函数——所有效果都由 u_time(转场还包括 u_progress)驱动。所有动画都通过一个可固定的 uniform 控制。
  • 着色器中的随机性已由确定性的 hash(uv) 提供——无需逐帧设定随机种子;只要不要在 u_time 之外使用墙上时钟即可。

跳转/冻结测试框架——在固定时间渲染一帧以供截图。 ?t=N 会将 u_time(以及可选的 u_progress)设为 N,渲染一帧后停止循环,生成确定性的静帧。

<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 会提供该上下文(swiftshader)。

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)截图——符合需求,没有色带/NaN/黑屏。
  4. 如果嵌入 SPA,应正确释放资源且无泄漏(material.dispose()、geometry.dispose()、renderer.dispose(),并停止循环)。
  5. 遵循 prefers-reduced-motion——冻结 u_time,或在动画仅用于装饰时降低动画速度。

快速参考

目标基元
动态渐变余弦调色板 a + b*cos(...)
有机纹理fbm(uv * scale + time)
清晰形状SDF + smoothstep(fwidth(d), -fwidth(d), d)
流体/大理石纹域扭曲:噪声输入噪声
显现转场将阈值噪声与 u_progress 比较
故障效果行哈希偏移 + RGB 通道偏移
抗锯齿边缘使用 fwidth(d) 获取屏幕空间宽度

参考文件

  • references/glsl-cookbook.md — 完整的值噪声和单纯形噪声 + fbm 实现、IQ 余弦调色板配方、包含布尔运算和圆角的完整 SDF 形状库、域扭曲、作为完整着色器实现的三种图像转场(溶解/位移/故障效果)、Three.js uniform/纹理接线、GLSL ES 3.00 迁移,以及移动端精度注意事项。

评论

支持 Markdown