ssao2RenderingPipeline.ts 20 KB

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