본문으로 건너뛰기
cccskills
무료GitHub에 공개

shader-web

Getting a shader onto a page - the WebGL bootstrap, uniform plumbing, device pixel ratio, resize, pause on hidden tab, reduced motion, the drop-in HTML file, parameter updates over postMessage, and embedding it without a framework. Read this when the shader has to run in a browser.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md7.7 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Shaders on the web

shader_target target=web writes one self-contained HTML file: no framework, no build step, no request. That is the deployment model the Radiant corpus uses, and it is the right default because a page that needs nothing cannot break because something is missing.

The bootstrap, in full

Everything a fragment shader page needs, and nothing else:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shader</title>
<style>
  html, body { margin: 0; height: 100%; overflow: hidden; background: #0a0a0a; }
  canvas { display: block; width: 100vw; height: 100vh; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl', { alpha: false, antialias: false });
if (!gl) throw new Error('no WebGL');

const VERTEX = 'attribute vec2 a_position; void main() { gl_Position = vec4(a_position, 0.0, 1.0); }';
const FRAGMENT = `precision highp float;
uniform vec2 u_resolution;
uniform float u_time;
void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution;
  gl_FragColor = vec4(uv.x, uv.y, 0.5 + 0.5 * sin(u_time), 1.0);
}`;

function compile(type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    throw new Error(gl.getShaderInfoLog(shader) || 'compile failed');
  }
  return shader;
}

const program = gl.createProgram();
gl.attachShader(program, compile(gl.VERTEX_SHADER, VERTEX));
gl.attachShader(program, compile(gl.FRAGMENT_SHADER, FRAGMENT));
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  throw new Error(gl.getProgramInfoLog(program) || 'link failed');
}
gl.useProgram(program);

const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
const at = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(at);
gl.vertexAttribPointer(at, 2, gl.FLOAT, false, 0, 0);

const uTime = gl.getUniformLocation(program, 'u_time');
const uRes = gl.getUniformLocation(program, 'u_resolution');

function resize() {
  // Cap the pixel ratio: a 3x phone rendering a 4K shader is a battery fire.
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  canvas.width = Math.round(canvas.clientWidth * dpr);
  canvas.height = Math.round(canvas.clientHeight * dpr);
  gl.viewport(0, 0, canvas.width, canvas.height);
}
window.addEventListener('resize', resize);
resize();

