/// module BABYLON { export class StandardRenderingPipeline extends PostProcessRenderPipeline implements IDisposable, IAnimatable { /** * Public members */ // Post-processes public originalPostProcess: PostProcess; public downSampleX4PostProcess: PostProcess = null; public brightPassPostProcess: PostProcess = null; public gaussianBlurHPostProcesses: PostProcess[] = []; public gaussianBlurVPostProcesses: PostProcess[] = []; public textureAdderPostProcess: PostProcess = null; public textureAdderFinalPostProcess: PostProcess = null; public lensFlareFinalPostProcess: PostProcess = null; public lensFlarePostProcess: PostProcess = null; public lensFlareComposePostProcess: PostProcess = null; public depthOfFieldPostProcess: PostProcess = null; // Values @serialize() public brightThreshold: number = 1.0; @serialize() public blurWidth: number = 2.0; @serialize() public horizontalBlur: boolean = false; @serialize() public gaussianCoefficient: number = 0.25; @serialize() public gaussianMean: number = 1.0; @serialize() public gaussianStandardDeviation: number = 1.0; @serialize() public exposure: number = 1.0; @serializeAsTexture("lensTexture") public lensTexture: Texture = null; @serializeAsTexture("lensColorTexture") public lensColorTexture: Texture = null; @serialize() public lensFlareStrength: number = 20.0; @serialize() public lensFlareGhostDispersal: number = 1.4; @serialize() public lensFlareHaloWidth: number = 0.7; @serialize() public lensFlareDistortionStrength: number = 16.0; @serializeAsTexture("lensStarTexture") public lensStarTexture: Texture = null; @serializeAsTexture("lensFlareDirtTexture") public lensFlareDirtTexture: Texture = null; @serialize() public depthOfFieldDistance: number = 10.0; @serialize() public depthOfFieldBlurWidth: number = 2.0; // IAnimatable public animations: Animation[] = []; /** * Private members */ private _scene: Scene; private _depthRenderer: DepthRenderer = null; private _currentDepthOfFieldSource: PostProcess = null; // Getters and setters private _depthOfFieldEnabled: boolean = true; private _lensFlareEnabled: boolean = true; public set DepthOfFieldEnabled(enabled: boolean) { var blurIndex = this.gaussianBlurHPostProcesses.length - 1; if (enabled && !this._depthOfFieldEnabled) { this._scene.postProcessRenderPipelineManager.enableEffectInPipeline(this._name, "HDRGaussianBlurH" + blurIndex, this._scene.cameras); this._scene.postProcessRenderPipelineManager.enableEffectInPipeline(this._name, "HDRGaussianBlurV" + blurIndex, this._scene.cameras); this._scene.postProcessRenderPipelineManager.enableEffectInPipeline(this._name, "HDRDepthOfField", this._scene.cameras); this._depthRenderer = this._scene.enableDepthRenderer(); } else if (!enabled && this._depthOfFieldEnabled) { this._scene.postProcessRenderPipelineManager.disableEffectInPipeline(this._name, "HDRGaussianBlurH" + blurIndex, this._scene.cameras); this._scene.postProcessRenderPipelineManager.disableEffectInPipeline(this._name, "HDRGaussianBlurV" + blurIndex, this._scene.cameras); this._scene.postProcessRenderPipelineManager.disableEffectInPipeline(this._name, "HDRDepthOfField", this._scene.cameras); } this._depthOfFieldEnabled = enabled; } @serialize() public get DepthOfFieldEnabled(): boolean { return this._depthOfFieldEnabled; } public set LensFlareEnabled(enabled: boolean) { var blurIndex = this.gaussianBlurHPostProcesses.length - 2; if (enabled && !this._lensFlareEnabled) { this._scene.postProcessRenderPipelineManager.enableEffectInPipeline(this._name, "HDRLensFlare", this._scene.cameras); this._scene.postProcessRenderPipelineManager.enableEffectInPipeline(this._name, "HDRLensFlareShift", this._scene.cameras); this._scene.postProcessRenderPipelineManager.enableEffectInPipeline(this._name, "HDRGaussianBlurH" + blurIndex, this._scene.cameras); this._scene.postProcessRenderPipelineManager.enableEffectInPipeline(this._name, "HDRGaussianBlurV" + blurIndex, this._scene.cameras); this._scene.postProcessRenderPipelineManager.enableEffectInPipeline(this._name, "HDRLensFlareCompose", this._scene.cameras); this._setDepthOfFieldSavePostProcess("HDRPostLensFlareDepthOfFieldSource"); } else if (!enabled && this._lensFlareEnabled) { this._scene.postProcessRenderPipelineManager.disableEffectInPipeline(this._name, "HDRLensFlare", this._scene.cameras); this._scene.postProcessRenderPipelineManager.disableEffectInPipeline(this._name, "HDRLensFlareShift", this._scene.cameras); this._scene.postProcessRenderPipelineManager.disableEffectInPipeline(this._name, "HDRGaussianBlurH" + blurIndex, this._scene.cameras); this._scene.postProcessRenderPipelineManager.disableEffectInPipeline(this._name, "HDRGaussianBlurV" + blurIndex, this._scene.cameras); this._scene.postProcessRenderPipelineManager.disableEffectInPipeline(this._name, "HDRLensFlareCompose", this._scene.cameras); this._setDepthOfFieldSavePostProcess("HDRBaseDepthOfFieldSource"); } this._lensFlareEnabled = enabled; } @serialize() public get LensFlareEnabled(): boolean { return this._lensFlareEnabled; } /** * @constructor * @param {string} name - The rendering pipeline name * @param {BABYLON.Scene} scene - The scene linked to this pipeline * @param {any} ratio - The size of the postprocesses (0.5 means that your postprocess will have a width = canvas.width 0.5 and a height = canvas.height 0.5) * @param {BABYLON.PostProcess} originalPostProcess - the custom original color post-process. Must be "reusable". Can be null. * @param {BABYLON.Camera[]} cameras - The array of cameras that the rendering pipeline will be attached to */ constructor(name: string, scene: Scene, ratio: number, originalPostProcess: PostProcess = null, cameras?: Camera[]) { super(scene.getEngine(), name); // Initialize this._scene = scene; // Create pass post-processe if (!originalPostProcess) { this.originalPostProcess = new PostProcess("HDRPass", "standard", [], [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define PASS_POST_PROCESS", Engine.TEXTURETYPE_FLOAT); } else { this.originalPostProcess = originalPostProcess; } this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRPassPostProcess", () => { return this.originalPostProcess; }, true)); // Create down sample X4 post-process this._createDownSampleX4PostProcess(scene, ratio / 2); // Create bright pass post-process this._createBrightPassPostProcess(scene, ratio / 2); // Create gaussian blur post-processes (down sampling blurs) this._createGaussianBlurPostProcesses(scene, ratio / 2, 0); this._createGaussianBlurPostProcesses(scene, ratio / 4, 1); this._createGaussianBlurPostProcesses(scene, ratio / 8, 2); this._createGaussianBlurPostProcesses(scene, ratio / 16, 3); // Create texture adder post-process this._createTextureAdderPostProcess(scene, ratio); // Create depth-of-field source post-process this.textureAdderFinalPostProcess = new PostProcess("HDRDepthOfFieldSource", "standard", [], [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define PASS_POST_PROCESS", Engine.TEXTURETYPE_UNSIGNED_INT); this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRBaseDepthOfFieldSource", () => { return this.textureAdderFinalPostProcess; }, true)); // Create lens flare post-process this._createLensFlarePostProcess(scene, ratio); // Create depth-of-field source post-process post lens-flare and disable it now this.lensFlareFinalPostProcess = new PostProcess("HDRPostLensFlareDepthOfFieldSource", "standard", [], [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define PASS_POST_PROCESS", Engine.TEXTURETYPE_UNSIGNED_INT); this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRPostLensFlareDepthOfFieldSource", () => { return this.lensFlareFinalPostProcess; }, true)); // Create gaussian blur used by depth-of-field this._createGaussianBlurPostProcesses(scene, ratio / 2, 5, "depthOfFieldBlurWidth"); // Create depth-of-field post-process this._createDepthOfFieldPostProcess(scene, ratio); // Finish scene.postProcessRenderPipelineManager.addPipeline(this); if (cameras !== null) { scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline(name, cameras); } // Deactivate this.LensFlareEnabled = false; this.DepthOfFieldEnabled = false; } // Sets depth-of-field save post-process private _setDepthOfFieldSavePostProcess(name: string): void { this._scene.postProcessRenderPipelineManager.disableEffectInPipeline(this._name, "HDRPostLensFlareDepthOfFieldSource", this._scene.cameras); this._scene.postProcessRenderPipelineManager.enableEffectInPipeline(this._name, name, this._scene.cameras); switch (name) { case "HDRBaseDepthOfFieldSource": this._currentDepthOfFieldSource = this.textureAdderFinalPostProcess; break; case "HDRPostLensFlareDepthOfFieldSource": this._currentDepthOfFieldSource = this.lensFlareFinalPostProcess; break; default: break; } } // Down Sample X4 Post-Processs private _createDownSampleX4PostProcess(scene: Scene, ratio: number): void { var downSampleX4Offsets = new Array(32); this.downSampleX4PostProcess = new PostProcess("HDRDownSampleX4", "standard", ["dsOffsets"], [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define DOWN_SAMPLE_X4", Engine.TEXTURETYPE_UNSIGNED_INT); this.downSampleX4PostProcess.onApply = (effect: Effect) => { var id = 0; for (var i = -2; i < 2; i++) { for (var j = -2; j < 2; j++) { downSampleX4Offsets[id] = (i + 0.5) * (1.0 / this.downSampleX4PostProcess.width); downSampleX4Offsets[id + 1] = (j + 0.5) * (1.0 / this.downSampleX4PostProcess.height); id += 2; } } effect.setArray2("dsOffsets", downSampleX4Offsets); }; // Add to pipeline this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRDownSampleX4", () => { return this.downSampleX4PostProcess; }, true)); } // Brightpass Post-Process private _createBrightPassPostProcess(scene: Scene, ratio: number): void { var brightOffsets = new Array(8); this.brightPassPostProcess = new PostProcess("HDRBrightPass", "standard", ["dsOffsets", "brightThreshold"], [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define BRIGHT_PASS", Engine.TEXTURETYPE_UNSIGNED_INT); this.brightPassPostProcess.onApply = (effect: Effect) => { var sU = (1.0 / this.brightPassPostProcess.width); var sV = (1.0 / this.brightPassPostProcess.height); brightOffsets[0] = -0.5 * sU; brightOffsets[1] = 0.5 * sV; brightOffsets[2] = 0.5 * sU; brightOffsets[3] = 0.5 * sV; brightOffsets[4] = -0.5 * sU; brightOffsets[5] = -0.5 * sV; brightOffsets[6] = 0.5 * sU; brightOffsets[7] = -0.5 * sV; effect.setArray2("dsOffsets", brightOffsets); effect.setFloat("brightThreshold", this.brightThreshold); } // Add to pipeline this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRBrightPass", () => { return this.brightPassPostProcess; }, true)); } // Create gaussian blur H&V post-processes private _createGaussianBlurPostProcesses(scene: Scene, ratio: number, indice: number, blurWidthKey: string = "blurWidth"): void { var blurOffsets = new Array(9); var blurWeights = new Array(9); var uniforms: string[] = ["blurOffsets", "blurWeights", "blurWidth"]; var callback = (height: boolean) => { return (effect: Effect) => { // Weights var x: number = 0.0; for (var i = 0; i < 9; i++) { x = (i - 4.0) / 4.0; blurWeights[i] = this.gaussianCoefficient * (1.0 / Math.sqrt(2.0 * Math.PI * this.gaussianStandardDeviation)) * Math.exp((-((x - this.gaussianMean) * (x - this.gaussianMean))) / (2.0 * this.gaussianStandardDeviation * this.gaussianStandardDeviation)); } var lastOutputDimensions: any = { width: scene.getEngine().getRenderWidth(), height: scene.getEngine().getRenderHeight() }; for (var i = 0; i < 9; i++) { var value = (i - 4.0) * (1.0 / (height === true ? lastOutputDimensions.height : lastOutputDimensions.width)); blurOffsets[i] = value; } effect.setArray("blurOffsets", blurOffsets); effect.setArray("blurWeights", blurWeights); if (height) { effect.setFloat("blurWidth", this.horizontalBlur ? 1.0 : this[blurWidthKey]); } else { effect.setFloat("blurWidth", this[blurWidthKey]); } }; }; // Create horizontal gaussian blur post-processes var gaussianBlurHPostProcess = new PostProcess("HDRGaussianBlurH_" + ratio + "_" + indice, "standard", uniforms, [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define GAUSSIAN_BLUR_H", Engine.TEXTURETYPE_UNSIGNED_INT); gaussianBlurHPostProcess.onApply = callback(false); // Create vertical gaussian blur post-process var gaussianBlurVPostProcess = new PostProcess("HDRGaussianBlurV_" + ratio + "_" + indice, "standard", uniforms, [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define GAUSSIAN_BLUR_V", Engine.TEXTURETYPE_UNSIGNED_INT); gaussianBlurVPostProcess.onApply = callback(true); // Add to pipeline this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRGaussianBlurH" + indice, () => { return gaussianBlurHPostProcess; }, true)); this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRGaussianBlurV" + indice, () => { return gaussianBlurVPostProcess; }, true)); // Finish this.gaussianBlurHPostProcesses.push(gaussianBlurHPostProcess); this.gaussianBlurVPostProcesses.push(gaussianBlurVPostProcess); } // Create texture adder post-process private _createTextureAdderPostProcess(scene: Scene, ratio: number): void { this.textureAdderPostProcess = new PostProcess("HDRTextureAdder", "standard", ["exposure"], ["otherSampler", "lensSampler"], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define TEXTURE_ADDER", Engine.TEXTURETYPE_UNSIGNED_INT); this.textureAdderPostProcess.onApply = (effect: Effect) => { effect.setTextureFromPostProcess("otherSampler", this.originalPostProcess); effect.setTexture("lensSampler", this.lensTexture); effect.setFloat("exposure", this.exposure); }; // Add to pipeline this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRTextureAdder", () => { return this.textureAdderPostProcess; }, true)); } // Create lens flare post-process private _createLensFlarePostProcess(scene: Scene, ratio: number): void { this.lensFlarePostProcess = new PostProcess("HDRLensFlare", "standard", ["strength", "ghostDispersal", "haloWidth", "resolution", "distortionStrength"], ["lensColorSampler"], ratio / 2, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define LENS_FLARE", Engine.TEXTURETYPE_UNSIGNED_INT); this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRLensFlare", () => { return this.lensFlarePostProcess; }, true)); this._createGaussianBlurPostProcesses(scene, ratio / 4, 4); this.lensFlareComposePostProcess = new PostProcess("HDRLensFlareCompose", "standard", ["lensStarMatrix"], ["otherSampler", "lensDirtSampler", "lensStarSampler"], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define LENS_FLARE_COMPOSE", Engine.TEXTURETYPE_UNSIGNED_INT); this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRLensFlareCompose", () => { return this.lensFlareComposePostProcess; }, true)); var resolution = new Vector2(0, 0); // Lens flare this.lensFlarePostProcess.onApply = (effect: Effect) => { effect.setTextureFromPostProcess("textureSampler", this.gaussianBlurHPostProcesses[0]); effect.setTexture("lensColorSampler", this.lensColorTexture); effect.setFloat("strength", this.lensFlareStrength); effect.setFloat("ghostDispersal", this.lensFlareGhostDispersal); effect.setFloat("haloWidth", this.lensFlareHaloWidth); // Shift resolution.x = this.lensFlarePostProcess.width; resolution.y = this.lensFlarePostProcess.height; effect.setVector2("resolution", resolution); effect.setFloat("distortionStrength", this.lensFlareDistortionStrength); }; // Compose var scaleBias1 = Matrix.FromValues( 2.0, 0.0, -1.0, 0.0, 0.0, 2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ); var scaleBias2 = Matrix.FromValues( 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ); this.lensFlareComposePostProcess.onApply = (effect: Effect) => { effect.setTextureFromPostProcess("otherSampler", this.textureAdderFinalPostProcess); effect.setTexture("lensDirtSampler", this.lensFlareDirtTexture); effect.setTexture("lensStarSampler", this.lensStarTexture); // Lens start rotation matrix var camerax = this._scene.activeCamera.getViewMatrix().getRow(0); var cameraz = this._scene.activeCamera.getViewMatrix().getRow(2); var camRot = Vector3.Dot(camerax.toVector3(), new Vector3(1.0, 0.0, 0.0)) + Vector3.Dot(cameraz.toVector3(), new Vector3(0.0, 0.0, 1.0)); camRot *= 4.0; var starRotation = Matrix.FromValues( Math.cos(camRot) * 0.5, -Math.sin(camRot), 0.0, 0.0, Math.sin(camRot), Math.cos(camRot) * 0.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ); var lensStarMatrix = scaleBias2.multiply(starRotation).multiply(scaleBias1); effect.setMatrix("lensStarMatrix", lensStarMatrix); }; } // Create depth-of-field post-process private _createDepthOfFieldPostProcess(scene: Scene, ratio: number): void { this.depthOfFieldPostProcess = new PostProcess("HDRDepthOfField", "standard", ["distance"], ["otherSampler", "depthSampler"], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define DEPTH_OF_FIELD", Engine.TEXTURETYPE_UNSIGNED_INT); this.depthOfFieldPostProcess.onApply = (effect: Effect) => { effect.setTextureFromPostProcess("otherSampler", this._currentDepthOfFieldSource); effect.setTexture("depthSampler", this._depthRenderer.getDepthMap()); effect.setFloat("distance", this.depthOfFieldDistance); }; // Add to pipeline this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRDepthOfField", () => { return this.depthOfFieldPostProcess; }, true)); } // Dispose public dispose(): void { for (var i = 0; i < this._scene.cameras.length; i++) { var camera = this._scene.cameras[i]; this.originalPostProcess.dispose(camera); this.downSampleX4PostProcess.dispose(camera); this.brightPassPostProcess.dispose(camera); this.textureAdderPostProcess.dispose(camera); for (var j = 0; j < this.gaussianBlurHPostProcesses.length; j++) { this.gaussianBlurHPostProcesses[j].dispose(camera); } for (var j = 0; j < this.gaussianBlurVPostProcesses.length; j++) { this.gaussianBlurVPostProcesses[j].dispose(camera); } this.textureAdderFinalPostProcess.dispose(camera); this.lensFlarePostProcess.dispose(camera); this.lensFlareComposePostProcess.dispose(camera); this.depthOfFieldPostProcess.dispose(camera); } this._scene.postProcessRenderPipelineManager.detachCamerasFromRenderPipeline(this._name, this._scene.cameras); super.dispose(); } // Serialize rendering pipeline public serialize(): any { var serializationObject = SerializationHelper.Serialize(this); serializationObject.customType = "StandardRenderingPipeline"; return serializationObject; } // Parse serialized pipeline public static Parse(source: any, scene: Scene, rootUrl: string): StandardRenderingPipeline { return SerializationHelper.Parse(() => new StandardRenderingPipeline(source._name, scene, source._ratio), source, scene, rootUrl); } } }