Skip to content

Commit

Permalink
Added seperate overlay layer
Browse files Browse the repository at this point in the history
  • Loading branch information
TarVK committed Aug 6, 2018
1 parent 6c47bf5 commit 0c60fb6
Show file tree
Hide file tree
Showing 20 changed files with 226 additions and 77 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"homepage": "https://github.com/TarVK/pixi-shadows",
"dependencies": {
"dat.gui": "^0.7.2",
"pixi-layers": "^0.1.9",
"pixi-lights": "^2.0.1",
"pixi.js": "^4.8.1",
Expand Down
Empty file removed src/demo.js
Empty file.
File renamed without changes
Binary file added src/demos/advanced/assets/flameDemon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/demos/advanced/assets/flameDemon2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/demos/advanced/assets/flameDemonShadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
98 changes: 98 additions & 0 deletions src/demos/advanced/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Import everything, can of course just use <script> tags on your page as well.
import 'pixi.js';
import 'pixi-layers';
import '../../shadows'; // This plugin, I use a relative path, but you would use 'pixi-shadows' from npm

/* The actual demo code: */

// Create your application
var width = 880;
var height = 600;
var app = new PIXI.Application(width, height);
document.body.appendChild(app.view);

// Initialise the shadows plugin
PIXI.shadows.init(app);

// Make sure to overwrite the stage, otherwise pixi-layers won't work
var stage = app.stage = new PIXI.display.Stage();

// Create a container for all objects
var world = new PIXI.Container();
stage.addChild(world);

// Set up the shadow layer
stage.addChild(PIXI.shadows.shadowCasterLayer);

// Add the shadow filter to the diffuse layer
world.filters = [PIXI.shadows.shadowFilter];
PIXI.shadows.shadowFilter.useShadowCasterAsOverlay = false // Allows us to customise the overlays

// A function to combine different assets if your world object, but give them a common transform by using pixi-layers
// It is of course recommended to create a custom class for this, but this demo just shows the minimal steps required
function createShadowSprite(texture, shadowTexture, shadowOverlayTexture) {
var container = new PIXI.Container(); // This represents your final 'sprite'

// Things that create shadows
var shadowCastingSprite = new PIXI.Sprite(shadowTexture);
shadowCastingSprite.parentGroup = PIXI.shadows.shadowCasterGroup;
container.addChild(shadowCastingSprite);

// Things that are ontop of shadows
var shadowOverlaySprite = new PIXI.Sprite(shadowOverlayTexture);
shadowOverlaySprite.parentGroup = PIXI.shadows.shadowOverlayGroup;
container.addChild(shadowOverlaySprite);

// The things themselves (their texture)
var sprite = new PIXI.Sprite(texture);
container.addChild(sprite);

return container;
}

// Can set ambientLight for the shadow filter, making the shadow less dark:
// PIXI.shadows.shadowFilter.ambientLight = 0.4;

// Create a light that casts shadows
var shadow = new PIXI.shadows.Shadow(700, 0.7);
shadow.position.set(300, 300);
world.addChild(shadow);

// Create a background (that doesn't cast shadows)
var bgTexture = PIXI.Texture.fromImage('/demos/advanced/assets/background.jpg');
var background = new PIXI.Sprite(bgTexture);
world.addChild(background);

// Create some shadow casting demons
var demonTexture = PIXI.Texture.fromImage('/demos/advanced/assets/flameDemon.png');
var demonTextureShadow = PIXI.Texture.fromImage('/demos/advanced/assets/flameDemonShadow.png');
var demonTexture2 = PIXI.Texture.fromImage('/demos/advanced/assets/flameDemon2.png');
demonTexture.baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST; //For pixelated scaling

var demon1 = createShadowSprite(demonTexture2, demonTexture2, demonTexture2);
demon1.position.set(100, 200);
demon1.scale.set(3);
world.addChild(demon1);

var demon2 = createShadowSprite(demonTexture, demonTextureShadow, demonTexture);
demon2.position.set(500, 200);
demon2.scale.set(3);
world.addChild(demon2);

var demon3 = createShadowSprite(demonTexture2, demonTexture2, demonTexture2);
demon3.position.set(300, 300);
demon3.scale.set(3);
world.addChild(demon3);

// Make the light track your mouse
world.interactive = true;
world.on('mousemove', function(event){
shadow.position.copy(event.data.global);
});

// Create a light point on click
world.on('pointerdown', function(event){
var shadow = new PIXI.shadows.Shadow(700, 0.7);
shadow.position.copy(event.data.global);
world.addChild(shadow);
});
Binary file added src/demos/pixi-lights/assets/background.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/demos/pixi-lights/assets/block.png
Binary file not shown.
Binary file removed src/demos/pixi-lights/assets/blockNormalMap.png
Binary file not shown.
7 changes: 3 additions & 4 deletions src/demos/pixi-lights/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ stage.addChild(world);
// Set up the shadow layer
stage.addChild(PIXI.shadows.objectLayer);

// Set up pixi layers
// Set up pixi light's layers
var diffuseLayer = new PIXI.display.Layer(PIXI.lights.diffuseGroup);
var diffuseBlackSprite = new PIXI.Sprite(diffuseLayer.getRenderTexture());
diffuseBlackSprite.tint = 0;
Expand Down Expand Up @@ -87,8 +87,8 @@ light.position.set(300, 300);
world.addChild(light);

// Create a background (that doesn't cast shadows)
var bgDiffuseTexture = PIXI.Texture.fromImage('/demos/pixi-lights/assets/BGTextureTest.jpg');
var bgNormalTexture = PIXI.Texture.fromImage('/demos/pixi-lights/assets/BGTextureNORM.jpg');
var bgDiffuseTexture = PIXI.Texture.fromImage('/demos/pixi-lights/assets/background.jpg');
var bgNormalTexture = PIXI.Texture.fromImage('/demos/pixi-lights/assets/backgroundNormalMap.jpg');
var background = create3DSprite(bgDiffuseTexture, bgNormalTexture);
world.addChild(background);

Expand All @@ -107,7 +107,6 @@ world.addChild(block2);
var block3 = create3DSprite(blockDiffuse, blockNormal, blockDiffuse);
block3.position.set(300, 300);
world.addChild(block3);
// stage.addChild(PIXI.shadows.shadowFilter._maskResultSprite);

// Make the light track your mouse
world.interactive = true;
Expand Down
19 changes: 10 additions & 9 deletions src/shadows/Shadow.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ShadowMapFilter from './filters/ShadowMapFilter';
-scatterRange [number] The radius at which the points of the light should be scattered. (Greater range = software shadow)
-radialResolution [number] The number of rays to draw for the light. (Higher resolution = more precise edges + more intensive)
-depthResolution [number] The of steps to take per pixel. (Higher resolution = more precise edges + more intensive)
-ignoreObject [Sprite] A object to ignore while creating the shadows. (Can be used if sprite and light always overlap)
-ignoreShadowCaster [Sprite] A shadow caster to ignore while creating the shadows. (Can be used if sprite and light always overlap)
*/

export default class Shadow extends PIXI.Sprite{
Expand All @@ -18,13 +18,13 @@ export default class Shadow extends PIXI.Sprite{

this._range = range;
this._pointCount = pointCount||20; //The number of lightpoins
this._scatterRange = scatterRange||(this._pointCount==1?0:20);
this._scatterRange = scatterRange||(this._pointCount==1?0:15);
this._intensity = 1;//intensity||1;
this._radialResolution = 500;
this._depthResolution = 1; //per screen pixel
this.anchor.set(0.5);

this._ignoreObject;
this._ignoreShadowCaster;

this.__createShadowMapSources();
}
Expand Down Expand Up @@ -67,8 +67,9 @@ export default class Shadow extends PIXI.Sprite{
}

// Update the map to create the mask from
update(renderer, objectSprite){
this._objectSprite = objectSprite;
update(renderer, shadowCasterSprite, shadowOverlaySprite){
this._shadowCasterSprite = shadowCasterSprite;
this._shadowOverlaySprite = shadowOverlaySprite;
renderer.render(this._shadowMapSprite, this._shadowMapResultTexture, true, null, true);
}

Expand All @@ -94,8 +95,8 @@ export default class Shadow extends PIXI.Sprite{
set depthResolution(resolution){
this._depthResolution = resolution;
}
set ignoreObject(sprite){
this.ignoreObject = sprite;
set ignoreShadowCaster(sprite){
this._ignoreShadowCaster = sprite;
}

// Attribute getters
Expand All @@ -117,7 +118,7 @@ export default class Shadow extends PIXI.Sprite{
get depthResolution(){
return this._depthResolution;
}
get ignoreObject(){
return this._ignoreObject;
get ignoreShadowCaster(){
return this._ignoreShadowCaster;
}
}
68 changes: 53 additions & 15 deletions src/shadows/filters/ShadowFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,35 @@ export default class ShadowFilter extends PIXI.Filter{

this.uniforms.ambientLight = 0.0;
this.uniforms.size = [this._width, this._height];
this._useShadowCastersAsOverlay = true;

this.__createObjectSources();
this.__createCasterSources();
this.__createOverlaySources();
this.__createMaskSources();
}
// collider objects
__createObjectSources(){
if(this._objectResultTexture) this._objectResultTexture.destroy();
if(this._objectResultSprite) this._objectResultSprite.destroy();
// Shadow overlay objects
__createOverlaySources(){
if(this._shadowOverlayResultTexture) this._shadowOverlayResultTexture.destroy();
if(this._shadowOverlayResultSprite) this._shadowOverlayResultSprite.destroy();

if(!this._objectContainer) this._objectContainer = new PIXI.Container();
if(!this._shadowOverlayContainer) this._shadowOverlayContainer = new PIXI.Container();

// Create the final mask to apply to the container that this filter is applied to
this._objectResultTexture = PIXI.RenderTexture.create(this._width, this._height);
this._objectResultSprite = new PIXI.Sprite(this._objectResultTexture);
this._shadowOverlayResultTexture = PIXI.RenderTexture.create(this._width, this._height);
this._shadowOverlayResultSprite = new PIXI.Sprite(this._shadowOverlayResultTexture);
}
// final mask to apply as a filter
// Shadow caster objects
__createCasterSources(){
if(this._shadowCasterResultTexture) this._shadowCasterResultTexture.destroy();
if(this._shadowCasterResultSprite) this._shadowCasterResultSprite.destroy();

if(!this._shadowCasterContainer) this._shadowCasterContainer = new PIXI.Container();

// Create the final mask to apply to the container that this filter is applied to
this._shadowCasterResultTexture = PIXI.RenderTexture.create(this._width, this._height);
this._shadowCasterResultSprite = new PIXI.Sprite(this._shadowCasterResultTexture);
}
// Final mask to apply as a filter
__createMaskSources(){
if(this._maskResultTexture) this._maskResultTexture.destroy();
if(this._maskResultSprite) this._maskResultSprite.destroy();
Expand All @@ -76,21 +89,38 @@ export default class ShadowFilter extends PIXI.Filter{

this.tick++; // Increase the tick so that shadows and objects know they can add themselves to the container again in their next update

/* render shadow casters */
// Remove the parent layer from the objects in order to properly render it to the container
this._objectContainer.children.forEach(child => {
this._shadowCasterContainer.children.forEach(child => {
child._activeParentLayer = null;
});

// Render all the objects onto 1 texture
renderer.render(this._objectContainer, this._objectResultTexture, true, null, true);
renderer.render(this._shadowCasterContainer, this._shadowCasterResultTexture, true, null, true);

// Remove all the objects from the container
this._objectContainer.children.length = 0;
this._shadowCasterContainer.children.length = 0;

/* render shadow overlays */
if(!this._useShadowCastersAsOverlay){
this._shadowOverlayContainer.children.forEach(child => {
child._activeParentLayer = null;
});

// Render all the objects onto 1 texture
renderer.render(this._shadowOverlayContainer, this._shadowOverlayResultTexture, true, null, true);

// Remove all the objects from the container
this._shadowOverlayContainer.children.length = 0;
}

/* render shadows */

// Update all shadows and indicate that they may properly be rendered now
let overlay = this._useShadowCastersAsOverlay? this._shadowCasterResultSprite: this._shadowOverlayResultSprite;
this._maskContainer.children.forEach(shadow => {
shadow.renderStep = true;
shadow.update(renderer, this._objectResultSprite);
shadow.update(renderer, this._shadowCasterResultSprite, overlay);
});

// Render all the final shadow masks onto 1 texture
Expand Down Expand Up @@ -132,16 +162,21 @@ export default class ShadowFilter extends PIXI.Filter{
this._width = width;

this.uniforms.size = [this._width, this._height];
this.__createObjectSources();
this.__createOverlaySources();
this.__createCasterSources();
this.__createMaskSources();
}
set height(height){
this._height = height;

this.uniforms.size = [this._width, this._height];
this.__createObjectSources();
this.__createOverlaySources();
this.__createCasterSources();
this.__createMaskSources();
}
set useShadowCasterAsOverlay(val){
this._useShadowCastersAsOverlay = val;
}

// Attribute getters
get ambientLight(){
Expand All @@ -153,4 +188,7 @@ export default class ShadowFilter extends PIXI.Filter{
get height(){
return this._height;
}
get useShadowCasterAsOverlay(){
return this._useShadowCastersAsOverlay;
}
}
44 changes: 22 additions & 22 deletions src/shadows/filters/ShadowMapFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ export default class ShadowMapFilter extends PIXI.Filter{
varying vec2 vTextureCoord;
uniform vec4 filterArea;
uniform sampler2D objectSampler;
uniform vec2 objectSpriteDimensions;
uniform sampler2D shadowCasterSampler;
uniform vec2 shadowCasterSpriteDimensions;
uniform bool hasIgnoreObject;
uniform sampler2D ignoreObjectSampler;
uniform mat3 ignoreObjectMatrix;
uniform vec2 ignoreObjectDimensions;
uniform bool hasIgnoreShadowCaster;
uniform sampler2D ignoreShadowCasterSampler;
uniform mat3 ignoreShadowCasterMatrix;
uniform vec2 ignoreShadowCasterDimensions;
uniform float lightRange;
uniform float lightScatterRange;
Expand Down Expand Up @@ -73,13 +73,13 @@ export default class ShadowMapFilter extends PIXI.Filter{
vec2 coord = lightLoc + offset + vec2(cos(angle), sin(angle)) * distance;
// Extract the pixel and check if it is opaque
float opacity = texture2D(objectSampler, coord / objectSpriteDimensions).a;
if(opacity > 0.95){
float opacity = texture2D(shadowCasterSampler, coord / shadowCasterSpriteDimensions).a;
if(opacity > 0.5){
// Check if it isn't hitting something that should be ignore
if(hasIgnoreObject){
vec2 l = (ignoreObjectMatrix * vec3(coord, 1.0)).xy / ignoreObjectDimensions;
if(hasIgnoreShadowCaster){
vec2 l = (ignoreShadowCasterMatrix * vec3(coord, 1.0)).xy / ignoreShadowCasterDimensions;
if(l.x >= -0.01 && l.x <= 1.01 && l.y >= -0.01 && l.y <= 1.01){
// If the pixel at the ignoreObject is opaque here, skip this pixel
// If the pixel at the ignoreShadowCaster is opaque here, skip this pixel
if(opacity > 0.5){
continue;
}
Expand All @@ -105,14 +105,14 @@ export default class ShadowMapFilter extends PIXI.Filter{
this.autoFit = false;
this.padding = 0;

this.ignoreObjectMatrix = new PIXI.Matrix();
this.ignoreShadowCasterMatrix = new PIXI.Matrix();
}

apply(filterManager, input, output){
// Attach the object sampler
var os = this.shadow._objectSprite;
this.uniforms.objectSpriteDimensions = [os.width, os.height];
this.uniforms.objectSampler = os._texture;
var sc = this.shadow._shadowCasterSprite;
this.uniforms.shadowCasterSpriteDimensions = [sc.width, sc.height];
this.uniforms.shadowCasterSampler = sc._texture;

// Use the world transform (data about the absolute location on the screen) to determine the lights relation to the objectSampler
var wt = this.shadow.worldTransform;
Expand All @@ -124,16 +124,16 @@ export default class ShadowMapFilter extends PIXI.Filter{
this.uniforms.depthResolution = range * this.shadow.depthResolution;

// Check if there is an object that the filter should attempt to ignore
var io = this.shadow.ignoreObject;
this.uniforms.hasIgnoreObject = !!io;
if(io){
var isc = this.shadow.ignoreShadowCaster;
this.uniforms.hasIgnoreShadowCaster = !!isc;
if(isc){
// Calculate the tranform matrix in order to access the proper pixel of the ignoreObject
io.worldTransform.copy(this.ignoreObjectMatrix);
this.uniforms.ignoreObjectMatrix = this.ignoreObjectMatrix.invert();
isc.worldTransform.copy(this.ignoreShadowCasterMatrix);
this.uniforms.ignoreShadowCasterMatrix = this.ignoreShadowCasterMatrix.invert();

// Attach the ignore object
this.uniforms.ignoreObjectDimensions = [io.width, io.height];
this.uniforms.ignoreObjectSampler = io._texture;
this.uniforms.ignoreShadowCasterDimensions = [isc.width, isc.height];
this.uniforms.ignoreShadowCasterSampler = isc._texture;
}

// Apply the filter
Expand Down
Loading

0 comments on commit 0c60fb6

Please sign in to comment.