babylon.ssao2RenderingPipeline.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. module BABYLON {
  2. export class SSAO2RenderingPipeline extends PostProcessRenderPipeline {
  3. // Members
  4. /**
  5. * The PassPostProcess id in the pipeline that contains the original scene color
  6. * @type {string}
  7. */
  8. public SSAOOriginalSceneColorEffect: string = "SSAOOriginalSceneColorEffect";
  9. /**
  10. * The SSAO PostProcess id in the pipeline
  11. * @type {string}
  12. */
  13. public SSAORenderEffect: string = "SSAORenderEffect";
  14. /**
  15. * The horizontal blur PostProcess id in the pipeline
  16. * @type {string}
  17. */
  18. public SSAOBlurHRenderEffect: string = "SSAOBlurHRenderEffect";
  19. /**
  20. * The vertical blur PostProcess id in the pipeline
  21. * @type {string}
  22. */
  23. public SSAOBlurVRenderEffect: string = "SSAOBlurVRenderEffect";
  24. /**
  25. * The PostProcess id in the pipeline that combines the SSAO-Blur output with the original scene color (SSAOOriginalSceneColorEffect)
  26. * @type {string}
  27. */
  28. public SSAOCombineRenderEffect: string = "SSAOCombineRenderEffect";
  29. /**
  30. * The output strength of the SSAO post-process. Default value is 1.0.
  31. * @type {number}
  32. */
  33. @serialize()
  34. public totalStrength: number = 1.0;
  35. /**
  36. * Maximum depth value to still render AO. A smooth falloff makes the dimming more natural, so there will be no abrupt shading change.
  37. * @type {number}
  38. */
  39. @serialize()
  40. public maxZ: number = 100.0;
  41. /**
  42. * In order to save performances, SSAO radius is clamped on close geometry. This ratio changes by how much
  43. * @type {number}
  44. */
  45. @serialize()
  46. public minZAspect: number = 0.2;
  47. /**
  48. * Number of samples used for the SSAO calculations. Default value is 8
  49. * @type {number}
  50. */
  51. @serialize("samples")
  52. private _samples: number = 8;
  53. /**
  54. * Dynamically generated sphere sampler.
  55. * @type {number[]}
  56. */
  57. private _sampleSphere: number[];
  58. /**
  59. * Blur filter offsets
  60. * @type {number[]}
  61. */
  62. private _samplerOffsets: number[];
  63. public set samples(n: number) {
  64. this._ssaoPostProcess.updateEffect("#define SAMPLES " + n + "\n#define SSAO");
  65. this._samples = n;
  66. this._sampleSphere = this._generateHemisphere();
  67. this._firstUpdate = true;
  68. }
  69. public get samples(): number {
  70. return this._samples;
  71. }
  72. /**
  73. * Are we using bilateral blur ?
  74. * @type {boolean}
  75. */
  76. @serialize("expensiveBlur")
  77. private _expensiveBlur: boolean = true;
  78. public set expensiveBlur(b: boolean) {
  79. this._blurHPostProcess.updateEffect("#define BILATERAL_BLUR\n#define BILATERAL_BLUR_H\n#define SAMPLES 16\n#define EXPENSIVE " + (b ? "1" : "0") + "\n",
  80. null, ["textureSampler", "depthSampler"]);
  81. this._blurVPostProcess.updateEffect("#define BILATERAL_BLUR\n#define SAMPLES 16\n#define EXPENSIVE " + (b ? "1" : "0") + "\n",
  82. null, ["textureSampler", "depthSampler"]);
  83. this._expensiveBlur = b;
  84. this._firstUpdate = true;
  85. }
  86. public get expensiveBlur(): boolean {
  87. return this._expensiveBlur;
  88. }
  89. /**
  90. * The radius around the analyzed pixel used by the SSAO post-process. Default value is 2.0
  91. * @type {number}
  92. */
  93. @serialize()
  94. public radius: number = 2.0;
  95. /**
  96. * The base color of the SSAO post-process
  97. * The final result is "base + ssao" between [0, 1]
  98. * @type {number}
  99. */
  100. @serialize()
  101. public base: number = 0.1;
  102. /**
  103. * Support test.
  104. * @type {boolean}
  105. */
  106. public static get IsSupported(): boolean {
  107. var engine = Engine.LastCreatedEngine;
  108. return engine.getCaps().drawBuffersExtension;
  109. }
  110. private _scene: Scene;
  111. private _depthTexture: Texture;
  112. private _normalTexture: Texture;
  113. private _randomTexture: DynamicTexture;
  114. private _originalColorPostProcess: PassPostProcess;
  115. private _ssaoPostProcess: PostProcess;
  116. private _blurHPostProcess: PostProcess;
  117. private _blurVPostProcess: PostProcess;
  118. private _ssaoCombinePostProcess: PostProcess;
  119. private _firstUpdate: boolean = true;
  120. @serialize()
  121. private _ratio: any;
  122. /**
  123. * @constructor
  124. * @param {string} name - The rendering pipeline name
  125. * @param {BABYLON.Scene} scene - The scene linked to this pipeline
  126. * @param {any} ratio - The size of the postprocesses. Can be a number shared between passes or an object for more precision: { ssaoRatio: 0.5, blurRatio: 1.0 }
  127. * @param {BABYLON.Camera[]} cameras - The array of cameras that the rendering pipeline will be attached to
  128. */
  129. constructor(name: string, scene: Scene, ratio: any, cameras?: Camera[]) {
  130. super(scene.getEngine(), name);
  131. this._scene = scene;
  132. if (!this.isSupported) {
  133. Tools.Error("SSAO 2 needs WebGL 2 support.");
  134. return;
  135. }
  136. var ssaoRatio = ratio.ssaoRatio || ratio;
  137. var blurRatio = ratio.blurRatio || ratio;
  138. this._ratio = {
  139. ssaoRatio: ssaoRatio,
  140. blurRatio: blurRatio
  141. };
  142. // Set up assets
  143. this._createRandomTexture();
  144. this._depthTexture = scene.enableGeometryBufferRenderer().getGBuffer().textures[0];
  145. this._normalTexture = scene.enableGeometryBufferRenderer().getGBuffer().textures[1];
  146. this._originalColorPostProcess = new PassPostProcess("SSAOOriginalSceneColor", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false);
  147. this._createSSAOPostProcess(1.0);
  148. this._createBlurPostProcess(ssaoRatio, blurRatio);
  149. this._createSSAOCombinePostProcess(blurRatio);
  150. // Set up pipeline
  151. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), this.SSAOOriginalSceneColorEffect, () => { return this._originalColorPostProcess; }, true));
  152. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), this.SSAORenderEffect, () => { return this._ssaoPostProcess; }, true));
  153. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), this.SSAOBlurHRenderEffect, () => { return this._blurHPostProcess; }, true));
  154. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), this.SSAOBlurVRenderEffect, () => { return this._blurVPostProcess; }, true));
  155. this.addEffect(new PostProcessRenderEffect(scene.getEngine(), this.SSAOCombineRenderEffect, () => { return this._ssaoCombinePostProcess; }, true));
  156. // Finish
  157. scene.postProcessRenderPipelineManager.addPipeline(this);
  158. if (cameras)
  159. scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline(name, cameras);
  160. }
  161. // Public Methods
  162. /**
  163. * Removes the internal pipeline assets and detatches the pipeline from the scene cameras
  164. */
  165. public dispose(disableGeometryBufferRenderer: boolean = false): void {
  166. for (var i = 0; i < this._scene.cameras.length; i++) {
  167. var camera = this._scene.cameras[i];
  168. this._originalColorPostProcess.dispose(camera);
  169. this._ssaoPostProcess.dispose(camera);
  170. this._blurHPostProcess.dispose(camera);
  171. this._blurVPostProcess.dispose(camera);
  172. this._ssaoCombinePostProcess.dispose(camera);
  173. }
  174. this._randomTexture.dispose();
  175. if (disableGeometryBufferRenderer)
  176. this._scene.disableGeometryBufferRenderer();
  177. this._scene.postProcessRenderPipelineManager.detachCamerasFromRenderPipeline(this._name, this._scene.cameras);
  178. super.dispose();
  179. }
  180. // Private Methods
  181. private _createBlurPostProcess(ssaoRatio: number, blurRatio: number): void {
  182. var samples = 16;
  183. this._samplerOffsets = [];
  184. var expensive = this.expensiveBlur;
  185. for (var i = -8; i < 8; i++) {
  186. this._samplerOffsets.push(i * 2 + 0.5);
  187. }
  188. this._blurHPostProcess = new PostProcess("BlurH", "ssao2", ["outSize", "samplerOffsets", "near", "far", "radius"], ["depthSampler"], ssaoRatio, null, Texture.TRILINEAR_SAMPLINGMODE, this._scene.getEngine(), false, "#define BILATERAL_BLUR\n#define BILATERAL_BLUR_H\n#define SAMPLES 16\n#define EXPENSIVE " + (expensive ? "1" : "0") + "\n");
  189. this._blurHPostProcess.onApply = (effect: Effect) => {
  190. effect.setFloat("outSize", this._ssaoCombinePostProcess.width);
  191. effect.setFloat("near", this._scene.activeCamera.minZ);
  192. effect.setFloat("far", this._scene.activeCamera.maxZ);
  193. effect.setFloat("radius", this.radius);
  194. effect.setTexture("depthSampler", this._depthTexture);
  195. if (this._firstUpdate) {
  196. effect.setArray("samplerOffsets", this._samplerOffsets);
  197. }
  198. };
  199. this._blurVPostProcess = new PostProcess("BlurV", "ssao2", ["outSize", "samplerOffsets", "near", "far", "radius"], ["depthSampler"], blurRatio, null, Texture.TRILINEAR_SAMPLINGMODE, this._scene.getEngine(), false, "#define BILATERAL_BLUR\n#define BILATERAL_BLUR_V\n#define SAMPLES 16\n#define EXPENSIVE " + (expensive ? "1" : "0") + "\n");
  200. this._blurVPostProcess.onApply = (effect: Effect) => {
  201. effect.setFloat("outSize", this._ssaoCombinePostProcess.height);
  202. effect.setFloat("near", this._scene.activeCamera.minZ);
  203. effect.setFloat("far", this._scene.activeCamera.maxZ);
  204. effect.setFloat("radius", this.radius);
  205. effect.setTexture("depthSampler", this._depthTexture);
  206. if (this._firstUpdate) {
  207. effect.setArray("samplerOffsets", this._samplerOffsets);
  208. this._firstUpdate = false;
  209. }
  210. };
  211. }
  212. public _rebuild() {
  213. this._firstUpdate = true;
  214. super._rebuild();
  215. }
  216. private _generateHemisphere(): number[] {
  217. var numSamples = this.samples;
  218. var result = [];
  219. var vector, scale;
  220. var rand = (min, max) => {
  221. return Math.random() * (max - min) + min;
  222. }
  223. var lerp = (start, end, percent) => {
  224. return (start + percent*(end - start));
  225. }
  226. var i = 0;
  227. var normal = new BABYLON.Vector3(0, 0, 1);
  228. while (i < numSamples) {
  229. vector = new BABYLON.Vector3(
  230. rand(-1.0, 1.0),
  231. rand(-1.0, 1.0),
  232. rand(0.30, 1.0));
  233. vector.normalize();
  234. scale = i / numSamples;
  235. scale = lerp(0.1, 1.0, scale*scale);
  236. vector.scaleInPlace(scale);
  237. result.push(vector.x, vector.y, vector.z);
  238. i++;
  239. }
  240. return result;
  241. }
  242. private _createSSAOPostProcess(ratio: number): void {
  243. var numSamples = this.samples;
  244. this._sampleSphere = this._generateHemisphere();
  245. this._ssaoPostProcess = new PostProcess("ssao2", "ssao2",
  246. [
  247. "sampleSphere", "samplesFactor", "randTextureTiles", "totalStrength", "radius",
  248. "base", "range", "projection", "near", "far", "texelSize",
  249. "xViewport", "yViewport", "maxZ", "minZAspect"
  250. ],
  251. ["randomSampler", "normalSampler"],
  252. ratio, null, Texture.BILINEAR_SAMPLINGMODE,
  253. this._scene.getEngine(), false,
  254. "#define SAMPLES " + numSamples + "\n#define SSAO");
  255. this._ssaoPostProcess.onApply = (effect: Effect) => {
  256. if (this._firstUpdate) {
  257. effect.setArray3("sampleSphere", this._sampleSphere);
  258. effect.setFloat("randTextureTiles", 4.0);
  259. }
  260. effect.setFloat("samplesFactor", 1 / this.samples);
  261. effect.setFloat("totalStrength", this.totalStrength);
  262. effect.setFloat2("texelSize", 1 / this._ssaoPostProcess.width, 1 / this._ssaoPostProcess.height);
  263. effect.setFloat("radius", this.radius);
  264. effect.setFloat("maxZ", this.maxZ);
  265. effect.setFloat("minZAspect", this.minZAspect);
  266. effect.setFloat("base", this.base);
  267. effect.setFloat("near", this._scene.activeCamera.minZ);
  268. effect.setFloat("far", this._scene.activeCamera.maxZ);
  269. effect.setFloat("xViewport", Math.tan(this._scene.activeCamera.fov / 2) * this._scene.getEngine().getAspectRatio(this._scene.activeCamera, true));
  270. effect.setFloat("yViewport", Math.tan(this._scene.activeCamera.fov / 2) );
  271. effect.setMatrix("projection", this._scene.getProjectionMatrix());
  272. effect.setTexture("textureSampler", this._depthTexture);
  273. effect.setTexture("normalSampler", this._normalTexture);
  274. effect.setTexture("randomSampler", this._randomTexture);
  275. };
  276. }
  277. private _createSSAOCombinePostProcess(ratio: number): void {
  278. this._ssaoCombinePostProcess = new PostProcess("ssaoCombine", "ssaoCombine", [], ["originalColor"],
  279. ratio, null, Texture.BILINEAR_SAMPLINGMODE,
  280. this._scene.getEngine(), false);
  281. this._ssaoCombinePostProcess.onApply = (effect: Effect) => {
  282. effect.setTextureFromPostProcess("originalColor", this._originalColorPostProcess);
  283. };
  284. }
  285. private _createRandomTexture(): void {
  286. var size = 512;
  287. this._randomTexture = new DynamicTexture("SSAORandomTexture", size, this._scene, false, Texture.TRILINEAR_SAMPLINGMODE);
  288. this._randomTexture.wrapU = Texture.WRAP_ADDRESSMODE;
  289. this._randomTexture.wrapV = Texture.WRAP_ADDRESSMODE;
  290. var context = this._randomTexture.getContext();
  291. var rand = (min, max) => {
  292. return Math.random() * (max - min) + min;
  293. }
  294. var randVector = Vector3.Zero();
  295. for (var x = 0; x < size; x++) {
  296. for (var y = 0; y < size; y++) {
  297. randVector.x = rand(0.0, 1.0);
  298. randVector.y = rand(0.0, 1.0);
  299. randVector.z = 0.0;
  300. randVector.normalize();
  301. randVector.scaleInPlace(255);
  302. randVector.x = Math.floor(randVector.x);
  303. randVector.y = Math.floor(randVector.y);
  304. context.fillStyle = 'rgb(' + randVector.x + ', ' + randVector.y + ', ' + randVector.z + ')';
  305. context.fillRect(x, y, 1, 1);
  306. }
  307. }
  308. this._randomTexture.update(false);
  309. }
  310. }
  311. }