Skip to content

Commit

Permalink
Update ASCII Filter
Browse files Browse the repository at this point in the history
  • Loading branch information
bbazukun123 committed Jan 1, 2024
1 parent 2374fe9 commit dc60c38
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 31 deletions.
102 changes: 83 additions & 19 deletions filters/ascii/src/AsciiFilter.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
import { vertex } from '@tools/fragments';
import { vertex, wgslVertex } from '@tools/fragments';
import fragment from './ascii.frag';
import { Filter, GlProgram } from 'pixi.js';
import source from './ascii.wgsl';
import { Color, ColorSource, Filter, GlProgram, GpuProgram } from 'pixi.js';

// TODO (cengler) - The Y is flipped in this shader for some reason.
// This WebGPU filter has been ported from the WebGL renderer that was originally created by Vico (@vicocotea)

// @author Vico @vicocotea
// original shader : https://www.shadertoy.com/view/lssGDj by @movAX13h
export interface AsciiFilterOptions
{
/**
* The pixel size used by the filter
* @default 8
*/
size?: number;
/**
* A color to set the ascii characters to. If not set, the color will be taken from the source.
* @example [1.0, 1.0, 1.0] = 0xffffff
* @default 0x000000
*/
color?: ColorSource;
/**
* Determine whether or not to replace the source colors with the provided.
*
* Will automatically be assigned to `true` if `color` is provided.
* Set `replaceColor` to `false` to prevent that.
* @default false
*/
replaceColor?: boolean;
}

/**
* An ASCII filter.<br>
Expand All @@ -18,33 +39,76 @@ import { Filter, GlProgram } from 'pixi.js';
*/
export class AsciiFilter extends Filter
{
/**
* @param {number} [size=8] - Size of the font
*/
constructor(size = 8)
/** Default values for options. */
public static readonly DEFAULT_OPTIONS: AsciiFilterOptions = {
size: 8,
color: 0xffffff,
replaceColor: false,
};

private _color: Color;

constructor(options?: AsciiFilterOptions)
{
const replaceColor = options?.color && options.replaceColor !== false;

options = { ...AsciiFilter.DEFAULT_OPTIONS, ...options } as AsciiFilterOptions;

const gpuProgram = new GpuProgram({
vertex: {
source: wgslVertex,
entryPoint: 'mainVertex',
},
fragment: {
source,
entryPoint: 'mainFragment',
},
});

const glProgram = new GlProgram({
vertex,
fragment,
name: 'ascii-filter',
});

super({
gpuProgram,
glProgram,
resources: {},
resources: {
asciiUniforms: {
uSize: { value: options.size, type: 'f32' },
uColor: { value: new Float32Array(3), type: 'vec3<f32>' },
uReplaceColor: { value: Number(replaceColor), type: 'f32' },
}
},
});
// this.size = size;

this._color = new Color();
this.color = options.color ?? 0xffffff;
}

/**
* The pixel size used by the filter.
* @default 8
*/
get size(): number { return this.resources.asciiUniforms.uniforms.uSize; }
set size(value: number) { this.resources.asciiUniforms.uniforms.uSize = value; }

/**
* The resulting color of the ascii characters, as a 3 component RGB or numerical hex
* @example [1.0, 1.0, 1.0] = 0xffffff
* @default 0xffffff
*/
get color(): ColorSource { return this._color.value as ColorSource; }
set color(value: ColorSource)
{
this._color.setValue(value);
this.resources.asciiUniforms.uniforms.uColor = this._color.toArray().slice(0, 3);
}

/**
* Determine whether or not to replace the source colors with the provided.
*/
// get size(): number
// {
// return this.uniforms.pixelSize;
// }
// set size(value: number)
// {
// this.uniforms.pixelSize = value;
// }
get replaceColor(): boolean { return this.resources.asciiUniforms.uniforms.uReplaceColor > 0.5; }
set replaceColor(value: boolean) { this.resources.asciiUniforms.uniforms.uReplaceColor = value ? 1 : 0; }
}
28 changes: 16 additions & 12 deletions filters/ascii/src/ascii.frag
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
varying vec2 vTextureCoord;
precision highp float;
in vec2 vTextureCoord;
out vec4 finalColor;

uniform vec4 filterArea;
uniform float pixelSize;
uniform sampler2D uSampler;
uniform float uSize;
uniform vec3 uColor;
uniform float uReplaceColor;

uniform vec4 uInputSize;

vec2 mapCoord( vec2 coord )
{
coord *= filterArea.xy;
coord += filterArea.zw;
coord *= uInputSize.xy;
coord += uInputSize.zw;

return coord;
}

vec2 unmapCoord( vec2 coord )
{
coord -= filterArea.zw;
coord /= filterArea.xy;
coord -= uInputSize.zw;
coord /= uInputSize.xy;

return coord;
}
Expand Down Expand Up @@ -49,11 +54,11 @@ void main()
vec2 coord = mapCoord(vTextureCoord);

