babylon.hdrRenderingPipeline.ts 18 KB

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