Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Extensions/3D/BloomEffect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ namespace gdjs {
0,
0
);
// The bloom is added on the rendered layer, but it must not make
// the transparent parts of the layer opaque (the layers rendered
// before this one must remain visible).
const blendMaterial = this.shaderPass.blendMaterial;
blendMaterial.blending = THREE.CustomBlending;
blendMaterial.blendEquation = THREE.AddEquation;
blendMaterial.blendSrc = THREE.SrcAlphaFactor;
blendMaterial.blendDst = THREE.OneFactor;
blendMaterial.blendEquationAlpha = THREE.AddEquation;
blendMaterial.blendSrcAlpha = THREE.ZeroFactor;
blendMaterial.blendDstAlpha = THREE.OneFactor;
this._isEnabled = false;
}

Expand Down
15 changes: 15 additions & 0 deletions Extensions/Effects/bevel-pixi-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,24 @@ namespace gdjs {
lc: number;
sc: number;
}
/**
* `PIXI.filters.BevelFilter` reads the pixels up to `thickness` pixels away, but
* declares a padding of 1. Keep the padding in sync with the thickness, otherwise
* the filter reads outside of what PixiJS rendered and a dark line appears on the
* edges of the screen.
*/
const updateBevelFilterPadding = function (
bevelFilter: PIXI.filters.BevelFilter
) {
bevelFilter.padding = Math.max(1, bevelFilter.thickness);
};

