How would I allow retro pixelizing without using a Post Process

How would I allow retro pixelizing without using a Post Process.

So I have a post process effect I use ‘Pixelate’ which gives me great results and pretty much what I’m looking for. So there isn’t really a problem finding a solution that works now, example below:

But I thought i’d try to turn it off and see if I can simply just lower the resolution and get the same effect. It’s not perfect, like it’s slightly blurred as if it is trying to sample blend between what it ‘THINKS’ it should look like without overly pixelating it. Which is great in most cases but when you are trying to GENUINELY tell it to ignore that interpolation, I’m not sure how to fix this. examples below:

The mouse cursor is in the screen shot for frame of reference on how blurry it gets in the image it’s on top of.


20-07-28_Capture-1595974183

Now the main reason for this change of strategy is simply because it would clearly be more performant with less resolution and no anti alias as well as calculating an interpolation. Or that’s what it feels like when I run it at a much lower res. But the pay off is a bit of blurryness. Now in softwares like Photoshop or Blender you can do something called ‘Closest/Nearest’ and even in Playcanvas turn the textures to ‘Point’ vs ‘Linear’ to get pixel perfect look. It’s just bizarre that this simple ‘downres’ approach just doesn’t work since it tries to interpolate what pixels are missing versus just don’t even bother, let it pixelate!

Any clues or suggestions? Or am I truly limited to using a post process effect to Simulate a retro look. Seems so odd lol

SIDE QUEST.

Can you do something similar to that logic to local lights? Like make my light falloff on a spot light be more pixelated? Like less samples? The image below demonstrates the stark contrast from pixel words to smooth gradiated local lights.

Thanks ahead of time :smiley:

Also just occurred to me the pixelate code may very well do this thing just via post process. Just curious if there is other solutions that are simpler.

CODE EXAMPLE:

//--------------- POST EFFECT DEFINITION------------------------//
pc.extend(pc, function () {
    // Constructor - Creates an instance of our post effect
    var PixelatePostEffect = function (graphicsDevice, vs, fs) {
        // this is the shader definition for our effect
        this.shader = new pc.Shader(graphicsDevice, {
            attributes: {
                aPosition: pc.SEMANTIC_POSITION
            },
            vshader: [
                "attribute vec2 aPosition;",
                "",
                "varying vec2 vUv0;",
                "",
                "void main(void)",
                "{",
                "    gl_Position = vec4(aPosition, 0.0, 1.0);",
                "    vUv0 = (aPosition.xy + 1.0) * 0.5;",
                "}"
            ].join("\n"),
            fshader: [
                "precision " + graphicsDevice.precision + " float;",
                "",
                "uniform vec2 uResolution;",
                "uniform float uPixelize;",
                "uniform sampler2D uColorBuffer;",
                "",
                "varying vec2 vUv0;",
                "",
                "void main() {",
                "    vec2 uv = gl_FragCoord.xy / uResolution.xy;",
                "    vec2 div = vec2(uResolution.x * uPixelize / uResolution.y, uPixelize);",
                "    uv = floor(uv * div)/div;",
                "    vec4 color = texture2D(uColorBuffer, uv);",
                "    gl_FragColor = color;",
                "}"
            ].join("\n")
        });

        this.resolution = new Float32Array(2);
        this.pixelize = 100;
    };

    // Our effect must derive from pc.PostEffect
    PixelatePostEffect = pc.inherits(PixelatePostEffect, pc.PostEffect);

    PixelatePostEffect.prototype = pc.extend(PixelatePostEffect.prototype, {
        // Every post effect must implement the render method which 
        // sets any parameters that the shader might require and 
        // also renders the effect on the screen
        render: function (inputTarget, outputTarget, rect) {
            var device = this.device;
            var scope = device.scope;

            // Set the input render target to the shader. This is the image rendered from our camera
            scope.resolve("uColorBuffer").setValue(inputTarget.colorBuffer);
            
            this.resolution[0] = device.width;
            this.resolution[1] = device.height;
            scope.resolve("uResolution").setValue(this.resolution);       
            
            scope.resolve("uPixelize").setValue(this.pixelize);
            
            // Draw a full screen quad on the output target. In this case the output target is the screen.
            // Drawing a full screen quad will run the shader that we defined above
            pc.drawFullscreenQuad(device, outputTarget, this.vertexBuffer, this.shader, rect);
        }
    });

    return {
        PixelatePostEffect: PixelatePostEffect
    };
}());


//--------------- SCRIPT DEFINITION------------------------//
var PostEffectPixelate = pc.createScript('PostEffectPixelate');

PostEffectPixelate.attributes.add('pixelize', {
    type: 'number',
    title: 'Pixelize',
    min: 2,
    max: 256,
    step: 1,
    default: 100
});

// initialize code called once per entity
PostEffectPixelate.prototype.initialize = function() {
    var effect = new pc.PixelatePostEffect(this.app.graphicsDevice);
    
    // add the effect to the camera's postEffects queue
    var queue = this.entity.camera.postEffects;
    queue.addEffect(effect);
    
    // when the script is enabled add our effect to the camera's postEffects queue
    this.on('enable', function () {
        queue.addEffect(effect, false); 
    });
    
    // when the script is disabled remove our effect from the camera's postEffects queue
    this.on('disable', function () {
        queue.removeEffect(effect); 
    });
    
    this.on('attr:pixelize', function (value, prev) {
        effect.pixelize = value;
    });
};

I wonder if it’s the canvas being resized that is doing the interpolation :thinking:

It could be this? https://stackoverflow.com/questions/7615009/disable-interpolation-when-scaling-a-canvas

1 Like

And like that the great Yaustar to the rescue!

The solution may very well be in the link you shared! Thanks I’ll come back with what I discover :slight_smile: Or with more questions :slight_smile: stay tuned.

Also found this!

Also good to see you back Robotpencil!

1 Like

VICTORY! Thanks ;D

Here is how you do it homies!
Just make a simple .js and put in this simple code:

document.getElementsByTagName("body")[0].style.imageRendering = "pixelated";

I added a few more to test it out.

Then Set my resolution to 320x180 and some other default settings.
20-07-28_Capture-1595979989

And here is the result :smiley:

2 Likes

If you are getting the canvas, doing pc.app.graphicsDevice.canvas would be safer as the canvas may not always be the first child of the body.

1 Like

I can confirm.

This works too! And perhaps safer as suggested :wink:

pc.app.graphicsDevice.canvas.style.imageRendering = "pixelated";

1 Like

Low resolution without smoothing not enough for retro pixelizing. Border distortion (CRT monitor), some types interlacing, color range reduction… This is only postprocessing.