babylon.postProcess.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. module BABYLON {
  2. export type PostProcessOptions = { width: number, height: number };
  3. export class PostProcess {
  4. public width = -1;
  5. public height = -1;
  6. public renderTargetSamplingMode: number;
  7. public clearColor: Color4;
  8. public autoClear = true;
  9. public alphaMode = Engine.ALPHA_DISABLE;
  10. public alphaConstants: Color4;
  11. public animations = new Array<Animation>();
  12. /*
  13. Enable Pixel Perfect mode where texture is not scaled to be power of 2.
  14. Can only be used on a single postprocess or on the last one of a chain.
  15. */
  16. public enablePixelPerfectMode = false;
  17. public scaleMode = Engine.SCALEMODE_FLOOR;
  18. public alwaysForcePOT = false;
  19. public samples = 1;
  20. public adaptScaleToCurrentViewport = false;
  21. private _camera: Camera;
  22. private _scene: Scene;
  23. private _engine: Engine;
  24. private _options: number | PostProcessOptions;
  25. private _reusable = false;
  26. private _textureType: number;
  27. public _textures = new SmartArray<InternalTexture>(2);
  28. public _currentRenderTextureInd = 0;
  29. private _effect: Effect;
  30. private _samplers: string[];
  31. private _fragmentUrl: string;
  32. private _vertexUrl: string;
  33. private _parameters: string[];
  34. private _scaleRatio = new Vector2(1, 1);
  35. protected _indexParameters: any;
  36. private _shareOutputWithPostProcess: PostProcess;
  37. private _texelSize = Vector2.Zero();
  38. private _forcedOutputTexture: InternalTexture;
  39. // Events
  40. /**
  41. * An event triggered when the postprocess is activated.
  42. * @type {BABYLON.Observable}
  43. */
  44. public onActivateObservable = new Observable<Camera>();
  45. private _onActivateObserver: Nullable<Observer<Camera>>;
  46. public set onActivate(callback: Nullable<(camera: Camera) => void>) {
  47. if (this._onActivateObserver) {
  48. this.onActivateObservable.remove(this._onActivateObserver);
  49. }
  50. if (callback) {
  51. this._onActivateObserver = this.onActivateObservable.add(callback);
  52. }
  53. }
  54. /**
  55. * An event triggered when the postprocess changes its size.
  56. * @type {BABYLON.Observable}
  57. */
  58. public onSizeChangedObservable = new Observable<PostProcess>();
  59. private _onSizeChangedObserver: Nullable<Observer<PostProcess>>;
  60. public set onSizeChanged(callback: (postProcess: PostProcess) => void) {
  61. if (this._onSizeChangedObserver) {
  62. this.onSizeChangedObservable.remove(this._onSizeChangedObserver);
  63. }
  64. this._onSizeChangedObserver = this.onSizeChangedObservable.add(callback);
  65. }
  66. /**
  67. * An event triggered when the postprocess applies its effect.
  68. * @type {BABYLON.Observable}
  69. */
  70. public onApplyObservable = new Observable<Effect>();
  71. private _onApplyObserver: Nullable<Observer<Effect>>;
  72. public set onApply(callback: (effect: Effect) => void) {
  73. if (this._onApplyObserver) {
  74. this.onApplyObservable.remove(this._onApplyObserver);
  75. }
  76. this._onApplyObserver = this.onApplyObservable.add(callback);
  77. }
  78. /**
  79. * An event triggered before rendering the postprocess
  80. * @type {BABYLON.Observable}
  81. */
  82. public onBeforeRenderObservable = new Observable<Effect>();
  83. private _onBeforeRenderObserver: Nullable<Observer<Effect>>;
  84. public set onBeforeRender(callback: (effect: Effect) => void) {
  85. if (this._onBeforeRenderObserver) {
  86. this.onBeforeRenderObservable.remove(this._onBeforeRenderObserver);
  87. }
  88. this._onBeforeRenderObserver = this.onBeforeRenderObservable.add(callback);
  89. }
  90. /**
  91. * An event triggered after rendering the postprocess
  92. * @type {BABYLON.Observable}
  93. */
  94. public onAfterRenderObservable = new Observable<Effect>();
  95. private _onAfterRenderObserver: Nullable<Observer<Effect>>;
  96. public set onAfterRender(callback: (efect: Effect) => void) {
  97. if (this._onAfterRenderObserver) {
  98. this.onAfterRenderObservable.remove(this._onAfterRenderObserver);
  99. }
  100. this._onAfterRenderObserver = this.onAfterRenderObservable.add(callback);
  101. }
  102. public get outputTexture(): InternalTexture {
  103. return this._textures.data[this._currentRenderTextureInd];
  104. }
  105. public set outputTexture(value: InternalTexture) {
  106. this._forcedOutputTexture = value;
  107. }
  108. public getCamera(): Camera {
  109. return this._camera;
  110. }
  111. public get texelSize(): Vector2 {
  112. if (this._shareOutputWithPostProcess) {
  113. return this._shareOutputWithPostProcess.texelSize;
  114. }
  115. if (this._forcedOutputTexture) {
  116. this._texelSize.copyFromFloats(1.0 / this._forcedOutputTexture.width, 1.0 / this._forcedOutputTexture.height);
  117. }
  118. return this._texelSize;
  119. }
  120. constructor(public name: string, fragmentUrl: string, parameters: Nullable<string[]>, samplers: Nullable<string[]>, options: number | PostProcessOptions, camera: Nullable<Camera>,
  121. samplingMode: number = Texture.NEAREST_SAMPLINGMODE, engine?: Engine, reusable?: boolean, defines: Nullable<string> = null, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT, vertexUrl: string = "postprocess", indexParameters?: any, blockCompilation = false) {
  122. if (camera != null) {
  123. this._camera = camera;
  124. this._scene = camera.getScene();
  125. camera.attachPostProcess(this);
  126. this._engine = this._scene.getEngine();
  127. this._scene.postProcesses.push(this);
  128. }
  129. else if (engine) {
  130. this._engine = engine;
  131. this._engine.postProcesses.push(this);
  132. }
  133. this._options = options;
  134. this.renderTargetSamplingMode = samplingMode ? samplingMode : Texture.NEAREST_SAMPLINGMODE;
  135. this._reusable = reusable || false;
  136. this._textureType = textureType;
  137. this._samplers = samplers || [];
  138. this._samplers.push("textureSampler");
  139. this._fragmentUrl = fragmentUrl;
  140. this._vertexUrl = vertexUrl;
  141. this._parameters = parameters || [];
  142. this._parameters.push("scale");
  143. this._indexParameters = indexParameters;
  144. if (!blockCompilation) {
  145. this.updateEffect(defines);
  146. }
  147. }
  148. public getEngine(): Engine {
  149. return this._engine;
  150. }
  151. public getEffect(): Effect {
  152. return this._effect;
  153. }
  154. public shareOutputWith(postProcess: PostProcess): PostProcess {
  155. this._disposeTextures();
  156. this._shareOutputWithPostProcess = postProcess;
  157. return this;
  158. }
  159. public updateEffect(defines: Nullable<string> = null, uniforms: Nullable<string[]> = null, samplers: Nullable<string[]> = null, indexParameters?: any,
  160. onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void) {
  161. this._effect = this._engine.createEffect({ vertex: this._vertexUrl, fragment: this._fragmentUrl },
  162. ["position"],
  163. uniforms || this._parameters,
  164. samplers || this._samplers,
  165. defines !== null ? defines : "",
  166. undefined,
  167. onCompiled,
  168. onError,
  169. indexParameters || this._indexParameters
  170. );
  171. }
  172. public isReusable(): boolean {
  173. return this._reusable;
  174. }
  175. /** invalidate frameBuffer to hint the postprocess to create a depth buffer */
  176. public markTextureDirty(): void {
  177. this.width = -1;
  178. }
  179. public activate(camera: Nullable<Camera>, sourceTexture: Nullable<InternalTexture> = null, forceDepthStencil?: boolean): void {
  180. camera = camera || this._camera;
  181. var scene = camera.getScene();
  182. var engine = scene.getEngine();
  183. var maxSize = engine.getCaps().maxTextureSize;
  184. var requiredWidth = ((sourceTexture ? sourceTexture.width : this._engine.getRenderWidth(true)) * <number>this._options) | 0;
  185. var requiredHeight = ((sourceTexture ? sourceTexture.height : this._engine.getRenderHeight(true)) * <number>this._options) | 0;
  186. var desiredWidth = ((<PostProcessOptions>this._options).width || requiredWidth);
  187. var desiredHeight = (<PostProcessOptions>this._options).height || requiredHeight;
  188. if (!this._shareOutputWithPostProcess && !this._forcedOutputTexture) {
  189. if (this.adaptScaleToCurrentViewport) {
  190. let currentViewport = engine.currentViewport;
  191. if (currentViewport) {
  192. desiredWidth *= currentViewport.width;
  193. desiredHeight *= currentViewport.height;
  194. }
  195. }
  196. if (this.renderTargetSamplingMode === Texture.TRILINEAR_SAMPLINGMODE || this.alwaysForcePOT) {
  197. if (!(<PostProcessOptions>this._options).width) {
  198. desiredWidth = engine.needPOTTextures ? Tools.GetExponentOfTwo(desiredWidth, maxSize, this.scaleMode) : desiredWidth;
  199. }
  200. if (!(<PostProcessOptions>this._options).height) {
  201. desiredHeight = engine.needPOTTextures ? Tools.GetExponentOfTwo(desiredHeight, maxSize, this.scaleMode) : desiredHeight;
  202. }
  203. }
  204. if (this.width !== desiredWidth || this.height !== desiredHeight) {
  205. if (this._textures.length > 0) {
  206. for (var i = 0; i < this._textures.length; i++) {
  207. this._engine._releaseTexture(this._textures.data[i]);
  208. }
  209. this._textures.reset();
  210. }
  211. this.width = desiredWidth;
  212. this.height = desiredHeight;
  213. let textureSize = { width: this.width, height: this.height };
  214. let textureOptions = {
  215. generateMipMaps: false,
  216. generateDepthBuffer: forceDepthStencil || camera._postProcesses.indexOf(this) === 0,
  217. generateStencilBuffer: (forceDepthStencil || camera._postProcesses.indexOf(this) === 0) && this._engine.isStencilEnable,
  218. samplingMode: this.renderTargetSamplingMode,
  219. type: this._textureType
  220. };
  221. this._textures.push(this._engine.createRenderTargetTexture(textureSize, textureOptions));
  222. if (this._reusable) {
  223. this._textures.push(this._engine.createRenderTargetTexture(textureSize, textureOptions));
  224. }
  225. this._texelSize.copyFromFloats(1.0 / this.width, 1.0 / this.height);
  226. this.onSizeChangedObservable.notifyObservers(this);
  227. }
  228. this._textures.forEach(texture => {
  229. if (texture.samples !== this.samples) {
  230. this._engine.updateRenderTargetTextureSampleCount(texture, this.samples);
  231. }
  232. });
  233. }
  234. var target: InternalTexture;
  235. if (this._shareOutputWithPostProcess) {
  236. target = this._shareOutputWithPostProcess.outputTexture;
  237. } else if (this._forcedOutputTexture) {
  238. target = this._forcedOutputTexture;
  239. this.width = this._forcedOutputTexture.width;
  240. this.height = this._forcedOutputTexture.height;
  241. } else {
  242. target = this.outputTexture;
  243. }
  244. if (this.enablePixelPerfectMode) {
  245. this._scaleRatio.copyFromFloats(requiredWidth / desiredWidth, requiredHeight / desiredHeight);
  246. this._engine.bindFramebuffer(target, 0, requiredWidth, requiredHeight, true);
  247. }
  248. else {
  249. this._scaleRatio.copyFromFloats(1, 1);
  250. this._engine.bindFramebuffer(target, 0, undefined, undefined, true);
  251. }
  252. this.onActivateObservable.notifyObservers(camera);
  253. // Clear
  254. if (this.autoClear && this.alphaMode === Engine.ALPHA_DISABLE) {
  255. this._engine.clear(this.clearColor ? this.clearColor : scene.clearColor, true, true, true);
  256. }
  257. if (this._reusable) {
  258. this._currentRenderTextureInd = (this._currentRenderTextureInd + 1) % 2;
  259. }
  260. }
  261. public get isSupported(): boolean {
  262. return this._effect.isSupported;
  263. }
  264. public get aspectRatio(): number {
  265. if (this._shareOutputWithPostProcess) {
  266. return this._shareOutputWithPostProcess.aspectRatio;
  267. }
  268. if (this._forcedOutputTexture) {
  269. return this._forcedOutputTexture.width / this._forcedOutputTexture.height;
  270. }
  271. return this.width / this.height;
  272. }
  273. /**
  274. * Get a value indicating if the post-process is ready to be used
  275. * @returns true if the post-process is ready (shader is compiled)
  276. */
  277. public isReady(): boolean {
  278. return this._effect && this._effect.isReady();
  279. }
  280. public apply(): Nullable<Effect> {
  281. // Check
  282. if (!this._effect || !this._effect.isReady())
  283. return null;
  284. // States
  285. this._engine.enableEffect(this._effect);
  286. this._engine.setState(false);
  287. this._engine.setDepthBuffer(false);
  288. this._engine.setDepthWrite(false);
  289. // Alpha
  290. this._engine.setAlphaMode(this.alphaMode);
  291. if (this.alphaConstants) {
  292. this.getEngine().setAlphaConstants(this.alphaConstants.r, this.alphaConstants.g, this.alphaConstants.b, this.alphaConstants.a);
  293. }
  294. // Texture
  295. var source: InternalTexture;
  296. if (this._shareOutputWithPostProcess) {
  297. source = this._shareOutputWithPostProcess.outputTexture;
  298. } else if (this._forcedOutputTexture) {
  299. source = this._forcedOutputTexture;
  300. } else {
  301. source = this.outputTexture;
  302. }
  303. this._effect._bindTexture("textureSampler", source);
  304. // Parameters
  305. this._effect.setVector2("scale", this._scaleRatio);
  306. this.onApplyObservable.notifyObservers(this._effect);
  307. return this._effect;
  308. }
  309. private _disposeTextures() {
  310. if (this._shareOutputWithPostProcess || this._forcedOutputTexture) {
  311. return;
  312. }
  313. if (this._textures.length > 0) {
  314. for (var i = 0; i < this._textures.length; i++) {
  315. this._engine._releaseTexture(this._textures.data[i]);
  316. }
  317. }
  318. this._textures.dispose();
  319. }
  320. public dispose(camera?: Camera): void {
  321. camera = camera || this._camera;
  322. this._disposeTextures();
  323. if (this._scene) {
  324. let index = this._scene.postProcesses.indexOf(this);
  325. if (index !== -1) {
  326. this._scene.postProcesses.splice(index, 1);
  327. }
  328. } else {
  329. let index = this._engine.postProcesses.indexOf(this);
  330. if (index !== -1) {
  331. this._engine.postProcesses.splice(index, 1);
  332. }
  333. }
  334. if (!camera) {
  335. return;
  336. }
  337. camera.detachPostProcess(this);
  338. var index = camera._postProcesses.indexOf(this);
  339. if (index === 0 && camera._postProcesses.length > 0) {
  340. this._camera._postProcesses[0].markTextureDirty();
  341. }
  342. this.onActivateObservable.clear();
  343. this.onAfterRenderObservable.clear();
  344. this.onApplyObservable.clear();
  345. this.onBeforeRenderObservable.clear();
  346. this.onSizeChangedObservable.clear();
  347. }
  348. }
  349. }