diff --git a/packages/core/src/Camera.ts b/packages/core/src/Camera.ts index 1ff9b08e3f..19edf40fae 100644 --- a/packages/core/src/Camera.ts +++ b/packages/core/src/Camera.ts @@ -15,6 +15,7 @@ import { CameraType } from "./enums/CameraType"; import { DepthTextureMode } from "./enums/DepthTextureMode"; import { Downsampling } from "./enums/Downsampling"; import { MSAASamples } from "./enums/MSAASamples"; +import { ReplacementFailureStrategy } from "./enums/ReplacementFailureStrategy"; import { Shader } from "./shader/Shader"; import { ShaderData } from "./shader/ShaderData"; import { ShaderMacroCollection } from "./shader/ShaderMacroCollection"; @@ -103,6 +104,8 @@ export class Camera extends Component { /** @internal */ _replacementSubShaderTag: ShaderTagKey = null; /** @internal */ + _replacementFailureStrategy: ReplacementFailureStrategy = null; + /** @internal */ @ignoreClone _cameraIndex: number = -1; @@ -584,6 +587,7 @@ export class Camera extends Component { context.virtualCamera = virtualCamera; context.replacementShader = this._replacementShader; context.replacementTag = this._replacementSubShaderTag; + context.replacementFailureStrategy = this._replacementFailureStrategy; // compute cull frustum. if (this.enableFrustumCulling && this._frustumChangeFlag.flag) { @@ -616,28 +620,35 @@ export class Camera extends Component { * Set the replacement shader. * @param shader - Replacement shader * @param replacementTagName - Sub shader tag name + * @param failureStrategy - Replacement failure strategy, @defaultValue `ReplacementFailureStrategy.KeepOriginalShader` * * @remarks * If replacementTagName is not specified, the first sub shader will be replaced. - * If replacementTagName is specified, the replacement shader will find the first sub shader which has the same tag value get by replacementTagKey. + * If replacementTagName is specified, the replacement shader will find the first sub shader which has the same tag value get by replacementTagKey. If failed to find the sub shader, the strategy will be determined by failureStrategy. */ - setReplacementShader(shader: Shader, replacementTagName?: string); + setReplacementShader(shader: Shader, replacementTagName?: string, failureStrategy?: ReplacementFailureStrategy); /** * Set the replacement shader. * @param shader - Replacement shader * @param replacementTag - Sub shader tag + * @param failureStrategy - Replacement failure strategy, @defaultValue `ReplacementFailureStrategy.KeepOriginalShader` * * @remarks * If replacementTag is not specified, the first sub shader will be replaced. - * If replacementTag is specified, the replacement shader will find the first sub shader which has the same tag value get by replacementTagKey. + * If replacementTag is specified, the replacement shader will find the first sub shader which has the same tag value get by replacementTagKey. If failed to find the sub shader, the strategy will be determined by failureStrategy. */ - setReplacementShader(shader: Shader, replacementTag?: ShaderTagKey); + setReplacementShader(shader: Shader, replacementTag?: ShaderTagKey, failureStrategy?: ReplacementFailureStrategy); - setReplacementShader(shader: Shader, replacementTag?: string | ShaderTagKey): void { + setReplacementShader( + shader: Shader, + replacementTag?: string | ShaderTagKey, + failureStrategy: ReplacementFailureStrategy = ReplacementFailureStrategy.KeepOriginalShader + ): void { this._replacementShader = shader; this._replacementSubShaderTag = typeof replacementTag === "string" ? ShaderTagKey.getByName(replacementTag) : replacementTag; + this._replacementFailureStrategy = failureStrategy; } /** @@ -646,6 +657,7 @@ export class Camera extends Component { resetReplacementShader(): void { this._replacementShader = null; this._replacementSubShaderTag = null; + this._replacementFailureStrategy = null; } /** diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index 41d3dcddb9..9aa4d9454f 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -8,6 +8,7 @@ import { BackgroundMode } from "../enums/BackgroundMode"; import { BackgroundTextureFillMode } from "../enums/BackgroundTextureFillMode"; import { CameraClearFlags } from "../enums/CameraClearFlags"; import { DepthTextureMode } from "../enums/DepthTextureMode"; +import { ReplacementFailureStrategy } from "../enums/ReplacementFailureStrategy"; import { Shader } from "../shader/Shader"; import { ShaderPass } from "../shader/ShaderPass"; import { RenderQueueType } from "../shader/enums/RenderQueueType"; @@ -211,13 +212,16 @@ export class BasicRenderPipeline { const replacementSubShaders = replacementShader.subShaders; const { replacementTag } = context; if (replacementTag) { + const materialSubShaderTagValue = materialSubShader.getTagValue(replacementTag); for (let i = 0, n = replacementSubShaders.length; i < n; i++) { const subShader = replacementSubShaders[i]; - if (subShader.getTagValue(replacementTag) === materialSubShader.getTagValue(replacementTag)) { + if (subShader.getTagValue(replacementTag) === materialSubShaderTagValue) { this.pushRenderDataWithShader(context, data, subShader.passes, renderStates); - break; + return; } } + context.replacementFailureStrategy === ReplacementFailureStrategy.KeepOriginalShader && + this.pushRenderDataWithShader(context, data, materialSubShader.passes, renderStates); } else { this.pushRenderDataWithShader(context, data, replacementSubShaders[0].passes, renderStates); } diff --git a/packages/core/src/RenderPipeline/RenderContext.ts b/packages/core/src/RenderPipeline/RenderContext.ts index fa7a182016..fc3c8d45ed 100644 --- a/packages/core/src/RenderPipeline/RenderContext.ts +++ b/packages/core/src/RenderPipeline/RenderContext.ts @@ -1,6 +1,7 @@ import { Matrix, Vector4 } from "@galacean/engine-math"; import { Camera } from "../Camera"; import { VirtualCamera } from "../VirtualCamera"; +import { ReplacementFailureStrategy } from "../enums/ReplacementFailureStrategy"; import { Shader, ShaderProperty } from "../shader"; import { ShaderTagKey } from "../shader/ShaderTagKey"; @@ -28,6 +29,7 @@ export class RenderContext { replacementShader: Shader; replacementTag: ShaderTagKey; + replacementFailureStrategy: ReplacementFailureStrategy; flipProjection = false; viewMatrix: Matrix; diff --git a/packages/core/src/enums/ReplacementFailureStrategy.ts b/packages/core/src/enums/ReplacementFailureStrategy.ts new file mode 100644 index 0000000000..d06d3c8f9d --- /dev/null +++ b/packages/core/src/enums/ReplacementFailureStrategy.ts @@ -0,0 +1,9 @@ +/** + * The strategy to use when a shader replacement fails. + */ +export enum ReplacementFailureStrategy { + /** Keep the original shader. */ + KeepOriginalShader, + /** Do not render. */ + DoNotRender +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4dc3fb50b7..9e826f27c3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -36,6 +36,7 @@ export { FogMode } from "./enums/FogMode"; export { CameraClearFlags } from "./enums/CameraClearFlags"; export { CameraType } from "./enums/CameraType"; export { MSAASamples } from "./enums/MSAASamples"; +export { ReplacementFailureStrategy } from "./enums/ReplacementFailureStrategy"; export { Downsampling } from "./enums/Downsampling"; export { ColorSpace } from "./enums/ColorSpace"; export { BackgroundTextureFillMode } from "./enums/BackgroundTextureFillMode"; diff --git a/tests/src/core/Camera.test.ts b/tests/src/core/Camera.test.ts index 4f2701aa74..4d8eabc946 100644 --- a/tests/src/core/Camera.test.ts +++ b/tests/src/core/Camera.test.ts @@ -1,4 +1,4 @@ -import { Camera, CameraClearFlags, Entity, Layer } from "@galacean/engine-core"; +import { Camera, CameraClearFlags, Entity, Layer, ReplacementFailureStrategy, Shader } from "@galacean/engine-core"; import { Matrix, Ray, Vector2, Vector3, Vector4 } from "@galacean/engine-math"; import { WebGLEngine } from "@galacean/engine-rhi-webgl"; import { expect } from "chai"; @@ -78,11 +78,34 @@ describe("camera test", function () { camera.orthographicSize = expectedOrthographicSize; expect(camera.orthographicSize).to.eq(expectedOrthographicSize); camera.orthographicSize = orthographicSize; + const testVS = ` + void main() { + gl_Position = vec4(1.0, 1.0, 1.0, 1.0); + }`; + + const testFS = ` + void main() { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); + } + `; + + const shader = Shader.create("TestReplaceShader", testVS, testFS); // Test ReplacementShader + camera.setReplacementShader(shader, "CanReplace"); + expect(camera["_replacementShader"]).to.eq(shader); + expect(camera["_replacementSubShaderTag"].name).to.eq("CanReplace"); + expect(camera["_replacementFailureStrategy"]).to.eq(ReplacementFailureStrategy.KeepOriginalShader); + + camera.setReplacementShader(shader, "CanReplace", ReplacementFailureStrategy.DoNotRender); + expect(camera["_replacementShader"]).to.eq(shader); + expect(camera["_replacementSubShaderTag"].name).to.eq("CanReplace"); + expect(camera["_replacementFailureStrategy"]).to.eq(ReplacementFailureStrategy.DoNotRender); + camera.resetReplacementShader(); expect(camera["_replacementShader"]).to.eq(null); expect(camera["_replacementSubShaderTag"]).to.eq(null); + expect(camera["_replacementFailureStrategy"]).to.eq(null); }); it("static void function", () => {