diff --git a/e2e/case/spriteMask-customStencil.ts b/e2e/case/spriteMask-customStencil.ts new file mode 100644 index 0000000000..7bebd8e6ff --- /dev/null +++ b/e2e/case/spriteMask-customStencil.ts @@ -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(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; + } +}); diff --git a/e2e/config.ts b/e2e/config.ts index 67a1095ec3..4d82268092 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -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", diff --git a/e2e/fixtures/originImage/SpriteMask_spriteMask-customStencil.jpg b/e2e/fixtures/originImage/SpriteMask_spriteMask-customStencil.jpg new file mode 100644 index 0000000000..8c84caac0d --- /dev/null +++ b/e2e/fixtures/originImage/SpriteMask_spriteMask-customStencil.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11c6d6a25c25d357039842588ee3d7ef44e78742f54f62b222cf2bd09a10f6eb +size 211342 diff --git a/examples/sprite-mask.ts b/examples/sprite-mask.ts index bca7246d60..ed3240fddc 100644 --- a/examples/sprite-mask.ts +++ b/examples/sprite-mask.ts @@ -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); @@ -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(); @@ -114,7 +114,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { function addMask( pos: Vector3, sprite: Sprite, - influenceLayers: number, + influenceLayers: SpriteMaskLayer, scriptType: new (entity: Entity) => T ): void { const entity = rootEntity.createChild("Mask"); diff --git a/packages/core/src/2d/index.ts b/packages/core/src/2d/index.ts index eb70341681..d8fccbd267 100644 --- a/packages/core/src/2d/index.ts +++ b/packages/core/src/2d/index.ts @@ -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"; diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts index a35ca833da..8c50eaf04a 100644 --- a/packages/core/src/2d/sprite/SpriteMask.ts +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -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"; @@ -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; @@ -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()); @@ -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 diff --git a/packages/core/src/2d/sprite/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts index a0e3a60017..7327bded52 100644 --- a/packages/core/src/2d/sprite/SpriteRenderer.ts +++ b/packages/core/src/2d/sprite/SpriteRenderer.ts @@ -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"; @@ -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; } } @@ -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); @@ -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 @@ -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) { diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 86b05d3cca..eaa5f82ca4 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -10,7 +10,6 @@ import { Renderer } from "../../Renderer"; import { TransformModifyFlags } from "../../Transform"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; import { ShaderData, ShaderProperty } from "../../shader"; -import { CompareFunction } from "../../shader/enums/CompareFunction"; import { ShaderDataGroup } from "../../shader/enums/ShaderDataGroup"; import { Texture2D } from "../../texture"; import { FontStyle } from "../enums/FontStyle"; @@ -250,7 +249,6 @@ export class TextRenderer extends Renderer { set maskInteraction(value: SpriteMaskInteraction) { if (this._maskInteraction !== value) { this._maskInteraction = value; - this._setDirtyFlagTrue(DirtyFlag.MaskInteraction); } } @@ -294,7 +292,7 @@ export class TextRenderer extends Renderer { const { engine } = this; this._font = engine._textDefaultFont; this._addResourceReferCount(this._font, 1); - this.setMaterial(engine._textDefaultMaterial); + this.setMaterial(engine._basicResources.textDefaultMaterial); //@ts-ignore this._color._onValueChanged = this._onColorChanged.bind(this); } @@ -394,11 +392,6 @@ export class TextRenderer extends Renderer { return; } - if (this._isContainDirtyFlag(DirtyFlag.MaskInteraction)) { - this._updateStencilState(); - this._setDirtyFlagFalse(DirtyFlag.MaskInteraction); - } - if (this._isContainDirtyFlag(DirtyFlag.SubFont)) { this._resetSubFont(); this._setDirtyFlagFalse(DirtyFlag.SubFont); @@ -437,29 +430,6 @@ export class TextRenderer extends Renderer { camera._renderPipeline.pushRenderElement(context, renderElement); } - private _updateStencilState(): void { - const material = this.getInstanceMaterial(); - const stencilState = material.renderState.stencilState; - const maskInteraction = this._maskInteraction; - - if (maskInteraction === 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; - const compare = - maskInteraction === SpriteMaskInteraction.VisibleInsideMask - ? CompareFunction.LessEqual - : CompareFunction.Greater; - stencilState.compareFunctionFront = compare; - stencilState.compareFunctionBack = compare; - } - } - private _resetSubFont(): void { const font = this._font; this._subFont = font._getSubFont(this.fontSize, this.fontStyle); @@ -762,8 +732,7 @@ enum DirtyFlag { LocalPositionBounds = 0x2, WorldPosition = 0x4, WorldBounds = 0x8, - MaskInteraction = 0x10, - Color = 0x20, + Color = 0x10, Position = LocalPositionBounds | WorldPosition | WorldBounds, Font = SubFont | Position diff --git a/packages/core/src/BasicResources.ts b/packages/core/src/BasicResources.ts index d4d11b5907..3fcd5c72cd 100644 --- a/packages/core/src/BasicResources.ts +++ b/packages/core/src/BasicResources.ts @@ -1,4 +1,6 @@ +import { SpriteMaskInteraction } from "./2d"; import { Engine } from "./Engine"; +import { RenderQueueMaskType } from "./RenderPipeline/enums/RenderQueueMaskType"; import { ContentRestorer } from "./asset/ContentRestorer"; import { Buffer } from "./graphic/Buffer"; import { VertexElement } from "./graphic/VertexElement"; @@ -9,6 +11,14 @@ import { VertexElementFormat } from "./graphic/enums/VertexElementFormat"; import { Material } from "./material"; import { ModelMesh } from "./mesh"; import { Shader } from "./shader/Shader"; +import { BlendFactor } from "./shader/enums/BlendFactor"; +import { BlendOperation } from "./shader/enums/BlendOperation"; +import { ColorWriteMask } from "./shader/enums/ColorWriteMask"; +import { CompareFunction } from "./shader/enums/CompareFunction"; +import { CullMode } from "./shader/enums/CullMode"; +import { RenderQueueType } from "./shader/enums/RenderQueueType"; +import { RenderStateElementKey } from "./shader/enums/RenderStateElementKey"; +import { StencilOperation } from "./shader/enums/StencilOperation"; import { Texture, Texture2D, TextureCube, TextureCubeFace } from "./texture"; import { Texture2DArray } from "./texture/Texture2DArray"; import { TextureFormat } from "./texture/enums/TextureFormat"; @@ -17,6 +27,79 @@ import { TextureFormat } from "./texture/enums/TextureFormat"; * @internal */ export class BasicResources { + private static _maskReadInsideRenderStates: RenderStateElementMap = null; + private static _maskReadOutsideRenderStates: RenderStateElementMap = null; + private static _maskWriteIncrementRenderStates: RenderStateElementMap = null; + private static _maskWriteDecrementRenderStates: RenderStateElementMap = null; + + static getMaskInteractionRenderStates(maskInteraction: SpriteMaskInteraction): RenderStateElementMap { + const visibleInsideMask = maskInteraction === SpriteMaskInteraction.VisibleInsideMask; + let renderStates: RenderStateElementMap; + let compareFunction: CompareFunction; + + if (visibleInsideMask) { + renderStates = BasicResources._maskReadInsideRenderStates; + if (renderStates) { + return renderStates; + } + BasicResources._maskReadInsideRenderStates = renderStates = {}; + compareFunction = CompareFunction.LessEqual; + } else { + renderStates = BasicResources._maskReadOutsideRenderStates; + if (renderStates) { + return renderStates; + } + BasicResources._maskReadOutsideRenderStates = renderStates = {}; + compareFunction = CompareFunction.Greater; + } + + renderStates[RenderStateElementKey.StencilStateEnabled] = true; + renderStates[RenderStateElementKey.StencilStateWriteMask] = 0x00; + renderStates[RenderStateElementKey.StencilStateReferenceValue] = 1; + renderStates[RenderStateElementKey.StencilStateCompareFunctionFront] = compareFunction; + renderStates[RenderStateElementKey.StencilStateCompareFunctionBack] = compareFunction; + + return renderStates; + } + + static getMaskTypeRenderStates(maskType: RenderQueueMaskType): RenderStateElementMap { + const isIncrement = maskType === RenderQueueMaskType.Increment; + let renderStates: RenderStateElementMap; + let passOperation: StencilOperation; + + if (isIncrement) { + renderStates = BasicResources._maskWriteIncrementRenderStates; + if (renderStates) { + return renderStates; + } + BasicResources._maskWriteIncrementRenderStates = renderStates = {}; + passOperation = StencilOperation.IncrementSaturate; + } else { + renderStates = BasicResources._maskWriteDecrementRenderStates; + if (renderStates) { + return renderStates; + } + BasicResources._maskWriteDecrementRenderStates = renderStates = {}; + passOperation = StencilOperation.DecrementSaturate; + } + + renderStates[RenderStateElementKey.StencilStateEnabled] = true; + renderStates[RenderStateElementKey.StencilStatePassOperationFront] = passOperation; + renderStates[RenderStateElementKey.StencilStatePassOperationBack] = passOperation; + renderStates[RenderStateElementKey.StencilStateCompareFunctionFront] = CompareFunction.Always; + renderStates[RenderStateElementKey.StencilStateCompareFunctionBack] = CompareFunction.Always; + const failStencilOperation = StencilOperation.Keep; + renderStates[RenderStateElementKey.StencilStateFailOperationFront] = failStencilOperation; + renderStates[RenderStateElementKey.StencilStateFailOperationBack] = failStencilOperation; + renderStates[RenderStateElementKey.StencilStateZFailOperationFront] = failStencilOperation; + renderStates[RenderStateElementKey.StencilStateZFailOperationBack] = failStencilOperation; + renderStates[RenderStateElementKey.BlendStateColorWriteMask0] = ColorWriteMask.None; + renderStates[RenderStateElementKey.DepthStateEnabled] = false; + renderStates[RenderStateElementKey.RasterStateCullMode] = CullMode.Off; + + return renderStates; + } + /** * Use triangle to blit texture, ref: https://michaldrobot.com/2014/04/01/gcn-execution-patterns-in-full-screen-passes/ . */ @@ -29,6 +112,10 @@ export class BasicResources { readonly whiteTexture2DArray: Texture2DArray; readonly uintWhiteTexture2D: Texture2D; + readonly spriteDefaultMaterial: Material; + readonly textDefaultMaterial: Material; + readonly spriteMaskDefaultMaterial: Material; + constructor(engine: Engine) { // prettier-ignore const vertices = new Float32Array([ @@ -74,6 +161,10 @@ export class BasicResources { whitePixel32 ); } + + this.spriteDefaultMaterial = this._create2DMaterial(engine, Shader.find("Sprite")); + this.textDefaultMaterial = this._create2DMaterial(engine, Shader.find("Text")); + this.spriteMaskDefaultMaterial = this._createSpriteMaskMaterial(engine); } private _createBlitMesh(engine: Engine, vertices: Float32Array): ModelMesh { @@ -140,6 +231,29 @@ export class BasicResources { ); return texture as T; } + + private _create2DMaterial(engine: Engine, shader: Shader): Material { + const material = new Material(engine, shader); + const renderState = material.renderState; + const target = renderState.blendState.targetBlendState; + target.enabled = true; + target.sourceColorBlendFactor = BlendFactor.SourceAlpha; + target.destinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha; + target.sourceAlphaBlendFactor = BlendFactor.One; + target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha; + target.colorBlendOperation = target.alphaBlendOperation = BlendOperation.Add; + renderState.depthState.writeEnabled = false; + renderState.rasterState.cullMode = CullMode.Off; + renderState.renderQueueType = RenderQueueType.Transparent; + material.isGCIgnored = true; + return material; + } + + private _createSpriteMaskMaterial(engine: Engine): Material { + const material = new Material(engine, Shader.find("SpriteMask")); + material.isGCIgnored = true; + return material; + } } enum TextureType { @@ -147,3 +261,5 @@ enum TextureType { TextureCube, Texture2DArray } + +export type RenderStateElementMap = Record; diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index 2ef3641005..a83511f44c 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -7,7 +7,6 @@ import { IXRDevice } from "@galacean/engine-design"; import { Color } from "@galacean/engine-math"; -import { SpriteMaskInteraction } from "./2d"; import { CharRenderInfo } from "./2d/text/CharRenderInfo"; import { Font } from "./2d/text/Font"; import { BasicResources } from "./BasicResources"; @@ -30,18 +29,12 @@ import { Material } from "./material/Material"; import { ParticleBufferUtils } from "./particle/ParticleBufferUtils"; import { PhysicsScene } from "./physics/PhysicsScene"; import { ColliderShape } from "./physics/shape/ColliderShape"; -import { CompareFunction } from "./shader"; import { Shader } from "./shader/Shader"; import { ShaderMacro } from "./shader/ShaderMacro"; import { ShaderMacroCollection } from "./shader/ShaderMacroCollection"; import { ShaderPass } from "./shader/ShaderPass"; import { ShaderPool } from "./shader/ShaderPool"; import { ShaderProgramPool } from "./shader/ShaderProgramPool"; -import { BlendFactor } from "./shader/enums/BlendFactor"; -import { BlendOperation } from "./shader/enums/BlendOperation"; -import { ColorWriteMask } from "./shader/enums/ColorWriteMask"; -import { CullMode } from "./shader/enums/CullMode"; -import { RenderQueueType } from "./shader/enums/RenderQueueType"; import { RenderState } from "./shader/state/RenderState"; import { Texture2D, TextureFormat } from "./texture"; import { ClearableObjectPool } from "./utils/ClearableObjectPool"; @@ -93,14 +86,6 @@ export class Engine extends EventDispatcher { /* @internal */ _basicResources: BasicResources; /* @internal */ - _spriteDefaultMaterial: Material; - /** @internal */ - _spriteDefaultMaterials: Material[] = []; - /* @internal */ - _textDefaultMaterial: Material; - /* @internal */ - _spriteMaskDefaultMaterial: Material; - /* @internal */ _textDefaultFont: Font; /* @internal */ _renderContext: RenderContext = new RenderContext(); @@ -239,18 +224,6 @@ export class Engine extends EventDispatcher { this._canvas = canvas; - const { _spriteDefaultMaterials: spriteDefaultMaterials } = this; - this._spriteDefaultMaterial = spriteDefaultMaterials[SpriteMaskInteraction.None] = this._createSpriteMaterial( - SpriteMaskInteraction.None - ); - spriteDefaultMaterials[SpriteMaskInteraction.VisibleInsideMask] = this._createSpriteMaterial( - SpriteMaskInteraction.VisibleInsideMask - ); - spriteDefaultMaterials[SpriteMaskInteraction.VisibleOutsideMask] = this._createSpriteMaterial( - SpriteMaskInteraction.VisibleOutsideMask - ); - this._textDefaultMaterial = this._createTextMaterial(); - this._spriteMaskDefaultMaterial = this._createSpriteMaskMaterial(); this._textDefaultFont = Font.createFromOS(this, "Arial"); this._textDefaultFont.isGCIgnored = true; @@ -581,64 +554,6 @@ export class Engine extends EventDispatcher { return Promise.all(initializePromises).then(() => this); } - private _createSpriteMaterial(maskInteraction: SpriteMaskInteraction): Material { - const material = new Material(this, Shader.find("Sprite")); - const renderState = material.renderState; - const target = renderState.blendState.targetBlendState; - target.enabled = true; - target.sourceColorBlendFactor = BlendFactor.SourceAlpha; - target.destinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha; - target.sourceAlphaBlendFactor = BlendFactor.One; - target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha; - target.colorBlendOperation = target.alphaBlendOperation = BlendOperation.Add; - if (maskInteraction !== SpriteMaskInteraction.None) { - const stencilState = renderState.stencilState; - stencilState.enabled = true; - stencilState.writeMask = 0x00; - stencilState.referenceValue = 1; - const compare = - maskInteraction === SpriteMaskInteraction.VisibleInsideMask - ? CompareFunction.LessEqual - : CompareFunction.Greater; - stencilState.compareFunctionFront = compare; - stencilState.compareFunctionBack = compare; - } - renderState.depthState.writeEnabled = false; - renderState.rasterState.cullMode = CullMode.Off; - renderState.renderQueueType = RenderQueueType.Transparent; - material.isGCIgnored = true; - return material; - } - - private _createSpriteMaskMaterial(): Material { - const material = new Material(this, Shader.find("SpriteMask")); - const renderState = material.renderState; - renderState.blendState.targetBlendState.colorWriteMask = ColorWriteMask.None; - renderState.rasterState.cullMode = CullMode.Off; - renderState.stencilState.enabled = true; - renderState.depthState.enabled = false; - renderState.renderQueueType = RenderQueueType.Transparent; - material.isGCIgnored = true; - return material; - } - - private _createTextMaterial(): Material { - const material = new Material(this, Shader.find("Text")); - const renderState = material.renderState; - const target = renderState.blendState.targetBlendState; - target.enabled = true; - target.sourceColorBlendFactor = BlendFactor.SourceAlpha; - target.destinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha; - target.sourceAlphaBlendFactor = BlendFactor.One; - target.destinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha; - target.colorBlendOperation = target.alphaBlendOperation = BlendOperation.Add; - renderState.depthState.writeEnabled = false; - renderState.rasterState.cullMode = CullMode.Off; - renderState.renderQueueType = RenderQueueType.Transparent; - material.isGCIgnored = true; - return material; - } - private _onDeviceLost(): void { this._isDeviceLost = true; // Lose graphic resources diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index d37d8fbc75..0acfe0cfcd 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -166,7 +166,12 @@ export class BasicRenderPipeline { const clearFlags = camera.clearFlags & ~(ignoreClear ?? CameraClearFlags.None); const color = background.solidColor; if (clearFlags !== CameraClearFlags.None) { - rhi.clearRenderTarget(camera.engine, clearFlags, color); + rhi.clearRenderTarget(engine, clearFlags, color); + } + + const maskManager = scene._maskManager; + if (clearFlags & CameraClearFlags.Stencil) { + maskManager.hasStencilWritten = false; } opaqueQueue.render(context, PipelineStage.Forward); @@ -195,6 +200,8 @@ export class BasicRenderPipeline { } transparentQueue.render(context, PipelineStage.Forward); + // Revert stencil buffer generated by mask + maskManager.clearMask(context, PipelineStage.Forward); const postProcessManager = scene._postProcessManager; const cameraRenderTarget = camera.renderTarget; diff --git a/packages/core/src/RenderPipeline/MaskManager.ts b/packages/core/src/RenderPipeline/MaskManager.ts index bc8428ec07..2464594f3f 100644 --- a/packages/core/src/RenderPipeline/MaskManager.ts +++ b/packages/core/src/RenderPipeline/MaskManager.ts @@ -1,8 +1,14 @@ import { SpriteMask } from "../2d"; -import { RenderQueueType } from "../shader"; +import { CameraClearFlags } from "../enums/CameraClearFlags"; +import { SpriteMaskLayer } from "../enums/SpriteMaskLayer"; +import { Material } from "../material"; +import { CompareFunction } from "../shader"; +import { RenderQueueType } from "../shader/enums/RenderQueueType"; +import { StencilOperation } from "../shader/enums/StencilOperation"; import { DisorderedArray } from "../utils/DisorderedArray"; +import { RenderContext } from "./RenderContext"; import { RenderQueue } from "./RenderQueue"; -import { SubRenderElement } from "./SubRenderElement"; +import { RenderQueueMaskType } from "./enums/RenderQueueMaskType"; /** * @internal @@ -19,29 +25,110 @@ export class MaskManager { return (MaskManager._maskDecrementRenderQueue ||= new RenderQueue(RenderQueueType.Transparent)); } - allSpriteMasks = new DisorderedArray(); - preMaskLayer = 0; + hasStencilWritten = false; + + private _preMaskLayer = SpriteMaskLayer.Nothing; + private _allSpriteMasks = new DisorderedArray(); addSpriteMask(mask: SpriteMask): void { - mask._maskIndex = this.allSpriteMasks.length; - this.allSpriteMasks.add(mask); + mask._maskIndex = this._allSpriteMasks.length; + this._allSpriteMasks.add(mask); } removeSpriteMask(mask: SpriteMask): void { - const replaced = this.allSpriteMasks.deleteByIndex(mask._maskIndex); + const replaced = this._allSpriteMasks.deleteByIndex(mask._maskIndex); replaced && (replaced._maskIndex = mask._maskIndex); mask._maskIndex = -1; } - buildMaskRenderElement( - element: SubRenderElement, + drawMask(context: RenderContext, pipelineStageTagValue: string, maskLayer: SpriteMaskLayer): void { + const incrementMaskQueue = MaskManager.getMaskIncrementRenderQueue(); + incrementMaskQueue.clear(); + const decrementMaskQueue = MaskManager.getMaskDecrementRenderQueue(); + decrementMaskQueue.clear(); + + this._buildMaskRenderElement(maskLayer, incrementMaskQueue, decrementMaskQueue); + + const batcherManager = context.camera.engine._batcherManager; + incrementMaskQueue.batch(batcherManager); + batcherManager.uploadBuffer(); + incrementMaskQueue.render(context, pipelineStageTagValue, RenderQueueMaskType.Increment); + decrementMaskQueue.batch(batcherManager); + batcherManager.uploadBuffer(); + decrementMaskQueue.render(context, pipelineStageTagValue, RenderQueueMaskType.Decrement); + } + + clearMask(context: RenderContext, pipelineStageTagValue: string): void { + const preMaskLayer = this._preMaskLayer; + if (preMaskLayer !== SpriteMaskLayer.Nothing) { + if (this.hasStencilWritten) { + const decrementMaskQueue = MaskManager.getMaskDecrementRenderQueue(); + decrementMaskQueue.clear(); + + const masks = this._allSpriteMasks; + for (let i = 0, n = masks.length; i < n; i++) { + const mask = masks.get(i); + mask.influenceLayers & preMaskLayer && decrementMaskQueue.pushRenderElement(mask._renderElement); + } + + const batcherManager = context.camera.engine._batcherManager; + decrementMaskQueue.batch(batcherManager); + batcherManager.uploadBuffer(); + decrementMaskQueue.render(context, pipelineStageTagValue, RenderQueueMaskType.Decrement); + } else { + const engine = context.camera.engine; + engine._hardwareRenderer.clearRenderTarget(engine, CameraClearFlags.Stencil, null); + } + + this._preMaskLayer = SpriteMaskLayer.Nothing; + } + } + + isStencilWritten(material: Material): boolean { + const stencilState = material.renderState.stencilState; + const stencilOperation = StencilOperation.Keep; + if ( + stencilState.enabled && + stencilState.writeMask !== 0x00 && + (stencilState.passOperationFront !== stencilOperation || + stencilState.passOperationBack !== stencilOperation || + stencilState.failOperationFront !== stencilOperation || + stencilState.failOperationBack !== stencilOperation || + stencilState.zFailOperationFront !== stencilOperation || + stencilState.zFailOperationBack !== stencilOperation) + ) { + return true; + } + return false; + } + + isReadStencil(material: Material): boolean { + const { enabled, mask, compareFunctionFront, compareFunctionBack } = material.renderState.stencilState; + if ( + enabled && + mask !== 0x00 && + ((compareFunctionFront !== CompareFunction.Always && compareFunctionFront !== CompareFunction.Never) || + (compareFunctionBack !== CompareFunction.Always && compareFunctionBack !== CompareFunction.Never)) + ) { + return true; + } + return false; + } + + destroy(): void { + const allSpriteMasks = this._allSpriteMasks; + allSpriteMasks.length = 0; + allSpriteMasks.garbageCollection(); + } + + private _buildMaskRenderElement( + curMaskLayer: SpriteMaskLayer, incrementMaskQueue: RenderQueue, decrementMaskQueue: RenderQueue ): void { - const preMaskLayer = this.preMaskLayer; - const curMaskLayer = element.component._maskLayer; + const preMaskLayer = this._preMaskLayer; if (preMaskLayer !== curMaskLayer) { - const masks = this.allSpriteMasks; + const masks = this._allSpriteMasks; const commonLayer = preMaskLayer & curMaskLayer; const reduceLayer = preMaskLayer & ~curMaskLayer; const maskElements = masks._elements; @@ -59,11 +146,7 @@ export class MaskManager { decrementMaskQueue.pushRenderElement(mask._renderElement); } } - this.preMaskLayer = curMaskLayer; + this._preMaskLayer = curMaskLayer; } } - - destroy(): void { - this.allSpriteMasks.length = 0; - } } diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 6f4edd876f..f1be060d90 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -1,12 +1,13 @@ import { SpriteMaskInteraction } from "../2d/enums/SpriteMaskInteraction"; +import { BasicResources, RenderStateElementMap } from "../BasicResources"; import { Utils } from "../Utils"; -import { RenderQueueType, Shader, StencilOperation } from "../shader"; +import { RenderQueueType, Shader } from "../shader"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; import { BatcherManager } from "./BatcherManager"; -import { MaskManager } from "./MaskManager"; import { ContextRendererUpdateFlag, RenderContext } from "./RenderContext"; import { RenderElement } from "./RenderElement"; import { SubRenderElement } from "./SubRenderElement"; +import { RenderQueueMaskType } from "./enums/RenderQueueMaskType"; /** * @internal @@ -30,8 +31,12 @@ export class RenderQueue { } sortBatch(compareFunc: Function, batcherManager: BatcherManager): void { - this._sort(compareFunc); - this._batch(batcherManager); + Utils._quickSort(this.elements, 0, this.elements.length, compareFunc); + this.batch(batcherManager); + } + + batch(batcherManager: BatcherManager): void { + batcherManager.batch(this); } render( @@ -47,16 +52,15 @@ export class RenderQueue { const { rendererUpdateFlag, camera } = context; const { engine, scene, instanceId: cameraId, shaderData: cameraData } = camera; - const { instanceId: sceneId, shaderData: sceneData } = scene; + const { instanceId: sceneId, shaderData: sceneData, _maskManager: maskManager } = scene; const renderCount = engine._renderCount; const rhi = engine._hardwareRenderer; const pipelineStageKey = RenderContext.pipelineStageKey; const renderQueueType = this.renderQueueType; - scene._maskManager.preMaskLayer = 0; for (let i = 0; i < length; i++) { const subElement = batchedSubElements[i]; - const { component: renderer, batched } = subElement; + const { component: renderer, batched, material } = subElement; // @todo: Can optimize update view projection matrix updated if ( @@ -71,37 +75,41 @@ export class RenderQueue { renderer._updateTransformShaderData(context, true, batched); } - renderer._maskInteraction !== SpriteMaskInteraction.None && - this._drawMask(context, pipelineStageTagValue, subElement); + const maskInteraction = renderer._maskInteraction; + const needMaskInteraction = maskInteraction !== SpriteMaskInteraction.None; + const needMaskType = maskType !== RenderQueueMaskType.No; + let customStates: RenderStateElementMap = null; + + if (needMaskType) { + customStates = BasicResources.getMaskTypeRenderStates(maskType); + } else { + if (needMaskInteraction) { + maskManager.drawMask(context, pipelineStageTagValue, subElement.component._maskLayer); + customStates = BasicResources.getMaskInteractionRenderStates(maskInteraction); + } else { + maskManager.isReadStencil(material) && maskManager.clearMask(context, pipelineStageTagValue); + } + maskManager.isStencilWritten(material) && (maskManager.hasStencilWritten = true); + } const compileMacros = Shader._compileMacros; - const { primitive, material, shaderPasses, shaderData: renderElementShaderData } = subElement; + const { primitive, shaderPasses, shaderData: renderElementShaderData } = subElement; const { shaderData: rendererData, instanceId: rendererId } = renderer; const { shaderData: materialData, instanceId: materialId, renderStates } = material; // Union render global macro and material self macro ShaderMacroCollection.unionCollection(renderer._globalShaderMacro, materialData._macroCollection, compileMacros); - // TODO: Mask should not modify material's render state, will delete this code after mask refactor - if (maskType !== RenderQueueMaskType.No) { - const operation = - maskType === RenderQueueMaskType.Increment - ? StencilOperation.IncrementSaturate - : StencilOperation.DecrementSaturate; - - const { stencilState } = material.renderState; - stencilState.passOperationFront = operation; - stencilState.passOperationBack = operation; - } - for (let j = 0, m = shaderPasses.length; j < m; j++) { const shaderPass = shaderPasses[j]; if (shaderPass.getTagValue(pipelineStageKey) !== pipelineStageTagValue) { continue; } - if ((shaderPass._renderState ?? renderStates[j]).renderQueueType !== renderQueueType) { - continue; + if (!needMaskType) { + if ((shaderPass._renderState ?? renderStates[j]).renderQueueType !== renderQueueType) { + continue; + } } const program = shaderPass._getShaderProgram(engine, compileMacros); @@ -168,9 +176,9 @@ export class RenderQueue { engine, renderer.entity.transform._isFrontFaceInvert(), shaderPass._renderStateDataMap, - material.shaderData + material.shaderData, + customStates ); - rhi.drawPrimitive(primitive, subElement.subPrimitive, program); } } @@ -182,37 +190,4 @@ export class RenderQueue { } destroy(): void {} - - private _sort(compareFunc: Function): void { - Utils._quickSort(this.elements, 0, this.elements.length, compareFunc); - } - - private _batch(batcherManager: BatcherManager): void { - batcherManager.batch(this); - } - - private _drawMask(context: RenderContext, pipelineStageTagValue: string, master: SubRenderElement): void { - const incrementMaskQueue = MaskManager.getMaskIncrementRenderQueue(); - incrementMaskQueue.renderQueueType = this.renderQueueType; - incrementMaskQueue.clear(); - - const decrementMaskQueue = MaskManager.getMaskDecrementRenderQueue(); - decrementMaskQueue.renderQueueType = this.renderQueueType; - decrementMaskQueue.clear(); - - const camera = context.camera; - const engine = camera.engine; - camera.scene._maskManager.buildMaskRenderElement(master, incrementMaskQueue, decrementMaskQueue); - - incrementMaskQueue._batch(engine._batcherManager); - incrementMaskQueue.render(context, pipelineStageTagValue, RenderQueueMaskType.Increment); - decrementMaskQueue._batch(engine._batcherManager); - decrementMaskQueue.render(context, pipelineStageTagValue, RenderQueueMaskType.Decrement); - } -} - -enum RenderQueueMaskType { - No, - Increment, - Decrement } diff --git a/packages/core/src/RenderPipeline/enums/RenderQueueMaskType.ts b/packages/core/src/RenderPipeline/enums/RenderQueueMaskType.ts new file mode 100644 index 0000000000..fee0a45b64 --- /dev/null +++ b/packages/core/src/RenderPipeline/enums/RenderQueueMaskType.ts @@ -0,0 +1,9 @@ +/** + * @internal + * Render queue mask type. + */ +export enum RenderQueueMaskType { + No, + Increment, + Decrement +} diff --git a/packages/core/src/Renderer.ts b/packages/core/src/Renderer.ts index b5f81682d5..4ee92e899a 100644 --- a/packages/core/src/Renderer.ts +++ b/packages/core/src/Renderer.ts @@ -1,6 +1,5 @@ // @ts-ignore import { BoundingBox, Matrix, Vector3, Vector4 } from "@galacean/engine-math"; -import { SpriteMaskLayer } from "./2d"; import { SpriteMaskInteraction } from "./2d/enums/SpriteMaskInteraction"; import { Component } from "./Component"; import { DependentMode, dependentComponents } from "./ComponentsDependencies"; @@ -10,6 +9,7 @@ import { SubRenderElement } from "./RenderPipeline/SubRenderElement"; import { Transform, TransformModifyFlags } from "./Transform"; import { assignmentClone, deepClone, ignoreClone } from "./clone/CloneManager"; import { IComponentCustomClone } from "./clone/ComponentCloner"; +import { SpriteMaskLayer } from "./enums/SpriteMaskLayer"; import { Material } from "./material"; import { ShaderMacro, ShaderProperty } from "./shader"; import { ShaderData } from "./shader/ShaderData"; diff --git a/packages/core/src/Scene.ts b/packages/core/src/Scene.ts index ecbcf66f28..fade2c1c0b 100644 --- a/packages/core/src/Scene.ts +++ b/packages/core/src/Scene.ts @@ -3,6 +3,7 @@ import { Background } from "./Background"; import { ComponentsManager } from "./ComponentsManager"; import { Engine } from "./Engine"; import { Entity } from "./Entity"; +import { MaskManager } from "./RenderPipeline/MaskManager"; import { SceneManager } from "./SceneManager"; import { EngineObject, Logger } from "./base"; import { ActiveChangeFlag } from "./enums/ActiveChangeFlag"; @@ -19,7 +20,6 @@ import { ShaderDataGroup } from "./shader/enums/ShaderDataGroup"; import { ShadowCascadesMode } from "./shadow/enum/ShadowCascadesMode"; import { ShadowResolution } from "./shadow/enum/ShadowResolution"; import { ShadowType } from "./shadow/enum/ShadowType"; -import { MaskManager } from "./RenderPipeline/MaskManager"; /** * Scene. diff --git a/packages/core/src/2d/enums/SpriteMaskLayer.ts b/packages/core/src/enums/SpriteMaskLayer.ts similarity index 95% rename from packages/core/src/2d/enums/SpriteMaskLayer.ts rename to packages/core/src/enums/SpriteMaskLayer.ts index 30d89c6ea9..8e10c6c861 100644 --- a/packages/core/src/2d/enums/SpriteMaskLayer.ts +++ b/packages/core/src/enums/SpriteMaskLayer.ts @@ -67,5 +67,7 @@ export enum SpriteMaskLayer { /** Mask layer 31. */ Layer31 = 0x80000000, /** All mask layers. */ - Everything = 0xffffffff + Everything = 0xffffffff, + /** None mask layer. */ + Nothing = 0x0 } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7d4380f068..2bdbe3633c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -42,6 +42,7 @@ export { ReplacementFailureStrategy } from "./enums/ReplacementFailureStrategy"; export { Downsampling } from "./enums/Downsampling"; export { ColorSpace } from "./enums/ColorSpace"; export { BackgroundTextureFillMode } from "./enums/BackgroundTextureFillMode"; +export { SpriteMaskLayer } from "./enums/SpriteMaskLayer"; export { XRManager } from "./xr/XRManager"; export * from "./utils/index"; export * from "./input/index"; diff --git a/packages/core/src/shader/state/BlendState.ts b/packages/core/src/shader/state/BlendState.ts index 7257e8cb06..89e21507e2 100644 --- a/packages/core/src/shader/state/BlendState.ts +++ b/packages/core/src/shader/state/BlendState.ts @@ -1,5 +1,6 @@ import { IHardwareRenderer } from "@galacean/engine-design"; import { Color } from "@galacean/engine-math"; +import { RenderStateElementMap } from "../../BasicResources"; import { GLCapabilityType } from "../../base/Constant"; import { deepClone } from "../../clone/CloneManager"; import { ShaderData } from "../ShaderData"; @@ -148,15 +149,19 @@ export class BlendState { * @internal * Apply the current blend state by comparing with the last blend state. */ - _apply(hardwareRenderer: IHardwareRenderer, lastRenderState: RenderState): void { - this._platformApply(hardwareRenderer, lastRenderState.blendState); + _apply( + hardwareRenderer: IHardwareRenderer, + lastRenderState: RenderState, + customStates?: RenderStateElementMap + ): void { + this._platformApply(hardwareRenderer, lastRenderState.blendState, customStates); } - private _platformApply(rhi: IHardwareRenderer, lastState: BlendState): void { + private _platformApply(rhi: IHardwareRenderer, lastState: BlendState, customStates?: RenderStateElementMap): void { const gl = rhi.gl; const lastTargetBlendState = lastState.targetBlendState; - const { + let { enabled, colorBlendOperation, alphaBlendOperation, @@ -167,6 +172,11 @@ export class BlendState { colorWriteMask } = this.targetBlendState; + if (customStates) { + const colorWriteMaskState = customStates[RenderStateElementKey.BlendStateColorWriteMask0]; + colorWriteMaskState !== undefined && (colorWriteMask = colorWriteMaskState); + } + if (enabled !== lastTargetBlendState.enabled) { if (enabled) { gl.enable(gl.BLEND); diff --git a/packages/core/src/shader/state/DepthState.ts b/packages/core/src/shader/state/DepthState.ts index 3e8a141eb6..ea4f1f0990 100644 --- a/packages/core/src/shader/state/DepthState.ts +++ b/packages/core/src/shader/state/DepthState.ts @@ -1,4 +1,5 @@ import { IHardwareRenderer } from "@galacean/engine-design"; +import { RenderStateElementMap } from "../../BasicResources"; import { ShaderData } from "../ShaderData"; import { ShaderProperty } from "../ShaderProperty"; import { CompareFunction } from "../enums/CompareFunction"; @@ -65,13 +66,22 @@ export class DepthState { * @internal * Apply the current depth state by comparing with the last depth state. */ - _apply(hardwareRenderer: IHardwareRenderer, lastRenderState: RenderState): void { - this._platformApply(hardwareRenderer, lastRenderState.depthState); + _apply( + hardwareRenderer: IHardwareRenderer, + lastRenderState: RenderState, + customStates?: RenderStateElementMap + ): void { + this._platformApply(hardwareRenderer, lastRenderState.depthState, customStates); } - private _platformApply(rhi: IHardwareRenderer, lastState: DepthState): void { + private _platformApply(rhi: IHardwareRenderer, lastState: DepthState, customStates?: RenderStateElementMap): void { const gl = rhi.gl; - const { enabled, compareFunction, writeEnabled } = this; + let { enabled, compareFunction, writeEnabled } = this; + + if (customStates) { + const enabledState = customStates[RenderStateElementKey.DepthStateEnabled]; + enabledState !== undefined && (enabled = enabledState); + } if (enabled != lastState.enabled) { if (enabled) { diff --git a/packages/core/src/shader/state/RasterState.ts b/packages/core/src/shader/state/RasterState.ts index ae56683b65..575f236581 100644 --- a/packages/core/src/shader/state/RasterState.ts +++ b/packages/core/src/shader/state/RasterState.ts @@ -1,4 +1,5 @@ import { IHardwareRenderer } from "@galacean/engine-design"; +import { RenderStateElementMap } from "../../BasicResources"; import { ShaderData } from "../ShaderData"; import { ShaderProperty } from "../ShaderProperty"; import { CullMode } from "../enums/CullMode"; @@ -44,13 +45,28 @@ export class RasterState { /** * @internal */ - _apply(hardwareRenderer: IHardwareRenderer, lastRenderState: RenderState, frontFaceInvert: boolean): void { - this._platformApply(hardwareRenderer, lastRenderState.rasterState, frontFaceInvert); + _apply( + hardwareRenderer: IHardwareRenderer, + lastRenderState: RenderState, + frontFaceInvert: boolean, + customStates?: RenderStateElementMap + ): void { + this._platformApply(hardwareRenderer, lastRenderState.rasterState, frontFaceInvert, customStates); } - private _platformApply(rhi: IHardwareRenderer, lastState: RasterState, frontFaceInvert: boolean): void { + private _platformApply( + rhi: IHardwareRenderer, + lastState: RasterState, + frontFaceInvert: boolean, + customStates?: RenderStateElementMap + ): void { const gl = rhi.gl; - const { cullMode, depthBias, slopeScaledDepthBias } = this; + let { cullMode, depthBias, slopeScaledDepthBias } = this; + + if (customStates) { + const cullModeState = customStates[RenderStateElementKey.RasterStateCullMode]; + cullModeState !== undefined && (cullMode = cullModeState); + } const cullFaceEnable = cullMode !== CullMode.Off; if (cullFaceEnable !== lastState._cullFaceEnable) { diff --git a/packages/core/src/shader/state/RenderState.ts b/packages/core/src/shader/state/RenderState.ts index 27af040234..c01fdadd3e 100644 --- a/packages/core/src/shader/state/RenderState.ts +++ b/packages/core/src/shader/state/RenderState.ts @@ -1,4 +1,5 @@ import { ShaderData, ShaderProperty } from ".."; +import { RenderStateElementMap } from "../../BasicResources"; import { Engine } from "../../Engine"; import { deepClone } from "../../clone/CloneManager"; import { RenderQueueType } from "../enums/RenderQueueType"; @@ -35,20 +36,22 @@ export class RenderState { engine: Engine, frontFaceInvert: boolean, renderStateDataMap: Record, - shaderData: ShaderData + shaderData: ShaderData, + customRenderStates?: RenderStateElementMap ): void { // @todo: Should merge when we can delete material render state renderStateDataMap && this._applyStatesByShaderData(renderStateDataMap, shaderData); const hardwareRenderer = engine._hardwareRenderer; const lastRenderState = engine._lastRenderState; const context = engine._renderContext; - this.blendState._apply(hardwareRenderer, lastRenderState); - this.depthState._apply(hardwareRenderer, lastRenderState); - this.stencilState._apply(hardwareRenderer, lastRenderState); + this.blendState._apply(hardwareRenderer, lastRenderState, customRenderStates); + this.depthState._apply(hardwareRenderer, lastRenderState, customRenderStates); + this.stencilState._apply(hardwareRenderer, lastRenderState, customRenderStates); this.rasterState._apply( hardwareRenderer, lastRenderState, - context.flipProjection ? !frontFaceInvert : frontFaceInvert + context.flipProjection ? !frontFaceInvert : frontFaceInvert, + customRenderStates ); } diff --git a/packages/core/src/shader/state/StencilState.ts b/packages/core/src/shader/state/StencilState.ts index 8d495fc3f7..b64c0cda6b 100644 --- a/packages/core/src/shader/state/StencilState.ts +++ b/packages/core/src/shader/state/StencilState.ts @@ -1,4 +1,5 @@ import { IHardwareRenderer } from "@galacean/engine-design"; +import { RenderStateElementMap } from "../../BasicResources"; import { ShaderData } from "../ShaderData"; import { ShaderProperty } from "../ShaderProperty"; import { CompareFunction } from "../enums/CompareFunction"; @@ -150,13 +151,17 @@ export class StencilState { /** * @internal */ - _apply(hardwareRenderer: IHardwareRenderer, lastRenderState: RenderState): void { - this._platformApply(hardwareRenderer, lastRenderState.stencilState); + _apply( + hardwareRenderer: IHardwareRenderer, + lastRenderState: RenderState, + customStates?: RenderStateElementMap + ): void { + this._platformApply(hardwareRenderer, lastRenderState.stencilState, customStates); } - private _platformApply(rhi: IHardwareRenderer, lastState: StencilState): void { + private _platformApply(rhi: IHardwareRenderer, lastState: StencilState, customStates?: RenderStateElementMap): void { const gl = rhi.gl; - const { + let { enabled, referenceValue, mask, @@ -171,6 +176,31 @@ export class StencilState { writeMask } = this; + if (customStates) { + const enabledState = customStates[RenderStateElementKey.StencilStateEnabled]; + enabledState !== undefined && (enabled = enabledState); + const writeMaskState = customStates[RenderStateElementKey.StencilStateWriteMask]; + writeMaskState !== undefined && (writeMask = writeMaskState); + const referenceValueState = customStates[RenderStateElementKey.StencilStateReferenceValue]; + referenceValueState !== undefined && (referenceValue = referenceValueState); + const compareFunctionFrontState = customStates[RenderStateElementKey.StencilStateCompareFunctionFront]; + compareFunctionFrontState !== undefined && (compareFunctionFront = compareFunctionFrontState); + const compareFunctionBackState = customStates[RenderStateElementKey.StencilStateCompareFunctionBack]; + compareFunctionBackState !== undefined && (compareFunctionBack = compareFunctionBackState); + const passOperationFrontState = customStates[RenderStateElementKey.StencilStatePassOperationFront]; + passOperationFrontState !== undefined && (passOperationFront = passOperationFrontState); + const passOperationBackState = customStates[RenderStateElementKey.StencilStatePassOperationBack]; + passOperationBackState !== undefined && (passOperationBack = passOperationBackState); + const failOperationFrontState = customStates[RenderStateElementKey.StencilStateFailOperationFront]; + failOperationFrontState !== undefined && (failOperationFront = failOperationFrontState); + const failOperationBackState = customStates[RenderStateElementKey.StencilStateFailOperationBack]; + failOperationBackState !== undefined && (failOperationBack = failOperationBackState); + const zFailOperationFrontState = customStates[RenderStateElementKey.StencilStateZFailOperationFront]; + zFailOperationFrontState !== undefined && (zFailOperationFront = zFailOperationFrontState); + const zFailOperationBackState = customStates[RenderStateElementKey.StencilStateZFailOperationBack]; + zFailOperationBackState !== undefined && (zFailOperationBack = zFailOperationBackState); + } + if (enabled != lastState.enabled) { if (enabled) { gl.enable(gl.STENCIL_TEST); @@ -200,11 +230,11 @@ export class StencilState { referenceValue, mask ); - lastState.compareFunctionBack = this.compareFunctionBack; + lastState.compareFunctionBack = compareFunctionBack; } if (referenceOrMaskChange) { - lastState.referenceValue = this.referenceValue; - lastState.mask = this.mask; + lastState.referenceValue = referenceValue; + lastState.mask = mask; } // apply stencil operation. diff --git a/packages/core/src/shadow/CascadedShadowCasterPass.ts b/packages/core/src/shadow/CascadedShadowCasterPass.ts index 2bd6fb6a49..d1f79b6248 100644 --- a/packages/core/src/shadow/CascadedShadowCasterPass.ts +++ b/packages/core/src/shadow/CascadedShadowCasterPass.ts @@ -2,7 +2,7 @@ import { Color, MathUtil, Matrix, Vector2, Vector3, Vector4 } from "@galacean/en import { Camera } from "../Camera"; import { PipelinePass } from "../RenderPipeline/PipelinePass"; import { PipelineUtils } from "../RenderPipeline/PipelineUtils"; -import { RenderContext, ContextRendererUpdateFlag } from "../RenderPipeline/RenderContext"; +import { ContextRendererUpdateFlag, RenderContext } from "../RenderPipeline/RenderContext"; import { RenderQueue } from "../RenderPipeline/RenderQueue"; import { PipelineStage } from "../RenderPipeline/index"; import { GLCapabilityType } from "../base/Constant";