index.frag

#version 300 es

// 全ての浮動小数点型の変数に高い精度を指定
precision highp float;

#pragma glslify: bilerp_colors4 = require('sketchgl/glsl/bilerp-colors4');

uniform vec2 uResolution;
uniform float uTime;

out vec4 outColor;

void main() {
  // フラグメント座標を正規化
  vec2 pos = gl_FragCoord.xy / uResolution.xy;
  
  vec3[4] colors4 = vec3[](
    vec3(1.0, 0.0, 0.0),
    vec3(1.0, 1.0, 0.0),
    vec3(1.0, 0.0, 1.0),
    vec3(1.0, 1.0, 1.0)
  );
  
  // 階調数
  float n = 8.0;
  // 範囲の始点と終点を動かすパラメータ
  float thr = 0.25 * sin(uTime);
  
  // フラグメント座標範囲を[0, n]区間にスケール
  pos *= n;
  // フラグメント座標を滑らかに階段化
  pos = floor(pos) + smoothstep(0.25 + thr, 0.75 - thr, fract(pos));
  // フラグメント座標範囲を[0, 1]区間に正規化
  pos /= n;
  
  vec3 bilerpColor = bilerp_colors4(colors4, pos);
  
  outColor = vec4(bilerpColor, 1.0);
}

render.ts

import { SketchFrg, type FragmentSketchConfig, type FragmentSketchFn } from "sketchgl"
import { Uniforms } from "sketchgl/program"
import { Timer } from "sketchgl/interactive"

import frag from "./index.frag"

const sketch: FragmentSketchFn = ({ gl, canvas, program, renderToCanvas }) => {
  const uniforms = new Uniforms(gl, ["uResolution", "uTime"])
  uniforms.init(program)

  const timer = new Timer()
  timer.start()

  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  gl.clearDepth(1.0)

  return {
    drawOnFrame() {
      gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

      uniforms.fvector2("uResolution", [canvas.width, canvas.height])
      uniforms.float("uTime", timer.elapsed * 0.001)

      renderToCanvas()
    }
  }
}

export const onload = () => {
  const config: FragmentSketchConfig = {
    frag,
    canvas: {
      el: "gl-canvas",
      fit: "screen",
      autoResize: true
    }
  }
  SketchFrg.init(config, sketch)
}