blurPostProcess.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import { __decorate } from "../tslib.es6.js";
  2. import { PostProcess } from "./postProcess.js";
  3. import { Texture } from "../Materials/Textures/texture.js";
  4. import "../Shaders/kernelBlur.fragment.js";
  5. import "../Shaders/kernelBlur.vertex.js";
  6. import { RegisterClass } from "../Misc/typeStore.js";
  7. import { serialize, serializeAsVector2 } from "../Misc/decorators.js";
  8. import { SerializationHelper } from "../Misc/decorators.serialization.js";
  9. /**
  10. * The Blur Post Process which blurs an image based on a kernel and direction.
  11. * Can be used twice in x and y directions to perform a gaussian blur in two passes.
  12. */
  13. export class BlurPostProcess extends PostProcess {
  14. /**
  15. * Sets the length in pixels of the blur sample region
  16. */
  17. set kernel(v) {
  18. if (this._idealKernel === v) {
  19. return;
  20. }
  21. v = Math.max(v, 1);
  22. this._idealKernel = v;
  23. this._kernel = this._nearestBestKernel(v);
  24. if (!this._blockCompilation) {
  25. this._updateParameters();
  26. }
  27. }
  28. /**
  29. * Gets the length in pixels of the blur sample region
  30. */
  31. get kernel() {
  32. return this._idealKernel;
  33. }
  34. /**
  35. * Sets whether or not the blur needs to unpack/repack floats
  36. */
  37. set packedFloat(v) {
  38. if (this._packedFloat === v) {
  39. return;
  40. }
  41. this._packedFloat = v;
  42. if (!this._blockCompilation) {
  43. this._updateParameters();
  44. }
  45. }
  46. /**
  47. * Gets whether or not the blur is unpacking/repacking floats
  48. */
  49. get packedFloat() {
  50. return this._packedFloat;
  51. }
  52. /**
  53. * Gets a string identifying the name of the class
  54. * @returns "BlurPostProcess" string
  55. */
  56. getClassName() {
  57. return "BlurPostProcess";
  58. }
  59. /**
  60. * Creates a new instance BlurPostProcess
  61. * @param name The name of the effect.
  62. * @param direction The direction in which to blur the image.
  63. * @param kernel The size of the kernel to be used when computing the blur. eg. Size of 3 will blur the center pixel by 2 pixels surrounding it.
  64. * @param options The required width/height ratio to downsize to before computing the render pass. (Use 1.0 for full size)
  65. * @param camera The camera to apply the render pass to.
  66. * @param samplingMode The sampling mode to be used when computing the pass. (default: 0)
  67. * @param engine The engine which the post process will be applied. (default: current engine)
  68. * @param reusable If the post process can be reused on the same frame. (default: false)
  69. * @param textureType Type of textures used when performing the post process. (default: 0)
  70. * @param defines
  71. * @param _blockCompilation If compilation of the shader should not be done in the constructor. The updateEffect method can be used to compile the shader at a later time. (default: false)
  72. * @param textureFormat Format of textures used when performing the post process. (default: TEXTUREFORMAT_RGBA)
  73. */
  74. constructor(name, direction, kernel, options, camera, samplingMode = Texture.BILINEAR_SAMPLINGMODE, engine, reusable, textureType = 0, defines = "", _blockCompilation = false, textureFormat = 5) {
  75. super(name, "kernelBlur", ["delta", "direction"], ["circleOfConfusionSampler"], options, camera, samplingMode, engine, reusable, null, textureType, "kernelBlur", { varyingCount: 0, depCount: 0 }, true, textureFormat);
  76. this._blockCompilation = _blockCompilation;
  77. this._packedFloat = false;
  78. this._staticDefines = "";
  79. this._staticDefines = defines;
  80. this.direction = direction;
  81. this.onApplyObservable.add((effect) => {
  82. if (this._outputTexture) {
  83. effect.setFloat2("delta", (1 / this._outputTexture.width) * this.direction.x, (1 / this._outputTexture.height) * this.direction.y);
  84. }
  85. else {
  86. effect.setFloat2("delta", (1 / this.width) * this.direction.x, (1 / this.height) * this.direction.y);
  87. }
  88. });
  89. this.kernel = kernel;
  90. }
  91. /**
  92. * Updates the effect with the current post process compile time values and recompiles the shader.
  93. * @param defines Define statements that should be added at the beginning of the shader. (default: null)
  94. * @param uniforms Set of uniform variables that will be passed to the shader. (default: null)
  95. * @param samplers Set of Texture2D variables that will be passed to the shader. (default: null)
  96. * @param indexParameters The index parameters to be used for babylons include syntax "#include<kernelBlurVaryingDeclaration>[0..varyingCount]". (default: undefined) See usage in babylon.blurPostProcess.ts and kernelBlur.vertex.fx
  97. * @param onCompiled Called when the shader has been compiled.
  98. * @param onError Called if there is an error when compiling a shader.
  99. */
  100. updateEffect(defines = null, uniforms = null, samplers = null, indexParameters, onCompiled, onError) {
  101. this._updateParameters(onCompiled, onError);
  102. }
  103. _updateParameters(onCompiled, onError) {
  104. // Generate sampling offsets and weights
  105. const N = this._kernel;
  106. const centerIndex = (N - 1) / 2;
  107. // Generate Gaussian sampling weights over kernel
  108. let offsets = [];
  109. let weights = [];
  110. let totalWeight = 0;
  111. for (let i = 0; i < N; i++) {
  112. const u = i / (N - 1);
  113. const w = this._gaussianWeight(u * 2.0 - 1);
  114. offsets[i] = i - centerIndex;
  115. weights[i] = w;
  116. totalWeight += w;
  117. }
  118. // Normalize weights
  119. for (let i = 0; i < weights.length; i++) {
  120. weights[i] /= totalWeight;
  121. }
  122. // Optimize: combine samples to take advantage of hardware linear sampling
  123. // Walk from left to center, combining pairs (symmetrically)
  124. const linearSamplingWeights = [];
  125. const linearSamplingOffsets = [];
  126. const linearSamplingMap = [];
  127. for (let i = 0; i <= centerIndex; i += 2) {
  128. const j = Math.min(i + 1, Math.floor(centerIndex));
  129. const singleCenterSample = i === j;
  130. if (singleCenterSample) {
  131. linearSamplingMap.push({ o: offsets[i], w: weights[i] });
  132. }
  133. else {
  134. const sharedCell = j === centerIndex;
  135. const weightLinear = weights[i] + weights[j] * (sharedCell ? 0.5 : 1);
  136. const offsetLinear = offsets[i] + 1 / (1 + weights[i] / weights[j]);
  137. if (offsetLinear === 0) {
  138. linearSamplingMap.push({ o: offsets[i], w: weights[i] });
  139. linearSamplingMap.push({ o: offsets[i + 1], w: weights[i + 1] });
  140. }
  141. else {
  142. linearSamplingMap.push({ o: offsetLinear, w: weightLinear });
  143. linearSamplingMap.push({ o: -offsetLinear, w: weightLinear });
  144. }
  145. }
  146. }
  147. for (let i = 0; i < linearSamplingMap.length; i++) {
  148. linearSamplingOffsets[i] = linearSamplingMap[i].o;
  149. linearSamplingWeights[i] = linearSamplingMap[i].w;
  150. }
  151. // Replace with optimized
  152. offsets = linearSamplingOffsets;
  153. weights = linearSamplingWeights;
  154. // Generate shaders
  155. const maxVaryingRows = this.getEngine().getCaps().maxVaryingVectors;
  156. const freeVaryingVec2 = Math.max(maxVaryingRows, 0) - 1; // Because of sampleCenter
  157. let varyingCount = Math.min(offsets.length, freeVaryingVec2);
  158. let defines = "";
  159. defines += this._staticDefines;
  160. // The DOF fragment should ignore the center pixel when looping as it is handled manually in the fragment shader.
  161. if (this._staticDefines.indexOf("DOF") != -1) {
  162. defines += `#define CENTER_WEIGHT ${this._glslFloat(weights[varyingCount - 1])}\n`;
  163. varyingCount--;
  164. }
  165. for (let i = 0; i < varyingCount; i++) {
  166. defines += `#define KERNEL_OFFSET${i} ${this._glslFloat(offsets[i])}\n`;
  167. defines += `#define KERNEL_WEIGHT${i} ${this._glslFloat(weights[i])}\n`;
  168. }
  169. let depCount = 0;
  170. for (let i = freeVaryingVec2; i < offsets.length; i++) {
  171. defines += `#define KERNEL_DEP_OFFSET${depCount} ${this._glslFloat(offsets[i])}\n`;
  172. defines += `#define KERNEL_DEP_WEIGHT${depCount} ${this._glslFloat(weights[i])}\n`;
  173. depCount++;
  174. }
  175. if (this.packedFloat) {
  176. defines += `#define PACKEDFLOAT 1`;
  177. }
  178. this._blockCompilation = false;
  179. super.updateEffect(defines, null, null, {
  180. varyingCount: varyingCount,
  181. depCount: depCount,
  182. }, onCompiled, onError);
  183. }
  184. /**
  185. * Best kernels are odd numbers that when divided by 2, their integer part is even, so 5, 9 or 13.
  186. * Other odd kernels optimize correctly but require proportionally more samples, even kernels are
  187. * possible but will produce minor visual artifacts. Since each new kernel requires a new shader we
  188. * want to minimize kernel changes, having gaps between physical kernels is helpful in that regard.
  189. * The gaps between physical kernels are compensated for in the weighting of the samples
  190. * @param idealKernel Ideal blur kernel.
  191. * @returns Nearest best kernel.
  192. */
  193. _nearestBestKernel(idealKernel) {
  194. const v = Math.round(idealKernel);
  195. for (const k of [v, v - 1, v + 1, v - 2, v + 2]) {
  196. if (k % 2 !== 0 && Math.floor(k / 2) % 2 === 0 && k > 0) {
  197. return Math.max(k, 3);
  198. }
  199. }
  200. return Math.max(v, 3);
  201. }
  202. /**
  203. * Calculates the value of a Gaussian distribution with sigma 3 at a given point.
  204. * @param x The point on the Gaussian distribution to sample.
  205. * @returns the value of the Gaussian function at x.
  206. */
  207. _gaussianWeight(x) {
  208. //reference: Engines/ImageProcessingBlur.cpp #dcc760
  209. // We are evaluating the Gaussian (normal) distribution over a kernel parameter space of [-1,1],
  210. // so we truncate at three standard deviations by setting stddev (sigma) to 1/3.
  211. // The choice of 3-sigma truncation is common but arbitrary, and means that the signal is
  212. // truncated at around 1.3% of peak strength.
  213. //the distribution is scaled to account for the difference between the actual kernel size and the requested kernel size
  214. const sigma = 1 / 3;
  215. const denominator = Math.sqrt(2.0 * Math.PI) * sigma;
  216. const exponent = -((x * x) / (2.0 * sigma * sigma));
  217. const weight = (1.0 / denominator) * Math.exp(exponent);
  218. return weight;
  219. }
  220. /**
  221. * Generates a string that can be used as a floating point number in GLSL.
  222. * @param x Value to print.
  223. * @param decimalFigures Number of decimal places to print the number to (excluding trailing 0s).
  224. * @returns GLSL float string.
  225. */
  226. _glslFloat(x, decimalFigures = 8) {
  227. return x.toFixed(decimalFigures).replace(/0+$/, "");
  228. }
  229. /**
  230. * @internal
  231. */
  232. static _Parse(parsedPostProcess, targetCamera, scene, rootUrl) {
  233. return SerializationHelper.Parse(() => {
  234. return new BlurPostProcess(parsedPostProcess.name, parsedPostProcess.direction, parsedPostProcess.kernel, parsedPostProcess.options, targetCamera, parsedPostProcess.renderTargetSamplingMode, scene.getEngine(), parsedPostProcess.reusable, parsedPostProcess.textureType, undefined, false);
  235. }, parsedPostProcess, scene, rootUrl);
  236. }
  237. }
  238. __decorate([
  239. serialize("kernel")
  240. ], BlurPostProcess.prototype, "_kernel", void 0);
  241. __decorate([
  242. serialize("packedFloat")
  243. ], BlurPostProcess.prototype, "_packedFloat", void 0);
  244. __decorate([
  245. serializeAsVector2()
  246. ], BlurPostProcess.prototype, "direction", void 0);
  247. RegisterClass("BABYLON.BlurPostProcess", BlurPostProcess);
  248. //# sourceMappingURL=blurPostProcess.js.map