babylon.ssao2RenderingPipeline.ts 17 KB

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