SubSurfaceScatteringPostProcess.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import { Nullable } from "../types";
  2. import { Camera } from "../Cameras/camera";
  3. import { Effect } from "../Materials/effect";
  4. import { Texture } from "../Materials/Textures/texture";
  5. import { PostProcess, PostProcessOptions } from "./postProcess";
  6. import { Engine } from "../Engines/engine";
  7. import { Scene } from "../scene";
  8. import { Color3 } from "../Maths/math.color";
  9. import { Constants } from "../Engines/constants";
  10. import { Logger } from "../Misc/logger";
  11. import "../Shaders/sceneCompositor.fragment";
  12. import "../Shaders/postprocess.vertex";
  13. /**
  14. * Scene compositor post process
  15. */
  16. export class SubSurfaceScatteringPostProcess extends PostProcess {
  17. /** @hidden */
  18. public texelWidth: number;
  19. /** @hidden */
  20. public texelHeight: number;
  21. private _diffusionS: number[] = [];
  22. private _filterRadii: number[] = [];
  23. private _diffusionD: number[] = [];
  24. constructor(name: string, scene: Scene, options: number | PostProcessOptions, camera: Nullable<Camera> = null, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Constants.TEXTURETYPE_UNSIGNED_INT) {
  25. super(name, "subSurfaceScattering", ["texelSize", "viewportSize", "metersPerUnit"], ["diffusionS", "diffusionD", "filterRadii", "irradianceSampler", "depthSampler", "albedoSampler"], options, camera, samplingMode || Texture.BILINEAR_SAMPLINGMODE, engine, reusable, null, textureType, "postprocess", undefined, true);
  26. this._scene = scene;
  27. const defines = this._getDefines();
  28. this.updateEffect(defines);
  29. // Adding default diffusion profile
  30. this.addDiffusionProfile(new Color3(1, 1, 1));
  31. this.onApplyObservable.add((effect: Effect) => {
  32. var texelSize = this.texelSize;
  33. effect.setFloat("metersPerUnit", scene.metersPerUnit);
  34. effect.setFloat2("texelSize", texelSize.x, texelSize.y);
  35. effect.setTexture("irradianceSampler", scene.highDefinitionMRT.textures[1]);
  36. effect.setTexture("depthSampler", scene.highDefinitionMRT.textures[2]);
  37. effect.setTexture("albedoSampler", scene.highDefinitionMRT.textures[3]);
  38. effect.setFloat2("viewportSize",
  39. Math.tan(scene.activeCamera!.fov / 2) * scene.getEngine().getAspectRatio(scene.activeCamera!, true),
  40. Math.tan(scene.activeCamera!.fov / 2));
  41. effect.setArray3("diffusionS", this._diffusionS);
  42. effect.setArray("diffusionD", this._diffusionD);
  43. effect.setArray("filterRadii", this._filterRadii);
  44. });
  45. }
  46. private _getDefines(): Nullable<string> {
  47. const engine = this.getEngine();
  48. if (!engine) {
  49. return null;
  50. }
  51. let defines = "";
  52. if (this._scene.imageProcessingConfiguration.applyByPostProcess) {
  53. // We must output linear color for post process
  54. defines = defines + "#define LINEAR_OUTPUT\n";
  55. }
  56. return defines;
  57. }
  58. public addDiffusionProfile(color: Color3) : number {
  59. if (this._diffusionD.length >= 5) {
  60. // We only suppport 5 diffusion profiles
  61. Logger.Error("You already reached the maximum number of diffusion profiles.");
  62. return -1;
  63. }
  64. // Do not add doubles
  65. for (let i = 0; i < this._diffusionS.length / 3; i++) {
  66. if (this._diffusionS[i * 3] === color.r &&
  67. this._diffusionS[i * 3 + 1] === color.g &&
  68. this._diffusionS[i * 3 + 2] === color.b) {
  69. return i;
  70. }
  71. }
  72. this._diffusionS.push(color.r, color.b, color.g);
  73. this._diffusionD.push(Math.max(Math.max(color.r, color.b), color.g));
  74. this._filterRadii.push(this.getDiffusionProfileParameters(color));
  75. return this._diffusionD.length - 1;
  76. }
  77. public getDiffusionProfileParameters(color: Color3)
  78. {
  79. const cdf = 0.997;
  80. // Importance sample the normalized diffuse reflectance profile for the computed value of 's'.
  81. // ------------------------------------------------------------------------------------
  82. // R[r, phi, s] = s * (Exp[-r * s] + Exp[-r * s / 3]) / (8 * Pi * r)
  83. // PDF[r, phi, s] = r * R[r, phi, s]
  84. // CDF[r, s] = 1 - 1/4 * Exp[-r * s] - 3/4 * Exp[-r * s / 3]
  85. // ------------------------------------------------------------------------------------
  86. // We importance sample the color channel with the widest scattering distance.
  87. const maxScatteringDistance = Math.max(color.r, color.g, color.b);
  88. return this._sampleBurleyDiffusionProfile(cdf, maxScatteringDistance);
  89. }
  90. // https://zero-radiance.github.io/post/sampling-diffusion/
  91. // Performs sampling of a Normalized Burley diffusion profile in polar coordinates.
  92. // 'u' is the random number (the value of the CDF): [0, 1).
  93. // rcp(s) = 1 / ShapeParam = ScatteringDistance.
  94. // Returns the sampled radial distance, s.t. (u = 0 -> r = 0) and (u = 1 -> r = Inf).
  95. private _sampleBurleyDiffusionProfile(u: number, rcpS: number)
  96. {
  97. u = 1 - u; // Convert CDF to CCDF
  98. let g = 1 + (4 * u) * (2 * u + Math.sqrt(1 + (4 * u) * u));
  99. let n = Math.pow(g, -1.0 / 3.0); // g^(-1/3)
  100. let p = (g * n) * n; // g^(+1/3)
  101. let c = 1 + p + n; // 1 + g^(+1/3) + g^(-1/3)
  102. let x = 3 * Math.log(c / (4 * u));
  103. return x * rcpS;
  104. }
  105. }