Skip to content

Commit

Permalink
Refactor sprite mask system (#2369)
Browse files Browse the repository at this point in the history
* refactor(mask): refactor mask system
  • Loading branch information
singlecoder authored Oct 22, 2024
1 parent 1b3c7fd commit a7b36ad
Show file tree
Hide file tree
Showing 24 changed files with 566 additions and 257 deletions.
178 changes: 178 additions & 0 deletions e2e/case/spriteMask-customStencil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
* @title SpriteMaskCustomStencil
* @category SpriteMask
*/

import {
AssetType,
Camera,
CompareFunction,
Layer,
Script,
Sprite,
SpriteMask,
SpriteMaskInteraction,
SpriteRenderer,
StencilOperation,
Texture2D,
Vector3,
WebGLEngine
} from "@galacean/engine";
import { initScreenshot, updateForE2E } from "./.mockForE2E";

// Create engine
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
engine.canvas.resizeByClientSize();

// Create root entity
const rootEntity = engine.sceneManager.activeScene.createRootEntity();

// Create camera
const cameraEntity = rootEntity.createChild("Camera");
cameraEntity.transform.setPosition(0, 0, 50);
const camera = cameraEntity.addComponent(Camera);
camera.cullingMask = Layer.Layer0;

// Create sprite and mask
engine.resourceManager
.load([
{
// Sprite texture
url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*rgNGR4Vb7lQAAAAAAAAAAAAAARQnAQ",
type: AssetType.Texture2D
},
{
// Mask texture
url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*qyhFT5Un5AgAAAAAAAAAAAAAARQnAQ",
type: AssetType.Texture2D
}
])
.then((textures: Texture2D[]) => {
const pos = new Vector3();
const scale = new Vector3();

// Create sprites.
const sprite = new Sprite(engine, textures[0]);
const maskSprite = new Sprite(engine, textures[1]);

// create a sprite renderer, and write stencil
pos.set(0, 0, 0);
scale.set(5, 5, 5);
const writeStencilSR = addSpriteRenderer(
pos,
scale,
sprite,
SpriteMaskInteraction.None,
Layer.Layer0,
Layer.Layer0,
0
);
const writeStencilMaterial = writeStencilSR.getInstanceMaterial();
const writeStencilState = writeStencilMaterial.renderState.stencilState;
writeStencilState.enabled = true;
writeStencilState.writeMask = 0xff;
writeStencilState.passOperationFront = StencilOperation.IncrementSaturate;

// create a sprite renderer, mask interaction is none, and read stencil
pos.set(3, 3, 0);
const readStencilSR = addSpriteRenderer(
pos,
scale,
sprite,
SpriteMaskInteraction.None,
Layer.Layer0,
Layer.Layer0,
1
);
readStencilSR.color.set(1, 0, 0, 1);
const readStencilMaterial = readStencilSR.getInstanceMaterial();
const readStencilState = readStencilMaterial.renderState.stencilState;
readStencilState.enabled = true;
readStencilState.referenceValue = 1;
readStencilState.compareFunctionFront = CompareFunction.LessEqual;
readStencilState.compareFunctionBack = CompareFunction.LessEqual;

// create a sprite renderer, mask interaction is not none
pos.set(5, -3, 0);
const maskSR = addSpriteRenderer(
pos,
scale,
sprite,
SpriteMaskInteraction.VisibleOutsideMask,
Layer.Layer0,
Layer.Layer0,
2
);
maskSR.color.set(0, 1, 0, 1);

// create a sprite mask
pos.set(20, 0, 0);
addMask(pos, maskSprite, Layer.Layer0, Layer.Layer0);

// create a sprite renderer, and read stencil
pos.set(20, 10, 0);
scale.set(3, 3, 3);
const readStencilSR2 = addSpriteRenderer(
pos,
scale,
sprite,
SpriteMaskInteraction.None,
Layer.Layer0,
Layer.Layer0,
4
);
readStencilSR2.color.set(1, 0.5, 0.8, 1);
const readStencilMaterial2 = readStencilSR2.getInstanceMaterial();
const readStencilState2 = readStencilMaterial2.renderState.stencilState;
readStencilState2.enabled = true;
readStencilState2.referenceValue = 1;
readStencilState2.compareFunctionFront = CompareFunction.Greater;
readStencilState2.compareFunctionBack = CompareFunction.Greater;

updateForE2E(engine, 100, 100);
initScreenshot(engine, camera);
});

engine.run();

/**
* Add sprite renderer and set mask interaction and layer.
*/
function addSpriteRenderer(
pos: Vector3,
scale: Vector3,
sprite: Sprite,
maskInteraction: SpriteMaskInteraction,
maskLayer: number,
layer: number,
priority: number
): SpriteRenderer {
const entity = rootEntity.createChild("Sprite");
entity.layer = layer;
const renderer = entity.addComponent(SpriteRenderer);
const { transform } = entity;

transform.position = pos;
transform.scale = scale;
renderer.sprite = sprite;
renderer.maskInteraction = maskInteraction;
renderer.maskLayer = maskLayer;
renderer.priority = priority;

return renderer;
}

/**
* Add sprite mask and set influence layers, include mask animation script.
*/
function addMask<T extends Script>(pos: Vector3, sprite: Sprite, layer: number, influenceLayers: number): void {
const entity = rootEntity.createChild(`Mask`);
entity.layer = layer;
const mask = entity.addComponent(SpriteMask);

// entity.addComponent(scriptType);
entity.transform.position = pos;
mask.sprite = sprite;
mask.influenceLayers = influenceLayers;
}
});
7 changes: 7 additions & 0 deletions e2e/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,13 @@ export const E2E_CONFIG = {
threshold: 0.2
}
},
SpriteMask: {
CustomStencil: {
category: "SpriteMask",
caseFileName: "spriteMask-customStencil",
threshold: 0.3
}
},
Text: {
TypedText: {
category: "Text",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions examples/sprite-mask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
// Create sprites.
const sprite = new Sprite(engine, textures[0]);
const maskSprite0 = new Sprite(engine, textures[1]);
const maksSprite1 = new Sprite(engine, textures[2]);
const maskSprite1 = new Sprite(engine, textures[2]);

// Show inside mask.
pos.set(-5, 0, 0);
Expand All @@ -82,7 +82,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
SpriteMaskInteraction.VisibleOutsideMask,
SpriteMaskLayer.Layer1
);
addMask(pos, maksSprite1, SpriteMaskLayer.Layer1, RotationScript);
addMask(pos, maskSprite1, SpriteMaskLayer.Layer1, RotationScript);
});