let running = true;
const start = performance.now();
function frame(now) {
  if (!running) return;
  gl.uniform1f(uTime, (now - start) / 1000);
  gl.uniform2f(uRes, canvas.width, canvas.height);
  gl.drawArrays(gl.TRIANGLES, 0, 3);
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

document.addEventListener('visibilitychange', () => {
  if (document.hidden) { running = false; }
  else if (!running) { running = true; requestAnimationFrame(frame); }
});
</script>
</body>
</html>

Every part of that is load-bearing:

  • preserveDrawingBuffer is not set here. Leave it off for animation (it costs performance); turn it ON if you need toDataURL() or a screenshot.
  • DPR capped at 2. canvas.width = clientWidth * devicePixelRatio uncapped renders four times the pixels on a 2x display for a difference nobody sees.
  • visibilitychange. A background tab that keeps drawing wastes a battery and is the most common complaint about shader backgrounds.
  • resize sets canvas.width, which resets the drawing buffer - so the viewport must be set again in the same handler, after the size.
  • The error is thrown with the log. A black canvas with nothing in the console is the worst outcome, and it is always avoidable.

Two contexts, two dialects

dialectcontextextra
es100 (gl_FragColor)getContext('webgl')call getExtension('OES_standard_derivatives') before compiling if you use fwidth
es300 (#version 300 es)getContext('webgl2')none
shadertoy / shader-skillsgetContext('webgl2')the page must supply iResolution, iTime, iMouse, iFrame

A WebGL 1 context rejects a #version 300 es shader with unsupported shader version 300 - which reads like a shader bug and is a context bug. shader_target target=web picks the right one for you.

Driving it from outside: the postMessage contract

A drop-in page has no API, so it takes parameter updates over postMessage. This is Radiant's own shape, and pages this row emits accept exactly it:

// on the parent page
const frame = document.querySelector('iframe');
frame.contentWindow.postMessage({ type: 'param', name: 'SPEED', value: 0.8 }, '*');

The page listens:

window.addEventListener('message', (event) => {
  const data = event.data;
  if (!data || data.type !== 'param') return;
  if (data.name === 'SPEED') speed = data.value;
});

Match on the parameter name (SPEED, as declared in // @param SPEED ...), not on the uniform name. The page owns the translation to u_speed.

Embedding it as a background

<iframe
  src="my-shader.html"
  title="Background"
  style="position: fixed; inset: 0; width: 100%; height: 100%; border: 0; z-index: -1;"
  aria-hidden="true"
  tabindex="-1"
></iframe>

Three habits: aria-hidden="true" (it is decoration, not content), z-index: -1 with the content above it, and no pointer events - an iframe over the page eats every click. If it must sit behind text, put it in a container with pointer-events: none.

Do not put a shader behind text without checking contrast: a moving background behind body copy is a readability failure waiting to happen. Either keep it behind a scrim (background: rgba(0,0,0,.55) over the canvas) or keep it in a hero band.

Inline instead of an iframe

If the page already has a canvas, the bootstrap above drops in directly: keep the program in a module, expose setParam(name, value) and resize(). The iframe is only needed when the shader is a file - which is the case for a drop-in background, and which is why the emitted artefact is a whole page.

Reduced motion is not optional

const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const time = reduced ? 0 : (now - start) / 1000;

An animation that ignores this preference is an accessibility defect, not a feature. The page this row emits freezes time at 0 when the preference is set, so the shader still shows its picture, unmoving.

What to check before you ship it

  1. It renders at the size it will be used at - not at 800x600.
  2. No console output. Warnings are still read as errors by people who look.
  3. It stops when the tab is hidden (watch requestAnimationFrame in the profiler).
  4. It survives a resize and a device rotation.
  5. It costs what you think: on a 4K display a heavy raymarcher is 8.3M pixels per frame. shader_render at your intended size is the honest measurement.
  6. Nothing reaches the network. A shader needs no font, no image, no CDN.

shader_verify proves the GLSL; the page is where the driver, the DPR and the resize behaviour get their say. Test it in the browser you are shipping to.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Author an editorial HTML+SVG diagram with the Diagram Design kit - choose the visual type, read its reference, start from an example, and satisfy the safety and accessibility contract that diagram_write({kind:"html"}) enforces.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

vecnode/vn-code102026년 10월 20일 업데이트

Control this machine's Docker or Podman engine through the docker_* tools - read the engine's state first, list containers and images, inspect one, tail its logs, pull an image and start it, then stop, restart or remove what you started. Covers the engine that is asleep behind Docker Desktop and the exact call for each verb, the confirm rule removals obey, and the flags the tools deliberately do not have.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

vecnode/vn-code102026년 10월 20일 업데이트

Drive ffmpeg through the media_run tool as an argv array - remux instead of re-encode, map streams explicitly, trim at keyframes, scale and crop, extract audio and frames, and build GIFs, contact sheets and concatenations. Covers the flags this pack injects, reading a failure from its exit code and last log lines, and the version-dependent spellings to hedge.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

vecnode/vn-code102026년 10월 20일 업데이트

Read a media file's real structure with ffprobe through media_run - the JSON invocation and how to read it, what every family of field means, how to count frames, sample without decoding everything, detect HDR and interlacing, and diagnose a missing duration, a stream with no frames or a container that lies. Covers when media_probe's own report is already enough.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

vecnode/vn-code102026년 10월 20일 업데이트

Writing a fragment shader that looks like something - the full-screen triangle, uv space, signed distance fields, noise and fbm, domain warping, raymarching budgets, tone mapping and banding. Every example compiles. Read this when the task is "make it look good", not "make it compile".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

vecnode/vn-code102026년 10월 20일 업데이트

Use when the user wants to design, redesign, shape, critique, audit, polish, simplify, harden, animate, or otherwise improve a frontend interface: websites, landing pages, dashboards, product UI, components, forms, settings, onboarding, empty states, and native iOS or Android screens. Handles UX review, hierarchy, layout, typography, color, motion, UX copy, accessibility, responsive behavior, edge cases, and capturing or documenting design systems (DESIGN.md, tokens). Also for making bland UI bolder, loud UI quieter, or ambitious visual effects. Not for backend-only or non-UI tasks.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

vecnode/vn-code102026년 10월 20일 업데이트

vecnode의 스킬 모두 보기

이 스킬의 문제 신고