babylon.hdrRenderingPipeline.js 20 KB

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