engine.run();
Expand Down Expand Up @@ -114,7 +114,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
function addMask<T extends Script>(
pos: Vector3,
sprite: Sprite,
influenceLayers: number,
influenceLayers: SpriteMaskLayer,
scriptType: new (entity: Entity) => T
): void {
const entity = rootEntity.createChild("Mask");
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/2d/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export { SpriteMaskInteraction } from "./enums/SpriteMaskInteraction";
export { SpriteMaskLayer } from "./enums/SpriteMaskLayer";
export { TextHorizontalAlignment, TextVerticalAlignment } from "./enums/TextAlignment";
export { OverflowMode } from "./enums/TextOverflow";
export { FontStyle } from "./enums/FontStyle";
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/2d/sprite/SpriteMask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk";
import { SubRenderElement } from "../../RenderPipeline/SubRenderElement";
import { Renderer, RendererUpdateFlags } from "../../Renderer";
import { assignmentClone, ignoreClone } from "../../clone/CloneManager";
import { SpriteMaskLayer } from "../../enums/SpriteMaskLayer";
import { ShaderProperty } from "../../shader/ShaderProperty";
import { SimpleSpriteAssembler } from "../assembler/SimpleSpriteAssembler";
import { SpriteMaskLayer } from "../enums/SpriteMaskLayer";
import { SpriteModifyFlags } from "../enums/SpriteModifyFlags";
import { Sprite } from "./Sprite";

Expand All @@ -26,7 +26,7 @@ export class SpriteMask extends Renderer {

/** The mask layers the sprite mask influence to. */
@assignmentClone
influenceLayers: number = SpriteMaskLayer.Everything;
influenceLayers: SpriteMaskLayer = SpriteMaskLayer.Everything;
/** @internal */
@ignoreClone
_renderElement: RenderElement;
Expand Down Expand Up @@ -177,7 +177,7 @@ export class SpriteMask extends Renderer {
constructor(entity: Entity) {
super(entity);
SimpleSpriteAssembler.resetData(this);
this.setMaterial(this._engine._spriteMaskDefaultMaterial);
this.setMaterial(this._engine._basicResources.spriteMaskDefaultMaterial);
this.shaderData.setFloat(SpriteMask._alphaCutoffProperty, this._alphaCutoff);
this._renderElement = new RenderElement();
this._renderElement.addSubRenderElement(new SubRenderElement());
Expand Down Expand Up @@ -261,7 +261,7 @@ export class SpriteMask extends Renderer {
const { _engine: engine } = this;
// @todo: This question needs to be raised rather than hidden.
if (material.destroyed) {
material = engine._spriteMaskDefaultMaterial;
material = engine._basicResources.spriteMaskDefaultMaterial;
}

// Update position
Expand Down
28 changes: 2 additions & 26 deletions packages/core/src/2d/sprite/SpriteRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { SubRenderElement } from "../../RenderPipeline/SubRenderElement";
import { Renderer, RendererUpdateFlags } from "../../Renderer";
import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager";
import { ShaderProperty } from "../../shader/ShaderProperty";
import { CompareFunction } from "../../shader/enums/CompareFunction";
import { ISpriteAssembler } from "../assembler/ISpriteAssembler";
import { SimpleSpriteAssembler } from "../assembler/SimpleSpriteAssembler";
import { SlicedSpriteAssembler } from "../assembler/SlicedSpriteAssembler";
Expand Down Expand Up @@ -257,7 +256,6 @@ export class SpriteRenderer extends Renderer {

set maskInteraction(value: SpriteMaskInteraction) {
if (this._maskInteraction !== value) {
this._updateStencilState(this._maskInteraction, value);
this._maskInteraction = value;
}
}
Expand All @@ -269,7 +267,7 @@ export class SpriteRenderer extends Renderer {
super(entity);
this.drawMode = SpriteDrawMode.Simple;
this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.Color;
this.setMaterial(this._engine._spriteDefaultMaterial);
this.setMaterial(this._engine._basicResources.spriteDefaultMaterial);
this._onSpriteChange = this._onSpriteChange.bind(this);
//@ts-ignore
this._color._onValueChanged = this._onColorChanged.bind(this);
Expand Down Expand Up @@ -334,7 +332,7 @@ export class SpriteRenderer extends Renderer {
}
// @todo: This question needs to be raised rather than hidden.
if (material.destroyed) {
material = this._engine._spriteDefaultMaterials[this._maskInteraction];
material = this._engine._basicResources.spriteDefaultMaterial;
}

// Update position
Expand Down Expand Up @@ -395,28 +393,6 @@ export class SpriteRenderer extends Renderer {
this._dirtyUpdateFlag &= ~SpriteRendererUpdateFlags.AutomaticSize;
}

private _updateStencilState(from: SpriteMaskInteraction, to: SpriteMaskInteraction): void {
const material = this.getMaterial();
const { _spriteDefaultMaterials: spriteDefaultMaterials } = this._engine;
if (material === spriteDefaultMaterials[from]) {
this.setMaterial(spriteDefaultMaterials[to]);
} else {
const { stencilState } = material.renderState;
if (to === SpriteMaskInteraction.None) {
stencilState.enabled = false;
stencilState.writeMask = 0xff;
stencilState.referenceValue = 0;
stencilState.compareFunctionFront = stencilState.compareFunctionBack = CompareFunction.Always;
} else {
stencilState.enabled = true;
stencilState.writeMask = 0x00;
stencilState.referenceValue = 1;
stencilState.compareFunctionFront = stencilState.compareFunctionBack =
to === SpriteMaskInteraction.VisibleInsideMask ? CompareFunction.LessEqual : CompareFunction.Greater;
}
}
}

@ignoreClone
private _onSpriteChange(type: SpriteModifyFlags): void {
switch (type) {
Expand Down
Loading

0 comments on commit a7b36ad

Please sign in to comment.