babylon.hdrRenderingPipeline.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. /// <reference path="RenderPipeline\babylon.postProcessRenderPipeline.ts" />
  2. module BABYLON {
  3. export class HDRRenderingPipeline extends PostProcessRenderPipeline implements IDisposable {
  4. /**
  5. * Public members
  6. */
  7. // Gaussian Blur
  8. /**
  9. * Gaussian blur coefficient
  10. * @type {number}
  11. */
  12. public gaussCoeff: number = 0.3;
  13. /**
  14. * Gaussian blur mean
  15. * @type {number}
  16. */
  17. public gaussMean: number = 1.0;
  18. /**
  19. * Gaussian blur standard deviation
  20. * @type {number}
  21. */
  22. public gaussStandDev: number = 0.8;
  23. /**
  24. * Gaussian blur multiplier. Multiplies the blur effect
  25. * @type {number}
  26. */
  27. public gaussMultiplier: number = 4.0;
  28. // HDR
  29. /**
  30. * Exposure, controls the overall intensity of the pipeline
  31. * @type {number}
  32. */
  33. public exposure: number = 1.0;
  34. /**
  35. * Minimum luminance that the post-process can output. Luminance is >= 0
  36. * @type {number}
  37. */
  38. public minimumLuminance: number = 1.0;
  39. /**
  40. * Maximum luminance that the post-process can output. Must be suprerior to minimumLuminance
  41. * @type {number}
  42. */
  43. public maximumLuminance: number = 1e20;
  44. /**
  45. * Increase rate for luminance: eye adaptation speed to dark
  46. * @type {number}
  47. */
  48. public luminanceIncreaserate: number = 0.5;
  49. /**
  50. * Decrease rate for luminance: eye adaptation speed to bright
  51. * @type {number}
  52. */
  53. public luminanceDecreaseRate: number = 0.5;
  54. // Bright pass
  55. /**
  56. * Minimum luminance needed to compute HDR
  57. * @type {number}
  58. */
  59. public brightThreshold: number = 0.8;
  60. /**
  61. * Private members
  62. */
  63. // Gaussian blur
  64. private _guassianBlurHPostProcess: PostProcess;
  65. private _guassianBlurVPostProcess: PostProcess;
  66. // Bright pass
  67. private _brightPassPostProcess: PostProcess;
  68. // Texture adder
  69. private _textureAdderPostProcess: PostProcess;
  70. // Down Sampling
  71. private _downSampleX4PostProcess: PostProcess;
  72. // Original Post-process
  73. private _originalPostProcess: PostProcess;
  74. // HDR
  75. private _hdrPostProcess: PostProcess;
  76. private _hdrCurrentLuminance: number;
  77. private _hdrOutputLuminance: number;
  78. // Luminance generator
  79. public static LUM_STEPS: number = 6;
  80. private _downSamplePostProcesses: Array<PostProcess>;
  81. // Global
  82. private _scene: Scene;
  83. private _needUpdate: boolean = true;
  84. /**
  85. * @constructor
  86. * @param {string} name - The rendering pipeline name
  87. * @param {BABYLON.Scene} scene - The scene linked to this pipeline
  88. * @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)
  89. * @param {BABYLON.PostProcess} originalPostProcess - the custom original color post-process. Must be "reusable". Can be null.
  90. * @param {BABYLON.Camera[]} cameras - The array of cameras that the rendering pipeline will be attached to
  91. */
  92. constructor(name: string, scene: Scene, ratio: number, originalPostProcess: PostProcess = null, cameras?: Camera[]) {
  93. super(scene.getEngine(), name);
  94. this._scene = scene;
  95. // Bright pass
  96. this._createBrightPassPostProcess(scene, ratio);
  97. // Down sample X4
  98. this._createDownSampleX4PostProcess(scene, ratio);
  99. // Create gaussian blur post-processes
  100. this._createGaussianBlurPostProcess(scene, ratio);
  101. // Texture adder
  102. this._createTextureAdderPostProcess(scene, ratio);
  103. // Luminance generator
  104. this._createLuminanceGeneratorPostProcess(scene);
  105. // HDR
  106. this._createHDRPostProcess(scene, ratio);
  107. // Pass postprocess
  108. if (originalPostProcess === null) {
  109. this._originalPostProcess = new PassPostProcess("hdr", ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false);
  110. } else {
  111. this._originalPostProcess = originalPostProcess;
  112. }
  113. // Configure pipeline
  114. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRPassPostProcess",() => { return this._originalPostProcess; }, true));
  115. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRBrightPass",() => { return this._brightPassPostProcess; }, true));
  116. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRDownSampleX4",() => { return this._downSampleX4PostProcess; }, true));
  117. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRGaussianBlurH",() => { return this._guassianBlurHPostProcess; }, true));
  118. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRGaussianBlurV",() => { return this._guassianBlurVPostProcess; }, true));
  119. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRTextureAdder",() => { return this._textureAdderPostProcess; }, true));
  120. var addDownSamplerPostProcess = (id: number) => {
  121. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDRDownSampler" + id,() => { return this._downSamplePostProcesses[id]; }, true));
  122. };
  123. for (var i = HDRRenderingPipeline.LUM_STEPS - 1; i >= 0; i--) {
  124. addDownSamplerPostProcess(i);
  125. }
  126. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), "HDR",() => { return this._hdrPostProcess; }, true));
  127. // Finish
  128. scene.postProcessRenderPipelineManager.addPipeline(this);
  129. if (cameras !== null) {
  130. scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline(name, cameras);
  131. }
  132. this.update();
  133. }
  134. /**
  135. * Tells the pipeline to update its post-processes
  136. */
  137. public update(): void {
  138. this._needUpdate = true;
  139. }
  140. /**
  141. * Returns the current calculated luminance
  142. */
  143. public getCurrentLuminance(): number {
  144. return this._hdrCurrentLuminance;
  145. }
  146. /**
  147. * Returns the currently drawn luminance
  148. */
  149. public getOutputLuminance(): number {
  150. return this._hdrOutputLuminance;
  151. }
  152. /**
  153. * Releases the rendering pipeline and its internal effects. Detaches pipeline from cameras
  154. */
  155. public dispose(): void {
  156. this._originalPostProcess = undefined;
  157. this._brightPassPostProcess = undefined;
  158. this._downSampleX4PostProcess = undefined;
  159. this._guassianBlurHPostProcess = undefined;
  160. this._guassianBlurVPostProcess = undefined;
  161. this._textureAdderPostProcess = undefined;
  162. for (var i = HDRRenderingPipeline.LUM_STEPS - 1; i >= 0; i--) {
  163. this._downSamplePostProcesses[i] = undefined;
  164. }
  165. this._hdrPostProcess = undefined;
  166. this._scene.postProcessRenderPipelineManager.detachCamerasFromRenderPipeline(this._name, this._scene.cameras);
  167. }
  168. /**
  169. * Creates the HDR post-process and computes the luminance adaptation
  170. */
  171. private _createHDRPostProcess(scene: Scene, ratio: number): void {
  172. var hdrLastLuminance = 0.0;
  173. this._hdrOutputLuminance = -1.0;
  174. this._hdrCurrentLuminance = 1.0;
  175. this._hdrPostProcess = new PostProcess("hdr", "hdr", ["exposure", "avgLuminance"], ["otherSampler"], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define HDR");
  176. this._hdrPostProcess.onApply = (effect: Effect) => {
  177. if (this._hdrOutputLuminance < 0.0) {
  178. this._hdrOutputLuminance = this._hdrCurrentLuminance;
  179. }
  180. else {
  181. var dt = (hdrLastLuminance - (hdrLastLuminance + scene.getEngine().getDeltaTime())) / 1000.0;
  182. if (this._hdrCurrentLuminance < this._hdrOutputLuminance + this.luminanceDecreaseRate * dt) {
  183. this._hdrOutputLuminance += this.luminanceDecreaseRate * dt;
  184. }
  185. else if (this._hdrCurrentLuminance > this._hdrOutputLuminance - this.luminanceIncreaserate * dt) {
  186. this._hdrOutputLuminance -= this.luminanceIncreaserate * dt;
  187. }
  188. else {
  189. this._hdrOutputLuminance = this._hdrCurrentLuminance;
  190. }
  191. }
  192. this._hdrOutputLuminance = MathTools.Clamp(this._hdrOutputLuminance, this.minimumLuminance, this.maximumLuminance);
  193. hdrLastLuminance += scene.getEngine().getDeltaTime();
  194. effect.setTextureFromPostProcess("textureSampler", this._textureAdderPostProcess);
  195. effect.setTextureFromPostProcess("otherSampler", this._originalPostProcess);
  196. effect.setFloat("exposure", this.exposure);
  197. effect.setFloat("avgLuminance", this._hdrOutputLuminance);
  198. this._needUpdate = false;
  199. };
  200. }
  201. /**
  202. * Texture Adder post-process
  203. */
  204. private _createTextureAdderPostProcess(scene: Scene, ratio: number): void {
  205. this._textureAdderPostProcess = new PostProcess("hdr", "hdr", [], ["otherSampler"], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define TEXTURE_ADDER");
  206. this._textureAdderPostProcess.onApply = (effect: Effect) => {
  207. effect.setTextureFromPostProcess("otherSampler", this._originalPostProcess);
  208. };
  209. }
  210. /**
  211. * Down sample X4 post-process
  212. */
  213. private _createDownSampleX4PostProcess(scene: Scene, ratio: number): void {
  214. var downSampleX4Offsets = new Array<number>(32);
  215. this._downSampleX4PostProcess = new PostProcess("hdr", "hdr", ["dsOffsets"], [], ratio / 4, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define DOWN_SAMPLE_X4");
  216. this._downSampleX4PostProcess.onApply = (effect: Effect) => {
  217. if (this._needUpdate) {
  218. var id = 0;
  219. for (var i = -2; i < 2; i++) {
  220. for (var j = -2; j < 2; j++) {
  221. downSampleX4Offsets[id] = (i + 0.5) * (1.0 / this._downSampleX4PostProcess.width);
  222. downSampleX4Offsets[id + 1] = (j + 0.5) * (1.0 / this._downSampleX4PostProcess.height);
  223. id += 2;
  224. }
  225. }
  226. }
  227. effect.setArray2("dsOffsets", downSampleX4Offsets);
  228. };
  229. }
  230. /**
  231. * Bright pass post-process
  232. */
  233. private _createBrightPassPostProcess(scene: Scene, ratio: number): void {
  234. var brightOffsets = new Array<number>(8);
  235. var brightPassCallback = (effect: Effect) => {
  236. if (this._needUpdate) {
  237. var sU = (1.0 / this._brightPassPostProcess.width);
  238. var sV = (1.0 / this._brightPassPostProcess.height);
  239. brightOffsets[0] = -0.5 * sU;
  240. brightOffsets[1] = 0.5 * sV;
  241. brightOffsets[2] = 0.5 * sU;
  242. brightOffsets[3] = 0.5 * sV;
  243. brightOffsets[4] = -0.5 * sU;
  244. brightOffsets[5] = -0.5 * sV;
  245. brightOffsets[6] = 0.5 * sU;
  246. brightOffsets[7] = -0.5 * sV;
  247. }
  248. effect.setArray2("dsOffsets", brightOffsets);
  249. effect.setFloat("brightThreshold", this.brightThreshold);
  250. };
  251. this._brightPassPostProcess = new PostProcess("hdr", "hdr", ["dsOffsets", "brightThreshold"], [], ratio, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define BRIGHT_PASS");
  252. this._brightPassPostProcess.onApply = brightPassCallback;
  253. }
  254. /**
  255. * Luminance generator. Creates the luminance post-process and down sample post-processes
  256. */
  257. private _createLuminanceGeneratorPostProcess(scene: Scene): void {
  258. var lumSteps: number = HDRRenderingPipeline.LUM_STEPS;
  259. var luminanceOffsets = new Array<number>(8);
  260. var downSampleOffsets = new Array<number>(18);
  261. var halfDestPixelSize: number;
  262. this._downSamplePostProcesses = new Array<PostProcess>(lumSteps);
  263. // Utils for luminance
  264. var luminanceUpdateSourceOffsets = (width: number, height: number) => {
  265. var sU = (1.0 / width);
  266. var sV = (1.0 / height);
  267. luminanceOffsets[0] = -0.5 * sU;
  268. luminanceOffsets[1] = 0.5 * sV;
  269. luminanceOffsets[2] = 0.5 * sU;
  270. luminanceOffsets[3] = 0.5 * sV;
  271. luminanceOffsets[4] = -0.5 * sU;
  272. luminanceOffsets[5] = -0.5 * sV;
  273. luminanceOffsets[6] = 0.5 * sU;
  274. luminanceOffsets[7] = -0.5 * sV;
  275. };
  276. var luminanceUpdateDestOffsets = (width: number, height: number) => {
  277. var id = 0;
  278. for (var x = -1; x < 2; x++) {
  279. for (var y = -1; y < 2; y++) {
  280. downSampleOffsets[id] = (x) / width;
  281. downSampleOffsets[id + 1] = (y) / height;
  282. id += 2;
  283. }
  284. }
  285. };
  286. // Luminance callback
  287. var luminanceCallback = (effect: Effect) => {
  288. if (this._needUpdate) {
  289. luminanceUpdateSourceOffsets(this._textureAdderPostProcess.width, this._textureAdderPostProcess.height);
  290. }
  291. effect.setTextureFromPostProcess("textureSampler", this._textureAdderPostProcess);
  292. effect.setArray2("lumOffsets", luminanceOffsets);
  293. }
  294. // Down sample callbacks
  295. var downSampleCallback = (indice: number) => {
  296. var i = indice;
  297. return (effect: Effect) => {
  298. luminanceUpdateSourceOffsets(this._downSamplePostProcesses[i].width, this._downSamplePostProcesses[i].height);
  299. luminanceUpdateDestOffsets(this._downSamplePostProcesses[i].width, this._downSamplePostProcesses[i].height);
  300. halfDestPixelSize = 0.5 / this._downSamplePostProcesses[i].width;
  301. effect.setTextureFromPostProcess("textureSampler", this._downSamplePostProcesses[i + 1]);
  302. effect.setFloat("halfDestPixelSize", halfDestPixelSize);
  303. effect.setArray2("dsOffsets", downSampleOffsets);
  304. }
  305. };
  306. var downSampleAfterRenderCallback = (effect: Effect) => {
  307. // Unpack result
  308. var pixel = scene.getEngine().readPixels(0, 0, 1, 1);
  309. var bit_shift = new Vector4(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0);
  310. this._hdrCurrentLuminance = (pixel[0] * bit_shift.x + pixel[1] * bit_shift.y + pixel[2] * bit_shift.z + pixel[3] * bit_shift.w) / 100.0;
  311. };
  312. // Create luminance post-process
  313. var ratio = { width: Math.pow(3, lumSteps - 1), height: Math.pow(3, lumSteps - 1) };
  314. this._downSamplePostProcesses[lumSteps - 1] = new PostProcess("hdr", "hdr", ["lumOffsets"], [], ratio, null, Texture.NEAREST_SAMPLINGMODE, scene.getEngine(), false, "#define LUMINANCE_GENERATOR", Engine.TEXTURETYPE_FLOAT);
  315. this._downSamplePostProcesses[lumSteps - 1].onApply = luminanceCallback;
  316. // Create down sample post-processes
  317. for (var i = lumSteps - 2; i >= 0; i--) {
  318. var length = Math.pow(3, i);
  319. ratio = { width: length, height: length };
  320. var defines = "#define DOWN_SAMPLE\n";
  321. if (i === 0) {
  322. defines += "#define FINAL_DOWN_SAMPLE\n"; // To pack the result
  323. }
  324. this._downSamplePostProcesses[i] = new PostProcess("hdr", "hdr", ["dsOffsets", "halfDestPixelSize"], [], ratio, null, Texture.NEAREST_SAMPLINGMODE, scene.getEngine(), false, defines, Engine.TEXTURETYPE_FLOAT);
  325. this._downSamplePostProcesses[i].onApply = downSampleCallback(i);
  326. if (i === 0) {
  327. this._downSamplePostProcesses[i].onAfterRender = downSampleAfterRenderCallback;
  328. }
  329. }
  330. }
  331. /**
  332. * Gaussian blur post-processes. Horizontal and Vertical
  333. */
  334. private _createGaussianBlurPostProcess(scene: Scene, ratio: number): void {
  335. var blurOffsetsW = new Array<number>(9);
  336. var blurOffsetsH = new Array<number>(9);
  337. var blurWeights = new Array<number>(9);
  338. var uniforms: string[] = ["blurOffsets", "blurWeights", "multiplier"];
  339. // Utils for gaussian blur
  340. var calculateBlurOffsets = (height: boolean) => {
  341. var lastOutputDimensions: any = {
  342. width: scene.getEngine().getRenderWidth(),
  343. height: scene.getEngine().getRenderHeight()
  344. };
  345. for (var i = 0; i < 9; i++) {
  346. var value = (i - 4.0) * (1.0 / (height === true ? lastOutputDimensions.height : lastOutputDimensions.width));
  347. if (height) {
  348. blurOffsetsH[i] = value;
  349. } else {
  350. blurOffsetsW[i] = value;
  351. }
  352. }
  353. };
  354. var calculateWeights = () => {
  355. var x: number = 0.0;
  356. for (var i = 0; i < 9; i++) {
  357. x = (i - 4.0) / 4.0;
  358. blurWeights[i] = this.gaussCoeff * (1.0 / Math.sqrt(2.0 * Math.PI * this.gaussStandDev)) * Math.exp((-((x - this.gaussMean) * (x - this.gaussMean))) / (2.0 * this.gaussStandDev * this.gaussStandDev));
  359. }
  360. }
  361. // Callback
  362. var gaussianBlurCallback = (height: boolean) => {
  363. return (effect: Effect) => {
  364. if (this._needUpdate) {
  365. calculateWeights();
  366. calculateBlurOffsets(height);
  367. }
  368. effect.setArray("blurOffsets", height ? blurOffsetsH : blurOffsetsW);
  369. effect.setArray("blurWeights", blurWeights);
  370. effect.setFloat("multiplier", this.gaussMultiplier);
  371. };
  372. };
  373. // Create horizontal gaussian blur post-processes
  374. this._guassianBlurHPostProcess = new PostProcess("hdr", "hdr", uniforms, [], ratio / 4, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define GAUSSIAN_BLUR_H");
  375. this._guassianBlurHPostProcess.onApply = gaussianBlurCallback(false);
  376. // Create vertical gaussian blur post-process
  377. this._guassianBlurVPostProcess = new PostProcess("hdr", "hdr", uniforms, [], ratio / 4, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, "#define GAUSSIAN_BLUR_V");
  378. this._guassianBlurVPostProcess.onApply = gaussianBlurCallback(true);
  379. }
  380. }
  381. }