Lights in pixi.js

Now this is one problematic topic...

As of writing this article PIXI.js version is 4.5.1 and lights are still not a part of official release, but if you really really want to have lights in your pixi project, there is a way.

All thanks to a guy called finscn, I don't know much about him, but he'll be a hero of this article.

Anyway, let's get to the juicy part. You can grab his pixi version from here but you'll probably have to build it yourself, or in case if lights get removed, I'll upload a pixi version which I have built from his repo, tested and wrote this article on so chances are it will most likely work if you download this file. Hopefully I'll update this version as new pixi versions get out, or feel free to remind me once in a while in the comments if necessary.

Before we start, here is what you're after:

To make that, you'll need at least 2 images, a normal one and a normals map one, here are the two from the example, courtesy of Proclive artist Pjero Jericic:

I've pushed this one a bit too far with settings for generating the normals image, pushed sharpness to the max and crazy stuff like that. This can probably look a lot better with more experimenting but I was just glad it worked and moved on.

Time to filter out people who came to copy/paste so here you go:

First, project structure.

image/  
image/bg.jpg  
image/bgNormals.jpg  
image/alien.png  
image/alienNormals.png  
lib/  
lib/pixi.js  
main.html  
main.js  

main.html

<!DOCTYPE html>  
<html>  
    <head>
        <meta charset="utf8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui" />

        <title>Pixi v4.5.0 Lights with finscn and proclive</title>
        <style>
            html, body { margin:0; padding: 0 }
        </style>
    </head>

    <body>
        <canvas id="canvas"></canvas>
        <script src="lib/pixi.min.js"></script>
        <script src="main.js"></script>
        <script>

        </script>
        <br>Number of Lights: <span id="numLights">1</span>
    </body>
</html>

main.js

var DirectionalLight = PIXI.lights.DirectionalLight;  
var PointLight = PIXI.lights.PointLight;


var canvas = document.getElementById("canvas");  
var viewWidth = 512;  
var viewHeight = 512;

var renderer = new PIXI.WebGLRenderer(viewWidth, viewHeight, {  
    view: canvas
});

var stage = new PIXI.Container();  
var lightCount = 1;


var lightHeight = 90;  
var allLights = [];


var dirLight = new DirectionalLight({  
    color: 0xffdd66,
    brightness: 0.25,
    ambientColor: 0x555555,
    ambientBrightness: 0.6,
    position: {
        x: 0,
        y: 0,
        z: lightHeight,
    },
    target: {
        x: 0,
        y: 0,
        z: 0,
    }
});

var mouseLight = new PointLight({  
    color: 0xffffff,
    brightness: 4,
    position: {
        x: viewWidth / 2,
        y: viewHeight / 2,
        z: lightHeight,
    }
});

allLights.push(dirLight);  
allLights.push(mouseLight);


function createClickLight(x, y) {  
    var clickLight = new PointLight({
        color: 0xee3311,
        brightness: 8,
        falloff: [0.3, 6, 60],
        position: {
            x: x,
            y: y,
            z: lightHeight,
        }
    });
    allLights.push(clickLight);
}

PIXI.loader  
    .add('alien_diffuse', 'image/alienpng')
    .add('alien_normal', 'image/alienNormals.png')
    .add('bg_diffuse', 'image/bg.jpg')
    .add('bg_normal', 'image/bgNormals.jpg')
    .load(function(loader, res) {
        var bg = new PIXI.Sprite(res.bg_diffuse.texture);
        stage.addChild(bg);

        var alien = new PIXI.Sprite(res.alien_diffuse.texture);

        alien.position.set(0, 0);
        alien.scale.set(0.5, 0.5);

        dirLight.target.x = alien.x;
        dirLight.target.y = alien.y;
        dirLight.updateDirection();

        bg.normalTexture = res.bg_normal.texture;
        alien.normalTexture = res.alien_normal.texture;

        bg.pluginName = "lightSprite";
        alien.pluginName = "lightSprite";

        bg.lights = allLights;
        alien.lights = allLights;

        stage.addChild(alien);

        canvas.addEventListener('mousemove', function(e) {
            var rect = e.target.getBoundingClientRect();

            mouseLight.position.x = e.clientX - rect.left;
            mouseLight.position.y = e.clientY - rect.top;
        });

        canvas.addEventListener('click', function(e) {
            var rect = e.target.getBoundingClientRect();

            createClickLight(e.clientX - rect.left, e.clientY - rect.top);

            document.getElementById('numLights').textContent = ++lightCount;
        });

        animate();
    });

function animate() {  
    requestAnimationFrame(animate);
    renderer.render(stage);
}

People who kept reading and didn't copy/paste directly into their working project parts of code which they thought they need will know that this only works with WebGLRenderer, apart from that, all you need to do when adding a new sprite is:

  • add a new sprite created with diffuse image
  • set ".normalTexture" to normal map (name from loader.add first param)
  • set ".pluginName" to "lightSprite"
  • set ".lights" to array containing all lights that should affect that sprite

Simple enough, now let's hope finscn PR gets merged into official pixi.js repo and get released soon so I can rewrite this tutorial.

That's all folks, now I can't wait to see some lights flickering on some game character using minigun