xainey

Help with beveled pixel effect

Recommended Posts

I played a ludem dare game a few years ago and finally re-found the game http://deepnight.net/games/last-breath/

I loved this beveled pixel effect and have been trying to use it in phaser.

Deepnight referenced this one Reddit post on how to implement the style, but I have not been able to successfully do anything with it yet.

 

I've tried desperately to use the following shader example by Rybar but always seem to fall a little bit short.

I wanted to render the effect on the entire game.world instead of having to add everything manually to a group like Rybar did in his examples.

I tried to use his shader and capture the world, scale it up, and render it to a sprite on top everything. Using this method causes all of the camera, and physics to be off.

I've made a simple example of my closest attempt using Phaser CE and some boilerplate. I've added some debug boxes to show where the original sprites are located in the background.

image.png.e195aa519a82eae08420446d5d09da93.png

<!doctype html>
<html lang="en">
<head>
	<meta charset="UTF-8" />
    <title>Phaser - Making your first game, part 7</title>
	<script type="text/javascript" src="js/phaser.min.js"></script>
    <style type="text/css">
        body {
            margin: 0;
        }
    </style>
</head>
<body>

<script type="text/javascript">

var w = 800
var h = 600;
var player;
var platforms;
var cursors;
var renderTexture;
var renderTexture2;
var stageSprite;
var outputSprite;
var gameMatrix;
var gameGroup;

var game = new Phaser.Game(w, h, Phaser.WEBGL, '', { preload: preload, create: create, update: update, render: render, preRender: preRender });

function preload() {
    game.load.image('sky', 'assets/sky.png');
    game.load.image('ground', 'assets/platform.png');
    game.load.spritesheet('dude', 'assets/dude.png', 32, 48);
}

function create() {
    game.physics.startSystem(Phaser.Physics.ARCADE);
    game.add.sprite(0, 0, 'sky');

    platforms = game.add.group();
    platforms.enableBody = true;

    var ground = platforms.create(0, game.world.height - 64, 'ground');
    ground.scale.setTo(2, 2);
    ground.body.immovable = true;

    var ledge = platforms.create(0, 110, 'ground');
    ledge.body.immovable = true;

    ledge = platforms.create(250, 255, 'ground');
    ledge.body.immovable = true;

    // player = game.make.sprite(32, game.world.height - 150, 'dude');

    // move the player to the top platform
    player = game.make.sprite(0, 0, 'dude');

    game.physics.arcade.enable(player);

    player.body.gravity.y = 300;
    player.body.collideWorldBounds = true;

    player.animations.add('left', [0, 1, 2, 3], 10, true);
    player.animations.add('right', [5, 6, 7, 8], 10, true);

    cursors = game.input.keyboard.createCursorKeys();

    game.camera.follow(player)

    var fragmentSrc = [
      'precision mediump float;',
      'varying vec2 vTextureCoord;',
      'varying vec4 vColor;',
      'uniform sampler2D uSampler;',
      'uniform vec2      resolution;',
      'uniform float     time;',
      'uniform vec2      mouse;',
      'void main( void ) {',
      'vec2 st = vTextureCoord.xy;',
      'st *= resolution;', // this makes the box defined below tiny and repeat across the screen
      'st = fract(st);',
      'vec2 bl = step(vec2(0.25, 0.75) , st);',
      'float pct = bl.x * bl.y;',
      'vec2 tr = step(vec2(0.5, 0.00),1.0-st);',
      'pct *= tr.x * tr.y;',
      'pct *= 0.15;', // knock it down to gray
      'vec3 src = texture2D(uSampler, vTextureCoord).xyz;', // game art
      'vec3 dst = vec3(pct) + vec3(0.4);', // the overlay pattern
      'vec3 color = vec3((dst.x <= 0.5) ? (2.0 * src.x * dst.x) : (1.0 - 2.0 * (1.0 - dst.x) * (1.0 - src.x)),' +
      '(dst.y <= 0.5) ? (2.0 * src.y * dst.y) : (1.0 - 2.0 * (1.0 - dst.y) * (1.0 - src.y)),' +
      '(dst.z <= 0.5) ? (2.0 * src.z * dst.z) : (1.0 - 2.0 * (1.0 - dst.z) * (1.0 - src.z)));',
      'gl_FragColor.rgb = clamp(color, 0.0, 1.0);',
      'gl_FragColor.w = 1.0;',
      '}'
    ]

    gameGroup = game.make.group();
    gameGroup.add(platforms)
    gameGroup.add(player)
    gameGroup.add(ledge)

    scanlineFilter = new Phaser.Filter(game, null, fragmentSrc);
    scanlineFilter.setResolution(w/4,h/4);

    game.world.setBounds(0, 0, 2000, 2000);
    game.stage.smoothed = false;

    renderTexture = game.make.renderTexture(w/4, h/4, 'texture1');
    renderTexture2 = game.make.renderTexture(w, h, 'texture2');

    renderTexture2.renderer.renderSession.roundPixels = true;
    renderTexture2.baseTexture.scaleMode = PIXI.scaleModes.NEAREST;

    stageSprite = game.make.sprite(0,0, renderTexture);
    stageSprite.smoothed = false;

    outputSprite = game.add.sprite(0,0, renderTexture2);
    outputSprite.smoothed = false;

    outputSprite.filters = [scanlineFilter];

    gameMatrix = new Phaser.Matrix(4,0,0,4,0,0);
}

