From ba9d4740ed61edcf83542486d621ec275d7f9543 Mon Sep 17 00:00:00 2001 From: FMS-Cat Date: Sun, 14 Mar 2021 11:35:05 +0900 Subject: [PATCH] feature: add PixelSorter --- src/automaton.json | 2 +- src/entities/PixelSorter.ts | 125 ++++++++++++++++++++++++++++ src/main.ts | 11 ++- src/shaders/pixel-sorter-index.frag | 37 ++++++++ src/shaders/pixel-sorter.frag | 58 +++++++++++++ 5 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 src/entities/PixelSorter.ts create mode 100644 src/shaders/pixel-sorter-index.frag create mode 100644 src/shaders/pixel-sorter.frag diff --git a/src/automaton.json b/src/automaton.json index 615c9ef..40ee9e3 100644 --- a/src/automaton.json +++ b/src/automaton.json @@ -1 +1 @@ -{"version":"4.1.1","resolution":100,"curves":[{"nodes":[[0,1,0,0,0.16140350877192983],[1,0,-0.47719298245614045]]}],"channels":[["Glitch/amp",{"items":[{"length":1,"curve":0}]}]],"labels":{"zero":0},"guiSettings":{"snapTimeActive":true,"snapTimeInterval":0.1,"snapValueActive":true,"snapValueInterval":0.1,"snapBeatActive":false,"bpm":140,"beatOffset":0,"useBeatInGUI":false,"minimizedPrecisionTime":3,"minimizedPrecisionValue":3}} \ No newline at end of file +{"version":"4.1.1","resolution":100,"curves":[{"nodes":[[0,1,0,0,0.16140350877192983],[1,0,-0.47719298245614045]]}],"channels":[["Glitch/amp",{"items":[{"length":1,"curve":0}]}],["PixelSorter/amp",{"items":[{},{"time":1,"length":1,"curve":0}]}]],"labels":{"zero":0},"guiSettings":{"snapTimeActive":true,"snapTimeInterval":0.1,"snapValueActive":true,"snapValueInterval":0.1,"snapBeatActive":false,"bpm":140,"beatOffset":0,"useBeatInGUI":false,"minimizedPrecisionTime":3,"minimizedPrecisionValue":3}} \ No newline at end of file diff --git a/src/entities/PixelSorter.ts b/src/entities/PixelSorter.ts new file mode 100644 index 0000000..3b41f6f --- /dev/null +++ b/src/entities/PixelSorter.ts @@ -0,0 +1,125 @@ +import { Entity } from '../heck/Entity'; +import { GLCatTexture } from '@fms-cat/glcat-ts'; +import { Material } from '../heck/Material'; +import { Quad } from '../heck/components/Quad'; +import { RenderTarget } from '../heck/RenderTarget'; +import pixelSorterIndexFrag from '../shaders/pixel-sorter-index.frag'; +import pixelSorterFrag from '../shaders/pixel-sorter.frag'; +import quadVert from '../shaders/quad.vert'; +import { BufferRenderTarget } from '../heck/BufferRenderTarget'; +import { Swap } from '@fms-cat/experimental'; +import { DISPLAY } from '../heck/DISPLAY'; +import { Automaton } from '@fms-cat/automaton'; + +export interface PixelSorterOptions { + input: GLCatTexture; + target: RenderTarget; + automaton: Automaton; +} + +export class PixelSorter { + public entity: Entity; + public swapBuffer: Swap; + + public constructor( options: PixelSorterOptions ) { + const { gl } = DISPLAY; + + this.entity = new Entity(); + this.entity.visible = false; + + this.swapBuffer = new Swap( + new BufferRenderTarget( { + width: options.target.width, + height: options.target.height, + numBuffers: 2, + name: process.env.DEV && 'PixelSorter/swap0', + } ), + new BufferRenderTarget( { + width: options.target.width, + height: options.target.height, + numBuffers: 2, + name: process.env.DEV && 'PixelSorter/swap1', + } ), + ); + + // -- calc index ------------------------------------------------------------------------------- + let mul = 1; + const indexMaterials: Material[] = []; + + while ( mul < options.target.width ) { + const isFirst = mul === 1; + + const material = new Material( + quadVert, + pixelSorterIndexFrag, + ); + material.addUniform( 'mul', '1f', mul ); + material.addUniformTexture( + 'sampler0', + isFirst ? options.input : this.swapBuffer.o.getTexture( gl.COLOR_ATTACHMENT0 ), + ); + material.addUniformTexture( + 'sampler1', + this.swapBuffer.o.getTexture( gl.COLOR_ATTACHMENT1 ), + ); + indexMaterials.push( material ); + + this.entity.components.push( new Quad( { + target: this.swapBuffer.i, + material, + name: process.env.DEV && `PixelSorter/quadIndex-${ mul }`, + } ) ); + + this.swapBuffer.swap(); + + mul *= 8; + } + + // -- sort ------------------------------------------------------------------------------------- + let dir = 1.0 / 64.0; + let comp = 1.0 / 64.0; + + while ( dir < 1.0 ) { + const isLast = ( dir === 0.5 ) && ( comp === 1.0 / 64.0 ); + + const material = new Material( + quadVert, + pixelSorterFrag, + ); + material.addUniform( 'dir', '1f', dir ); + material.addUniform( 'comp', '1f', comp ); + material.addUniformTexture( + 'sampler0', + this.swapBuffer.o.getTexture( gl.COLOR_ATTACHMENT0 ), + ); + material.addUniformTexture( + 'sampler1', + this.swapBuffer.o.getTexture( gl.COLOR_ATTACHMENT1 ), + ); + + this.entity.components.push( new Quad( { + target: isLast ? options.target : this.swapBuffer.i, + material, + name: process.env.DEV && `PixelSorter/quad-${ dir }-${ comp }`, + } ) ); + + this.swapBuffer.swap(); + + if ( comp === 1.0 / 64.0 ) { + dir *= 2.0; + comp = dir; + } else { + comp /= 2.0; + } + } + + // -- update uniform --------------------------------------------------------------------------- + options.automaton.auto( 'PixelSorter/amp', ( { value } ) => { + indexMaterials.map( ( material ) => { + material.addUniform( 'threshold', '1f', value ); + } ); + + this.entity.active = 0.0 < value; + } ); + } +} diff --git a/src/main.ts b/src/main.ts index 7ec6185..2eb5e25 100644 --- a/src/main.ts +++ b/src/main.ts @@ -23,6 +23,7 @@ import { Rings } from './entities/Rings'; import { RTInspector } from './entities/RTInspector'; import { Component } from './heck/components/Component'; import { FlickyParticles } from './entities/FlickyParticles'; +import { PixelSorter } from './entities/PixelSorter'; // == gl =========================================================================================== const { canvas, glCat } = DISPLAY; @@ -166,7 +167,7 @@ if ( process.env.DEV ) { ); } -// == random texture =============================================================================== +// == textures ===================================================================================== const randomTexture = new RandomTexture( glCat, RANDOM_RESOLUTION[ 0 ], @@ -316,6 +317,14 @@ const glitch = new Glitch( { } ); dog.root.children.push( glitch.entity ); +swap.swap(); +const pixelSorter = new PixelSorter( { + input: swap.i.texture, + target: swap.o, + automaton, +} ); +dog.root.children.push( pixelSorter.entity ); + swap.swap(); const post = new Post( { input: swap.i.texture, diff --git a/src/shaders/pixel-sorter-index.frag b/src/shaders/pixel-sorter-index.frag new file mode 100644 index 0000000..49aab74 --- /dev/null +++ b/src/shaders/pixel-sorter-index.frag @@ -0,0 +1,37 @@ +#version 300 es + +precision highp float; + +const vec3 RGB = vec3( 0.299, 0.587, 0.114 ); + +in vec2 vUv; + +layout (location = 0) out vec4 fragColor; +layout (location = 1) out vec4 fragClamp; + +uniform float threshold; +uniform float mul; +uniform vec2 resolution; +uniform sampler2D sampler0; +uniform sampler2D sampler1; + +vec2 getValue( vec2 uv ) { + return ( ( uv.x < 0.0 ) || ( 1.0 < uv.x ) ) + ? vec2( 0.0 ) + : ( mul == 1.0 ) + ? vec2( 1E9 * step( dot( texture( sampler0, uv ).xyz, RGB ), threshold ) ) + : texture( sampler1, uv ).xy; +} + +void main() { + vec2 uv = vUv; + + fragColor = vec4( texture( sampler0, uv ).xyz, 1.0 ); + fragClamp = vec4( getValue( uv ), 0.0, 1.0 ); + + for ( int i = 1; i < 8; i ++ ) { + vec2 uvc = uv - vec2( i, 0 ) / resolution * mul; + vec2 texc = getValue( uvc ); + fragClamp.xy = min( fragClamp.xy, texc + mul * float( i ) ); + } +} diff --git a/src/shaders/pixel-sorter.frag b/src/shaders/pixel-sorter.frag new file mode 100644 index 0000000..8015014 --- /dev/null +++ b/src/shaders/pixel-sorter.frag @@ -0,0 +1,58 @@ +#version 300 es + +precision highp float; + +#define lofi(i,m) (floor((i)/(m))*(m)) + +const vec3 RGB = vec3( 0.299, 0.587, 0.114 ); + +in vec2 vUv; + +layout (location = 0) out vec4 fragColor; +layout (location = 1) out vec4 fragClamp; + +uniform float dir; +uniform float comp; +uniform vec2 resolution; +uniform sampler2D sampler0; +uniform sampler2D sampler1; + +float positiveOrHuge( float i ) { + return 0.0 < i ? i : 1E9; +} + +void main() { + vec2 uv = vUv; + + fragColor = texture( sampler0, uv ); + fragClamp = texture( sampler1, uv ); + + if ( fragClamp.x < 0.5 ) { + return; + } + + float index = fragClamp.x - 1.0; + float width = fragClamp.x + fragClamp.y - 1.0; + + bool isCompRight = mod( index, 2.0 * comp * width ) < comp * width; + float offset = floor( ( isCompRight ? comp : -comp ) * width + 0.5 ); + + vec2 uvc = uv; + uvc.x += offset / resolution.x; + vec4 cColor = texture( sampler0, uvc ); + vec4 cClamp = texture( sampler1, uvc ); + + if ( uvc.x < 0.0 || 1.0 < uvc.x ) { + return; + } + + float vp = dot( fragColor.xyz, RGB ); + float vc = dot( cColor.xyz, RGB ); + + bool shouldSwap = mod( index / ( 2.0 * dir * width ), 2.0 ) < 1.0; + shouldSwap = shouldSwap ^^ isCompRight; + shouldSwap = shouldSwap ^^ ( vc < vp ); + if ( shouldSwap ) { + fragColor = cColor; + } +}