Skip to content

Commit

Permalink
Added an option to invert shadows (shadow.shadowFilter._inverted=true)
Browse files Browse the repository at this point in the history
  • Loading branch information
TarVK committed Sep 28, 2018
1 parent 6201de0 commit 30b7e40
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 51 deletions.
1 change: 1 addition & 0 deletions src/shadows/Shadow.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default class Shadow extends PIXI.Sprite {
// Create the mask filter
var filter = new ShadowMaskFilter(this);
filter.blendMode = PIXI.BLEND_MODES.ADD;
this.shadowFilter = filter;
this.filters = [filter];
}
// Properly dispose all the created resources
Expand Down
142 changes: 95 additions & 47 deletions src/shadows/filters/ShadowFilter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export default class ShadowFilter extends PIXI.Filter{
constructor(width, height){
super(`
export default class ShadowFilter extends PIXI.Filter {
constructor(width, height) {
super(
`
attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
Expand All @@ -17,7 +18,8 @@ export default class ShadowFilter extends PIXI.Filter{
vTextureCoord = aTextureCoord;
vMaskCoord = ( otherMatrix * vec3( aTextureCoord, 1.0) ).xy;
}
`,`
`,
`
varying vec2 vMaskCoord;
varying vec2 vTextureCoord;
Expand All @@ -34,7 +36,8 @@ export default class ShadowFilter extends PIXI.Filter{
gl_FragColor = original;
}
`);
`
);

this._width = width;
this._height = height;
Expand All @@ -49,45 +52,67 @@ export default class ShadowFilter extends PIXI.Filter{
this.__createMaskSources();
}
// Shadow overlay objects
__createOverlaySources(){
if(this._shadowOverlayResultTexture) this._shadowOverlayResultTexture.destroy();
if(this._shadowOverlayResultSprite) this._shadowOverlayResultSprite.destroy();
__createOverlaySources() {
if (this._shadowOverlayResultTexture)
this._shadowOverlayResultTexture.destroy();
if (this._shadowOverlayResultSprite)
this._shadowOverlayResultSprite.destroy();

if(!this._shadowOverlayContainer) this._shadowOverlayContainer = 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._shadowOverlayResultTexture = PIXI.RenderTexture.create(this._width, this._height);
this._shadowOverlayResultTexture.baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;
this._shadowOverlayResultSprite = new PIXI.Sprite(this._shadowOverlayResultTexture);
this._shadowOverlayResultTexture = PIXI.RenderTexture.create(
this._width,
this._height
);
this._shadowOverlayResultTexture.baseTexture.scaleMode =
PIXI.SCALE_MODES.NEAREST;
this._shadowOverlayResultSprite = new PIXI.Sprite(
this._shadowOverlayResultTexture
);
}
// Shadow caster objects
__createCasterSources(){
if(this._shadowCasterResultTexture) this._shadowCasterResultTexture.destroy();
if(this._shadowCasterResultSprite) this._shadowCasterResultSprite.destroy();
__createCasterSources() {
if (this._shadowCasterResultTexture)
this._shadowCasterResultTexture.destroy();
if (this._shadowCasterResultSprite)
this._shadowCasterResultSprite.destroy();

if(!this._shadowCasterContainer) this._shadowCasterContainer = new PIXI.Container();
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._shadowCasterResultTexture.baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;
this._shadowCasterResultSprite = new PIXI.Sprite(this._shadowCasterResultTexture);
this._shadowCasterResultTexture = PIXI.RenderTexture.create(
this._width,
this._height
);
this._shadowCasterResultTexture.baseTexture.scaleMode =
PIXI.SCALE_MODES.NEAREST;
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();
__createMaskSources() {
if (this._maskResultTexture) this._maskResultTexture.destroy();
if (this._maskResultSprite) this._maskResultSprite.destroy();

// Create maskMatrix for shader transform data
if(!this._maskMatrix) this._maskMatrix = new PIXI.Matrix();
if (!this._maskMatrix) this._maskMatrix = new PIXI.Matrix();

// Create the final mask to apply to the container that this filter is applied to
this._maskResultTexture = PIXI.RenderTexture.create(this._width, this._height);
this._maskResultTexture.baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;
if(!this._maskContainer) this._maskContainer = new PIXI.Container();
this._maskResultTexture = PIXI.RenderTexture.create(
this._width,
this._height
);
this._maskResultTexture.baseTexture.scaleMode =
PIXI.SCALE_MODES.NEAREST;
if (!this._maskContainer) this._maskContainer = new PIXI.Container();
this._maskResultSprite = new PIXI.Sprite(this._maskResultTexture);
}
// Update the mask texture (called from the Application mixin)
update(renderer){
update(renderer) {
// Shadows and objects will automatically be added to containers because of the Container mixin

this.tick++; // Increase the tick so that shadows and objects know they can add themselves to the container again in their next update
Expand All @@ -99,35 +124,55 @@ export default class ShadowFilter extends PIXI.Filter{
});

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

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

/* render shadow overlays */
if(!this._useShadowCastersAsOverlay){
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);

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;
let overlay = this._useShadowCastersAsOverlay
? this._shadowCasterResultSprite
: this._shadowOverlayResultSprite;
this._maskContainer.children.forEach(shadow => {
shadow.renderStep = true;
shadow.update(renderer, this._shadowCasterResultSprite, overlay);
});

// Render all the final shadow masks onto 1 texture
renderer.render(this._maskContainer, this._maskResultTexture, true, null, true);
renderer.render(
this._maskContainer,
this._maskResultTexture,
true,
null,
true
);

// Indicate that the shadows may no longer render
this._maskContainer.children.forEach(shadow => {
Expand All @@ -139,7 +184,7 @@ export default class ShadowFilter extends PIXI.Filter{
}

// Apply the filter to a container
apply(filterManager, input, output){
apply(filterManager, input, output) {
// Filter almost directly taken from the pixi mask filter
const maskSprite = this._maskResultSprite;
const tex = this._maskResultSprite.texture;
Expand All @@ -152,7 +197,10 @@ export default class ShadowFilter extends PIXI.Filter{
}

this.uniforms.mask = tex;
this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this._maskMatrix, maskSprite);
this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(
this._maskMatrix,
maskSprite
);

filterManager.applyFilter(this, input, output);
}
Expand All @@ -161,13 +209,13 @@ export default class ShadowFilter extends PIXI.Filter{
/**
* @type {number} The brightness that unlit areas of the world should have
*/
set ambientLight(frac){
set ambientLight(frac) {
this.uniforms.ambientLight = frac;
}
/**
* @type {number} The width of your application
*/
set width(width){
set width(width) {
this._width = width;

this.uniforms.size = [this._width, this._height];
Expand All @@ -178,7 +226,7 @@ export default class ShadowFilter extends PIXI.Filter{
/**
* @type {number} The height of your application
*/
set height(height){
set height(height) {
this._height = height;

this.uniforms.size = [this._width, this._height];
Expand All @@ -187,23 +235,23 @@ export default class ShadowFilter extends PIXI.Filter{
this.__createMaskSources();
}
/**
* @type {boolean} Whether or not to use shadow casters as shadow overlays as well
* @type {boolean} Whether or not to use shadow casters as shadow overlays as well
*/
set useShadowCasterAsOverlay(val){
set useShadowCasterAsOverlay(val) {
this._useShadowCastersAsOverlay = val;
}

// Attribute getters
get ambientLight(){
get ambientLight() {
return this.uniforms.ambientLight;
}
get width(){
get width() {
return this._width;
}
get height(){
get height() {
return this._height;
}
get useShadowCasterAsOverlay(){
get useShadowCasterAsOverlay() {
return this._useShadowCastersAsOverlay;
}
}
}
23 changes: 19 additions & 4 deletions src/shadows/filters/ShadowMaskFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default class ShadowMaskFilter extends PIXI.Filter {
uniform sampler2D shadowSampler;
uniform bool darkenOverlay;
uniform bool inverted;
uniform float lightPointCount;
uniform float lightRange;
Expand Down Expand Up @@ -85,10 +86,20 @@ export default class ShadowMaskFilter extends PIXI.Filter {
intensity *= pow(1.0 - (distance - objectDistance) / (lightRange - objectDistance), 2.5) * overlayPixel.a;
}
}else{
if(overlayPixel.a > 0.5){
intensity = (1.0 - pow(distance / lightRange, 0.3)) * overlayPixel.a;
}else if (objectDistance > pointDistance || objectDistance >= lightRange) {
intensity = 1.0 - distance / lightRange;
if(inverted){
if(overlayPixel.a > 0.5){
intensity = 1.0-overlayPixel.a;
}else if (objectDistance > pointDistance || objectDistance >= lightRange) {
intensity = 0.0;
}else{
intensity = 1.0;
}
}else{
if(overlayPixel.a > 0.5){
intensity = (1.0 - pow(distance / lightRange, 0.3)) * overlayPixel.a;
}else if (objectDistance > pointDistance || objectDistance >= lightRange) {
intensity = 1.0 - distance / lightRange;
}
}
}
Expand All @@ -107,6 +118,7 @@ export default class ShadowMaskFilter extends PIXI.Filter {
this.uniforms.lightPointCount = shadow._pointCount;

this.shadow = shadow;
this._inverted = false;

this.autoFit = false;
this.padding = 0;
Expand All @@ -130,6 +142,9 @@ export default class ShadowMaskFilter extends PIXI.Filter {
this.uniforms.lightScatterRange = this.shadow.scatterRange;
this.uniforms.lightIntensity = this.shadow.intensity;

// Invert the filter if specified
this.uniforms.inverted = this._inverted;

// Texture size increase in order to fit the sprite rectangle (even though we are only interested in a circle)
// So we have to consider this in the texture size
var texSize = 2 * this.shadow.range * (wt.a + wt.b);
Expand Down

0 comments on commit 30b7e40

Please sign in to comment.