update.vert

#version 300 es

layout (location = 0) in vec3 aVertexPosition;
layout (location = 1) in vec3 aVelocity;
layout (location = 2) in vec4 aVertexColor;

uniform float uTime;
uniform vec2 uMouse; // -1.0 ~ 1.0
uniform float uMove; // 0.0 ~ 1.0

out vec3 vVertexPosition;
out vec3 vVelocity;
out vec4 vVertexColor;

void main(){
  vVertexPosition = aVertexPosition + aVelocity * 0.1 * uMove;
  
  vec3 p = vec3(uMouse, sin(uTime) * 0.25) - aVertexPosition;
  vVelocity = normalize(aVelocity + p * 0.2 * uMove);
  
  vVertexColor = aVertexColor;
}

update.frag

#version 300 es

precision highp float;

out vec4 fragColor;

void main(){
  fragColor = vec4(1.0);
}

render.vert

#version 300 es

layout (location = 0) in vec3 aVertexPosition;
layout (location = 1) in vec3 aVelocity;
layout (location = 2) in vec4 aVertexColor;

uniform mat4 uMatrix;
uniform float uMove;

out vec4 vVertexColor;

void main(){
  vVertexColor = aVertexColor + vec4(aVelocity, 0.0);
  gl_Position = uMatrix * vec4(aVertexPosition, 1.0);
  gl_PointSize = 1.0 * (1.0 + uMove);
}

render.frag

#version 300 es

precision highp float;

in vec4 vVertexColor;

out vec4 fragColor;

void main(){
  fragColor = vVertexColor;
}

render.ts

import { SketchGl, type SketchConfig, type SketchFn } from "sketchgl"
import { ImageCanvas } from "sketchgl/texture"
import { ImageInterleavedData } from "sketchgl/utility"
import { SwapTFRenderer } from "sketchgl/renderer"
import { Uniforms } from "sketchgl/program"
import { Matrix4, Vector3 } from "sketchgl/math"
import { SpreadTextureInteraction } from "@/lib/feature/particle/spread-texture"
import { Timer } from "sketchgl/interactive"

import vertForUpdate from "./update.vert?raw"
import fragForUpdate from "./update.frag?raw"

import vertForRender from "./render.vert?raw"
import fragForRender from "./render.frag?raw"

import image from "@/assets/542x542/waterpaint-pink-purple.png"

const sketch: SketchFn = ({ gl, canvas }) => {
  const uniformsFor = {
    update: new Uniforms(gl, ["uTime", "uMouse", "uMove"]),
    render: new Uniforms(gl, ["uMatrix", "uMove"])
  }

  const matV = Matrix4.view(new Vector3(0, 0, 3), new Vector3(0, 0, 0), new Vector3(0, 1, 0))
  const matP = Matrix4.perspective(45, canvas.width / canvas.height, 0.1, 10)
  const matVP = matP.multiply(matV)

  const imgCvs = new ImageCanvas(image, 542, 542)

  const interleave = new ImageInterleavedData(imgCvs, ["vVertexPosition", "vVelocity", "vVertexColor"])

  interleave.useImageColorAs("vVertexColor")

  interleave.add("vVertexPosition", {
    components: 3,
    generate: ({ position }) => {
      const { x, y } = position
      return [x, -y, 0]
    }
  })

  interleave.add("vVelocity", {
    components: 3,
    generate: ({ position }) => {
      const { x, y } = position
      const m = Math.sqrt(x * x + y * y)
      return [x / m, -y / m, 0]
    }
  })

  const renderer = new SwapTFRenderer(gl, interleave.keys)

  renderer.attachUpdateProgram(vertForUpdate, fragForUpdate)
  renderer.attachRenderProgram(vertForRender, fragForRender)

  renderer.registUpdateAttrib("vVertexPosition", { location: 0, components: 3 })
  renderer.registUpdateAttrib("vVelocity", { location: 1, components: 3 })
  renderer.registUpdateAttrib("vVertexColor", { location: 2, components: 4 })

  renderer.registRenderAttrib("aVertexPosition", { location: 0, components: 3 })
  renderer.registRenderAttrib("aVelocity", { location: 1, components: 3 })
  renderer.registRenderAttrib("aVertexColor", { location: 2, components: 4 })

  uniformsFor.update.init(renderer.glProgramForUpdate)
  uniformsFor.render.init(renderer.glProgramForRender)

  // TODO: これもライブラリ側で大枠を提供したい
  const camera = new SpreadTextureInteraction(canvas)

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

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

  gl.disable(gl.DEPTH_TEST)
  gl.disable(gl.CULL_FACE)
  gl.enable(gl.BLEND)
  gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE)
  gl.disable(gl.RASTERIZER_DISCARD)

  return {
    preload: [imgCvs.load()],

    preloaded: [
      () => {
        const interleavedArray = interleave.generate()
        renderer.setup(new Float32Array(interleavedArray))
      }
    ],

    drawOnFrame() {
      camera.update()

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

      renderer.startUpdate()
      uniformsFor.update.float("uTime", timer.elapsed * 0.001)
      uniformsFor.update.float("uMove", camera.movePower)
      uniformsFor.update.fvector2("uMouse", camera.mouseCoords)
      gl.drawArrays(gl.POINTS, 0, imgCvs.width * imgCvs.height)
      renderer.endUpdate()

      renderer.startRender()
      uniformsFor.render.fmatrix4("uMatrix", matVP.values)
      uniformsFor.render.float("uMove", camera.movePower)
      gl.drawArrays(gl.POINTS, 0, imgCvs.width * imgCvs.height)
      renderer.endRender()
    }
  }
}

export const onload = () => {
  const config: SketchConfig = {
    canvas: {
      el: "gl-canvas",
      fit: "square",
      autoResize: true
    }
  }
  SketchGl.init(config, sketch)
}