pixijs / filters

Collection of community-authored custom display filters for PixiJS
https://pixijs.io/filters/docs/
MIT License
967 stars 158 forks source link

`MotionBlurFilter` filter does not work using `webgl` as renderer. #461

Closed cvpcasada closed 5 months ago

cvpcasada commented 6 months ago

I haven't tested with other filters but MotionBlurFilter filter currently only works with webgpu renderer and not webgl

Reproduction:

https://stackblitz.com/edit/vitejs-vite-bu1fxt?file=src%2FApp.tsx

Environment

ANovokmet commented 5 months ago

This morning I was trying to make this filter work (I am using the webgl renderer). This is a version of the filter that works for webgl:


import { Filter, GlProgram, PointData } from 'pixi.js';
import { vertex, wgslVertex } from "pixi-filters";

const fragment = `
precision highp float;
in vec2 vTextureCoord;
out vec4 finalColor;

uniform sampler2D uTexture;
uniform vec2 uVelocity;
uniform int uKernelSize;
uniform float uOffset;

uniform vec4 uInputSize;

const int MAX_KERNEL_SIZE = 2048;

// Notice:
// the perfect way:
//    int kernelSize = min(uKernelSize, MAX_KERNELSIZE);
// BUT in real use-case , uKernelSize < MAX_KERNELSIZE almost always.
// So use uKernelSize directly.

void main(void)
{
    vec4 color = texture(uTexture, vTextureCoord);

    if (uKernelSize == 0)
    {
        finalColor = color;
        return;
    }

    vec2 velocity = uVelocity / uInputSize.xy;
    float offset = -uOffset / length(uVelocity) - 0.5;
    int k = uKernelSize - 1;

    for(int i = 0; i < MAX_KERNEL_SIZE - 1; i++) {
        if (i == k) {
            break;
        }
        vec2 bias = velocity * (float(i) / float(k) + offset);
        color += texture(uTexture, vTextureCoord + bias);
    }

    finalColor = color / float(uKernelSize);
}
`;

/** Options for the MotionBlurFilter constructor. */
export interface MotionBlurFilterOptions {
    velocity?: number[];
    kernelSize?: number;
    offset?: number;
}

export class MotionBlurFilter extends Filter {
    public static readonly DEFAULT_OPTIONS: MotionBlurFilterOptions = {
        velocity: [0, 0],
        kernelSize: 5,
        offset: 0,
    };

    public uniforms: {
        uVelocity: [number, number];
        uKernelSize: number;
        uOffset: number;
    };

    private _kernelSize: number;

    constructor(options?: MotionBlurFilterOptions) {
        options = options ?? {};

        options = { ...MotionBlurFilter.DEFAULT_OPTIONS, ...options };

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

        const glProgram = GlProgram.from({
            vertex,
            fragment,
            name: 'motion-blur-filter',
        });

        super({
            // gpuProgram,
            glProgram,
            resources: {
                motionBlurUniforms: {
                    uVelocity: { value: options.velocity, type: 'vec2<f32>' },
                    uKernelSize: { value: options.kernelSize, type: 'i32' },
                    uOffset: { value: options.offset, type: 'f32' },
                }
            },
        });

        this.uniforms = this.resources.motionBlurUniforms.uniforms;

        Object.assign(this, options);
    }

    /**
     * Sets the velocity of the motion for blur effect
     * This should be a size 2 array or an object containing `x` and `y` values, you cannot change types
     * once defined in the constructor
     * @default {x:0,y:0}
     */
    get velocity(): [number, number] { return this.uniforms.uVelocity; }
    set velocity(value: [number, number]) {
        this.uniforms.uVelocity = value;
        this._updateDirty();
    }

    get velocityX(): number { return this.velocity[0]; }
    get velocityY(): number { return this.velocity[1]; }

    /**
     * The kernelSize of the blur filter. Must be odd number >= 5
     * @default 5
     */
    get kernelSize(): number { return this._kernelSize; }
    set kernelSize(value: number) {
        this._kernelSize = value;
        this._updateDirty();
    }

    /**
     * The offset of the blur filter
     * @default 0
     */
    get offset(): number { return this.uniforms.uOffset; }
    set offset(value: number) { this.uniforms.uOffset = value; }

    private _updateDirty() {
        // The padding will be increased as the velocity and intern the blur size is changed
        this.padding = (Math.max(Math.abs(this.velocityX), Math.abs(this.velocityY)) >> 0) + 1;
        this.uniforms.uKernelSize = (this.velocityX !== 0 || this.velocityY !== 0) ? this._kernelSize : 0;
    }
}

I changed the motionBlurUniforms.uKernelSize.type to i32, as it is an int in the shader, and updated the class so only [number, number] are allowed for uVelocity. Please note that I am very new at pixi.js and I did this by trial and error.