function preRender(){
    // gameGroup.visible = false;
}

function render(){
    // gameGroup.visible = true;
    // renderTexture.render(player, game.stage.worldTransform, true);
    // renderTexture2.render(stageSprite, gameMatrix, true);

    // Debug
    game.debug.spriteBounds(player, 'pink', false)
    game.debug.body(player, 'red', false)

    game.debug.spriteBounds(stageSprite, 'yellow', false)
    game.debug.body(stageSprite, 'orange', false)

    game.debug.spriteBounds(outputSprite, 'cyan', false)
    game.debug.body(outputSprite, 'blue', false)

}

function update() {
    game.physics.arcade.collide(player, platforms);

    // renderTexture.clear()
    // renderTexture2.clear()
    outputSprite.sendToBack()
    renderTexture.render(game.world, null, true);
    renderTexture2.render(stageSprite, gameMatrix, true);
    outputSprite.bringToTop()

    player.body.velocity.x = 0;

    if (cursors.left.isDown) {
        player.body.velocity.x = -150;
        player.animations.play('left');
    } else if (cursors.right.isDown) {
        player.body.velocity.x = 150;
        player.animations.play('right');
    } else {
        player.animations.stop();
        player.frame = 4;
    }

    if (cursors.up.isDown && player.body.touching.down) {
        player.body.velocity.y = -350;
    }
}
</script>

</body>
</html>

I've also tried scaling the game up then set the filter to the world but the filter is applied before it is scaled.

game.scale.scaleMode = Phaser.ScaleManager.USER_SCALE
game.scale.setUserScale(4, 4)
game.renderer.renderSession.roundPixels = true
Phaser.Canvas.setImageRenderingCrisp(game.canvas)


var scanlineFilter = new Phaser.Filter(game, null, fragmentSrc);
scanlineFilter.setResolution(game.width / 4, game.height / 4)
game.world.filters = [scanlineFilter];

Can anyone help me get this working or suggest a better approach?

 

sky.png

dude.png

platform.png

Share this post


Link to post
Share on other sites

I still haven't figured out how to get this working. I found out I need to use updateTransform() to fix the physics bodies after using render. Now I need help getting the camera aligned properly with the game being scaled.

Here is my closest attempt, It's pretty close. Any ideas?

 

Share this post


Link to post
Share on other sites

For anyone interested I was able to get something working.

I've set the camera to follow the player, scaled the size of the camera, and updated the output sprite's coords to the camera's coords each pass.

If this could be optimized I'd love to know. It seems ok and runs at 60FPS.

Share this post


Link to post
Share on other sites

This is looking good! I abandoned this last year to work on my own tiny render-engine very shortly after getting the shader working, hadn't bothered with getting a camera properly scaled. It'd probably be trivial to make a custom debug drawing function that's scaled properly too I'd think.  I'm revisiting Phaser for a larger project now that 3.0 release is pending, very excited to see someone else make use of the little shader I managed to cobble together. :D 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.