// get the grid position
vec2 pixCoord = pixelate(coord, vec2(pixelSize));
vec2 pixCoord = pixelate(coord, vec2(uSize));
pixCoord = unmapCoord(pixCoord);

// sample the color at grid position
vec4 color = texture2D(uSampler, pixCoord);
vec4 color = texture(uSampler, pixCoord);

// brightness of the color as it's perceived by the human eye
float gray = 0.3 * color.r + 0.59 * color.g + 0.11 * color.b;
Expand All @@ -69,8 +74,7 @@ void main()
if (gray > 0.8) n = 11512810.0; // #

// get the mod..
vec2 modd = getMod(coord, vec2(pixelSize));

gl_FragColor = color * character( n, vec2(-1.0) + modd * 2.0);
vec2 modd = getMod(coord, vec2(uSize));

finalColor = (uReplaceColor > 0.5 ? vec4(uColor, 1.) : color) * character( n, vec2(-1.0) + modd * 2.0);
}
108 changes: 108 additions & 0 deletions filters/ascii/src/ascii.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
struct AsciiUniforms {
uSize: f32,
uColor: vec3<f32>,
uReplaceColor: f32,
};

struct GlobalFilterUniforms {
uInputSize:vec4<f32>,
uInputPixel:vec4<f32>,
uuInputClamp:vec4<f32>,
uOutputFrame:vec4<f32>,
uGlobalFrame:vec4<f32>,
uOutputTexture:vec4<f32>,
};

@group(0) @binding(0) var<uniform> gfu: GlobalFilterUniforms;

@group(0) @binding(1) var uSampler: texture_2d<f32>;
@group(1) @binding(0) var<uniform> asciiUniforms : AsciiUniforms;

@fragment
fn mainFragment(
@location(0) uv: vec2<f32>,
@builtin(position) position: vec4<f32>
) -> @location(0) vec4<f32> {
let pixelSize: f32 = asciiUniforms.uSize;
let coord: vec2<f32> = mapCoord(uv);

// get the rounded color..
var pixCoord: vec2<f32> = pixelate(coord, vec2<f32>(pixelSize));
pixCoord = unmapCoord(pixCoord);

var color = textureSample(uSampler, uSampler, pixCoord);

// determine the character to use
let gray: f32 = 0.3 * color.r + 0.59 * color.g + 0.11 * color.b;

var n: f32 = 65536.0; // .
if (gray > 0.2) {
n = 65600.0; // :
}
if (gray > 0.3) {
n = 332772.0; // *
}
if (gray > 0.4) {
n = 15255086.0; // o
}
if (gray > 0.5) {
n = 23385164.0; // &
}
if (gray > 0.6) {
n = 15252014.0; // 8
}
if (gray > 0.7) {
n = 13199452.0; // @
}
if (gray > 0.8) {
n = 11512810.0; // #
}

// get the mod..
let modd: vec2<f32> = getMod(coord, vec2<f32>(pixelSize));
return select(color, vec4<f32>(asciiUniforms.uColor, 1.), asciiUniforms.uReplaceColor > 0.5) * character(n, vec2<f32>(-1.0) + modd * 2.0);
}

fn pixelate(coord: vec2<f32>, size: vec2<f32>) -> vec2<f32>
{
return floor( coord / size ) * size;
}

fn getMod(coord: vec2<f32>, size: vec2<f32>) -> vec2<f32>
{
return moduloVec2( coord , size) / size;
}

fn character(n: f32, p: vec2<f32>) -> f32
{
var q: vec2<f32> = floor(p*vec2<f32>(4.0, 4.0) + 2.5);

if (clamp(q.x, 0.0, 4.0) == q.x)
{
if (clamp(q.y, 0.0, 4.0) == q.y)
{
if (i32(modulo(n/exp2(q.x + 5.0*q.y), 2.0)) == 1)
{
return 1.0;
}
}
}

return 0.0;
}

fn mapCoord(coord: vec2<f32> ) -> vec2<f32>
{
var mappedCoord: vec2<f32> = coord;
mappedCoord *= gfu.inputSize.xy;
mappedCoord += gfu.outputFrame.xy;
return mappedCoord;
}

fn unmapCoord(coord: vec2<f32> ) -> vec2<f32>
{
var mappedCoord: vec2<f32> = coord;
mappedCoord -= gfu.outputFrame.xy;
mappedCoord /= gfu.inputSize.xy;
return mappedCoord;
}
2 changes: 2 additions & 0 deletions tools/demo/src/filters/ascii.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ export default function ()
this.addFilter('AsciiFilter', function (folder)
{
folder.add(this, 'size', 2, 20);
folder.addColor(this, 'color');
folder.add(this, 'replaceColor');
});
}
1 change: 1 addition & 0 deletions tools/demo/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const main = async () =>
filters.glow.call(app);
filters.hslAdjustment.call(app);
filters.rgb.call(app);
filters.ascii.call(app);
// filters.kawaseBlur.call(app);

// TODO: Re-enable this in place of the above once v8 conversion is complete
Expand Down

0 comments on commit dc60c38

Please sign in to comment.