gdjs.PixiFiltersTools.registerFilterCreator(
'Bevel',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
makePIXIFilter(target: EffectsTarget, effectData) {
const bevelFilter = new PIXI.filters.BevelFilter();
updateBevelFilterPadding(bevelFilter);
return bevelFilter;
}
updatePreRender(filter: PIXI.Filter, target: EffectsTarget) {}
Expand All @@ -31,6 +44,7 @@ namespace gdjs {
bevelFilter.rotation = value;
} else if (parameterName === 'thickness') {
bevelFilter.thickness = value;
updateBevelFilterPadding(bevelFilter);
} else if (parameterName === 'distance') {
bevelFilter.distance = value;
} else if (parameterName === 'lightAlpha') {
Expand Down Expand Up @@ -128,6 +142,7 @@ namespace gdjs {
bevelFilter.shadowAlpha = data.sa;
bevelFilter.lightColor = data.lc;
bevelFilter.shadowColor = data.sc;
updateBevelFilterPadding(bevelFilter);
}
})()
);
Expand Down
6 changes: 5 additions & 1 deletion Extensions/Effects/kawase-blur-pixi-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ namespace gdjs {
'KawaseBlur',
new (class extends gdjs.PixiFiltersTools.PixiFilterCreator {
makePIXIFilter(target: EffectsTarget, effectData) {
const kawaseBlurFilter = new PIXI.filters.KawaseBlurFilter();
// Clamp the texture coordinates, so that the blur never reads the empty
// area that PixiJS can leave around what is rendered (which would show
// up as a seam on the right and bottom edges).
const clamp = true;
const kawaseBlurFilter = new PIXI.filters.KawaseBlurFilter(4, 3, clamp);
return kawaseBlurFilter;
}
updatePreRender(filter: PIXI.Filter, target: EffectsTarget) {}
Expand Down
86 changes: 83 additions & 3 deletions GDJS/Runtime/pixi-renderers/layer-pixi-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@ namespace gdjs {
private _pixiContainer: PIXI.Container;

private _layer: gdjs.RuntimeLayer;
private _runtimeGameRenderer: gdjs.RuntimeGameRenderer;

/**
* True for a layer of a scene, false for a layer inside a custom object
* (only the former is covering the whole screen).
*/
private _isSceneLayer: boolean;

/** For a lighting layer, the sprite used to display the render texture. */
private _lightingSprite: PIXI.Sprite | null = null;
Expand Down Expand Up @@ -245,13 +252,17 @@ namespace gdjs {
this._pixiContainer = new PIXI.Container();
this._pixiContainer.sortableChildren = true;
this._layer = layer;
this._runtimeGameRenderer = runtimeGameRenderer;
const instanceContainer = layer.getInstanceContainer();
this._isSceneLayer = instanceContainer === instanceContainer.getScene();
this._isLightingLayer = layer.isLightingLayer();
const parentRendererObject =
runtimeInstanceContainerRenderer.getRendererObject();
if (parentRendererObject) {
parentRendererObject.addChild(this._pixiContainer);
}
this._pixiContainer.filters = [];
this._updateFilterArea();

// Setup rendering for lighting or 3D rendering:
const pixiRenderer = runtimeGameRenderer.getPIXIRenderer();
Expand Down Expand Up @@ -285,6 +296,47 @@ namespace gdjs {
onGameResolutionResized() {
// Ensure the 3D camera aspect is updated:
this._update3DCameraAspectAndPosition();

this._updateFilterArea();
}

/**
* Tell PixiJS the area on which the effects (filters) of a scene layer must
* be applied: the whole screen.
*
* Without this, PixiJS uses the bounding box of what the layer contains,
* which has two downsides:
* - the bounding box of the whole layer is computed at every frame;
* - when this bounding box is smaller than the screen, PixiJS renders the
* layer in a bigger (rounded up to a power of two) texture taken from its
* pool. Effects reading the neighbor pixels (blurs notably) then read the
* empty area around the layer, which shows up as a seam on the right and
* bottom edges of the screen.
*/
private _updateFilterArea() {
if (!this._isSceneLayer) {
// A layer of a custom object only covers the object: let PixiJS compute
// the area from its content.
return;
}
const pixiRenderer = this._runtimeGameRenderer.getPIXIRenderer();
if (!pixiRenderer) {
return;
}
const filterArea = this._pixiContainer.filterArea;
if (filterArea) {
filterArea.x = 0;
filterArea.y = 0;
filterArea.width = pixiRenderer.screen.width;
filterArea.height = pixiRenderer.screen.height;
} else {
this._pixiContainer.filterArea = new PIXI.Rectangle(
0,
0,
pixiRenderer.screen.width,
pixiRenderer.screen.height
);
}
}

private _update3DCameraAspectAndPosition() {
Expand Down Expand Up @@ -441,8 +493,17 @@ namespace gdjs {
this._threeEffectComposer = new THREE_ADDONS.EffectComposer(
threeRenderer
);
// Clear the composer buffers with a transparent color, so that the
// parts of the layer where nothing is rendered stay transparent
// (the layers rendered before this one must remain visible).
this._threeEffectComposer.addPass(
new THREE_ADDONS.RenderPass(this._threeScene, this._threeCamera)
new THREE_ADDONS.RenderPass(
this._threeScene,
this._threeCamera,
null,
null,
0
)
);
if (game.getAntialiasingMode() !== 'none') {
this._threeEffectComposer.addPass(
Expand All @@ -452,7 +513,18 @@ namespace gdjs {
)
);
}
this._threeEffectComposer.addPass(new THREE_ADDONS.OutputPass());
const outputPass = new THREE_ADDONS.OutputPass();
// The composer result is drawn on top of the layers already rendered
// on the canvas: blend it (the buffers hold premultiplied colors)
// instead of overwriting everything.
outputPass.material.transparent = true;
outputPass.material.depthTest = false;
outputPass.material.depthWrite = false;
outputPass.material.blending = THREE.CustomBlending;
outputPass.material.blendEquation = THREE.AddEquation;
outputPass.material.blendSrc = THREE.OneFactor;
outputPass.material.blendDst = THREE.OneMinusSrcAlphaFactor;
this._threeEffectComposer.addPass(outputPass);
}

if (
Expand Down Expand Up @@ -994,7 +1066,15 @@ namespace gdjs {
updateResolution() {
if (this._threeEffectComposer) {
const game = this._layer.getRuntimeScene().getGame();
this._threeEffectComposer.setPixelRatio(window.devicePixelRatio);
const threeRenderer = game.getRenderer().getThreeRenderer();
if (threeRenderer) {
// The composer must render at the same resolution as the canvas,
// otherwise the layer would be scaled (and so, antialiased) when it's
// drawn on the canvas.
this._threeEffectComposer.setPixelRatio(
threeRenderer.getPixelRatio()
);
}
this._threeEffectComposer.setSize(
game.getGameResolutionWidth(),
game.getGameResolutionHeight()
Expand Down
26 changes: 26 additions & 0 deletions GDJS/Runtime/pixi-renderers/pixi-filters-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,18 @@ namespace gdjs {
): void;
}

/**
* Check if the target of an effect is a layer of a scene, as opposed to an
* object or a layer of a custom object. Only these cover the whole screen.
*/
const isSceneLayer = function (target: EffectsTarget): boolean {
if (!target.getRuntimeLayer) {
return false;
}
const instanceContainer = target.getRuntimeLayer().getInstanceContainer();
return instanceContainer === instanceContainer.getScene();
};

/**
* An effect used to manipulate a Pixi filter.
* @category Core Engine > Effects
Expand Down Expand Up @@ -197,6 +209,20 @@ namespace gdjs {
if (!rendererObject) {
return false;
}
if (isSceneLayer(target)) {
// The area on which the effect is applied is the whole screen
// (see `LayerPixiRenderer`). Let PixiJS apply it a bit outside of the
// screen too (as much as the effect needs to read pixels around
// each pixel):
// - PixiJS renders the layer in a texture taken from a pool, which is
// often bigger than the area asked for. Effects reading the
// neighbor pixels (blurs notably) would otherwise read the empty
// part of this texture, which shows up as a seam on the right and
// bottom edges of the screen.
// - what is just outside of the screen is then properly taken into
// account by these effects.
this.pixiFilter.autoFit = false;
}
rendererObject.filters = (rendererObject.filters || []).concat(
this.pixiFilter
);
Expand Down
1 change: 1 addition & 0 deletions GDJS/tests/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ module.exports = function (config) {
'./newIDE/app/resources/GDJS/Runtime/Extensions/3D/Cube3DRuntimeObjectPixiRenderer.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/3D/CustomRuntimeObject3D.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/3D/CustomRuntimeObject3DRenderer.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/3D/BloomEffect.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/TopDownMovementBehavior/topdownmovementruntimebehavior.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/TweenBehavior/TweenManager.js',
'./newIDE/app/resources/GDJS/Runtime/Extensions/TweenBehavior/tweentools.js',
Expand Down
Loading
Loading