index.frag
#version 300 es
precision highp float;
float clamp_range(float v, float minV, float maxV) {
return v * (maxV - minV) + minV;
}
const uint UINT_MAX = 0xffffffffu;
uint k1 = 0x456789abu;
uint uhash11(uint n) {
n ^= (n << 1);
n ^= (n >> 1);
n *= k1;
n ^= (n << 1);
return n * k1;
}
float hash11(float b) {
uint n = floatBitsToUint(b);
return float(uhash11(n)) / float(UINT_MAX);
}
uniform sampler2D uOriginal;
uniform float uAlpha;
uniform float uMixingRatio;
uniform float uSiteCount;
in vec2 vTextureCoords;
out vec4 fragColor;
void main() {
ivec2 iTextureSize = textureSize(uOriginal, 0);
vec2 textureSize = vec2(float(iTextureSize.x), float(iTextureSize.y));
vec2 texelSize = 1.0 / textureSize;
vec2 uv = vec2(vTextureCoords.x, 1.0 - vTextureCoords.y);
vec3 original = texture(uOriginal, uv).rgb;
vec2 pos = vec2(0.0);
pos.x = clamp_range(hash11(uv.x), 0.0, texelSize.x);
pos.y = clamp_range(hash11(uv.y), 0.0, texelSize.y);
pos = fract(pos * uSiteCount);
vec3 random = texture(uOriginal, uv + pos).rgb;
vec3 outColor = mix(original, random, uMixingRatio);
fragColor = vec4(outColor, uAlpha);
}
render.ts
import { SketchFilter, type FilterSketchConfig, type FilterSketchFn } from "sketchgl"
import { ImageTexture } from "sketchgl/texture"
import { Program, Uniforms } from "sketchgl/program"
import { CanvasCoverPolygon } from "sketchgl/geometry"
import mainVertSrc from "./index.vert?raw"
import mainFragSrc from "./index.frag"
import imageGeometry from "@/assets/original/pastel-tomixy.png"
import imageAutumnLeaves from "@/assets/original/autumn-leaves_00037.jpg"
import imageWater from "@/assets/original/japanese-style_00011.jpg"
import imageGoldFish from "@/assets/original/fantasy-unicorn.jpg"
const sketch: FilterSketchFn = ({ gl, fitImage }) => {
const uniforms = new Uniforms(gl, ["uAlpha", "uSiteCount", "uMixingRatio"])
let uAlpha = 1.0
let uMixingRatio = 1.0
let uSiteCount = 12
const images = [
{ name: "ユニコーン", src: imageGoldFish },
{ name: "立方体", src: imageGeometry },
{ name: "紅葉", src: imageAutumnLeaves },
{ name: "金魚", src: imageWater }
]
const imageNames = images.map((obj) => obj.name)
const textures = images.map((img) => new ImageTexture(gl, img.src))
let activeImage = 2
const program = new Program(gl)
program.attach(mainVertSrc, mainFragSrc)
program.activate()
uniforms.init(program.glProgram)
const plane = new CanvasCoverPolygon(gl)
plane.setLocations({ vertices: 0, uv: 1 })
gl.clearColor(1.0, 0.0, 0.0, 1.0)
gl.clearDepth(1.0)
return {
preload: [...textures.map((tex) => tex.load())],
preloaded: [() => fitImage(textures[activeImage].img)],
drawOnFrame() {
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
plane.bind()
textures[activeImage].activate(program.glProgram!, "uOriginal")
uniforms.float("uAlpha", uAlpha)
uniforms.float("uMixingRatio", uMixingRatio)
uniforms.float("uSiteCount", uSiteCount)
plane.draw({ primitive: "TRIANGLES" })
},
control(ui) {
ui.select("Image", images[activeImage].name, imageNames, (name) => {
const idx = imageNames.indexOf(name)
if (idx < 0) return
activeImage = idx
fitImage(textures[activeImage].img)
})
ui.number("全体の透明度", uAlpha, 0.0, 1.0, 0.01, (v) => {
uAlpha = v
})
ui.number("ノイズの透明度", uMixingRatio, 0.0, 1.0, 0.01, (v) => {
uMixingRatio = v
})
ui.number("広がり", uSiteCount, 3, 30, 1, (v) => {
uSiteCount = v
})
}
}
}
export const onload = () => {
const config: FilterSketchConfig = {
canvas: {
el: "gl-canvas",
autoResize: true
}
}
SketchFilter.init(config, sketch)
}