ssao2RenderingPipeline.ts 21 KB

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