فهرست منبع

ported but untested

Benjamin Guignabert 5 سال پیش
والد
کامیت
860b06f7b0
2فایلهای تغییر یافته به همراه62 افزوده شده و 15 حذف شده
  1. 43 1
      src/PostProcesses/SubSurfaceScatteringPostProcess.ts
  2. 19 14
      src/Shaders/subSurfaceScattering.fragment.fx

+ 43 - 1
src/PostProcesses/SubSurfaceScatteringPostProcess.ts

@@ -5,6 +5,7 @@ import { Texture } from "../Materials/Textures/texture";
 import { PostProcess, PostProcessOptions } from "./postProcess";
 import { Engine } from "../Engines/engine";
 import { Scene } from "../scene";
+import { Color3 } from "../Maths/math.color";
 import { Constants } from "../Engines/constants";
 
 import "../Shaders/sceneCompositor.fragment";
@@ -19,8 +20,11 @@ export class SubSurfaceScatteringPostProcess extends PostProcess {
     /** @hidden */
     public texelHeight: number;
 
+    private _diffusionColor: Color3 = new Color3(0.7568628, 0.32156864, 0.20000002);
+    private _filterRadius: number;
+
     constructor(name: string, scene: Scene, options: number | PostProcessOptions, camera: Nullable<Camera> = null, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Constants.TEXTURETYPE_UNSIGNED_INT) {
-        super(name, "subSurfaceScattering", ["texelSize"], ["irradianceSampler"], options, camera, samplingMode || Texture.BILINEAR_SAMPLINGMODE, engine, reusable, null, textureType, "postprocess", undefined, true);
+        super(name, "subSurfaceScattering", ["texelSize", "filterRadius", "viewportSize"], ["irradianceSampler"], options, camera, samplingMode || Texture.BILINEAR_SAMPLINGMODE, engine, reusable, null, textureType, "postprocess", undefined, true);
 
         const defines = this._getDefines();
         this.updateEffect(defines);
@@ -29,7 +33,13 @@ export class SubSurfaceScatteringPostProcess extends PostProcess {
             var texelSize = this.texelSize;
             effect.setFloat2("texelSize", texelSize.x, texelSize.y);
             effect.setTexture("irradianceSampler", scene.highDefinitionMRT.textures[2]);
+            effect.setFloat("filterRadius", this._filterRadius);
+            effect.setFloat2("viewportSize", 
+                Math.tan(scene.activeCamera!.fov / 2) * scene.getEngine().getAspectRatio(scene.activeCamera!, true),
+                Math.tan(scene.activeCamera!.fov / 2));
         });
+
+        this._filterRadius = this._getDiffusionProfileParameters();
     }
 
     private _getDefines(): Nullable<string> {
@@ -40,4 +50,36 @@ export class SubSurfaceScatteringPostProcess extends PostProcess {
 
         return "";
     }
+
+    private _getDiffusionProfileParameters()
+    {
+        const cdf = 0.997;
+        // Importance sample the normalized diffuse reflectance profile for the computed value of 's'.
+        // ------------------------------------------------------------------------------------
+        // R[r, phi, s]   = s * (Exp[-r * s] + Exp[-r * s / 3]) / (8 * Pi * r)
+        // PDF[r, phi, s] = r * R[r, phi, s]
+        // CDF[r, s]      = 1 - 1/4 * Exp[-r * s] - 3/4 * Exp[-r * s / 3]
+        // ------------------------------------------------------------------------------------
+        // We importance sample the color channel with the widest scattering distance.
+        const maxScatteringDistance = Math.max(this._diffusionColor.r, this._diffusionColor.g, this._diffusionColor.b);
+
+        return this._sampleBurleyDiffusionProfile(cdf, maxScatteringDistance);
+    }
+    // https://zero-radiance.github.io/post/sampling-diffusion/
+    // Performs sampling of a Normalized Burley diffusion profile in polar coordinates.
+    // 'u' is the random number (the value of the CDF): [0, 1).
+    // rcp(s) = 1 / ShapeParam = ScatteringDistance.
+    // Returns the sampled radial distance, s.t. (u = 0 -> r = 0) and (u = 1 -> r = Inf).
+    private _sampleBurleyDiffusionProfile(u: number, rcpS: number)
+    {
+        u = 1 - u; // Convert CDF to CCDF
+
+        let g = 1 + (4 * u) * (2 * u + Math.sqrt(1 + (4 * u) * u));
+        let n = Math.pow(g, -1.0/3.0);                      // g^(-1/3)
+        let p = (g * n) * n;                                   // g^(+1/3)
+        let c = 1 + p + n;                                     // 1 + g^(+1/3) + g^(-1/3)
+        let x = 3 * Math.log(c / (4 * u));
+
+        return x * rcpS;
+    }
 }

+ 19 - 14
src/Shaders/subSurfaceScattering.fragment.fx

@@ -6,10 +6,14 @@ varying vec2 texelSize;
 uniform sampler2D textureSampler;
 uniform sampler2D irradianceSampler;
 
+uniform float filterRadius;
+uniform vec2 viewportSize;
+
 const float LOG2_E = 1.4426950408889634;
 const float PI = 3.1415926535897932;
-const int SSS_PIXELS_PER_SAMPLE = 32;
+const int SSS_PIXELS_PER_SAMPLE = 1;
 const int _SssSampleBudget = 32;
+
 #define rcp(x) 1. / x
 #define Sq(x) x * x
 
@@ -27,7 +31,7 @@ vec3 EvalBurleyDiffusionProfile(float r, vec3 S)
 // rcp(s) = 1 / ShapeParam = ScatteringDistance.
 // 'r' is the sampled radial distance, s.t. (u = 0 -> r = 0) and (u = 1 -> r = Inf).
 // rcp(Pdf) is the reciprocal of the corresponding PDF value.
-vec2 SampleBurleyDiffusionProfile(float u, float rcpS, out float r, out float rcpPdf)
+vec2 SampleBurleyDiffusionProfile(float u, float rcpS)
 {
     u = 1. - u; // Convert CDF to CCDF
 
@@ -79,17 +83,17 @@ void main(void)
     //   ior: 1.4
     //   hash: 1075477546
 
+    // TODO : uniforms
 	float  distScale     = 1.; //sssData.subsurfaceMask;
 	vec3 S             = vec3(0.7568628, 0.32156864, 0.20000002); //_ShapeParamsAndMaxScatterDists[profileIndex].rgb; -> diffusion color
-	float  d             = 0.5; //_ShapeParamsAndMaxScatterDists[profileIndex].a; -> max scatter dist
-	float  metersPerUnit = 1.; //_WorldScalesAndFilterRadiiAndThicknessRemaps[profileIndex].x;
-	float  filterRadius  = 0.5; //_WorldScalesAndFilterRadiiAndThicknessRemaps[profileIndex].y; // In millimeters
+	float  d             = 0.7568628; //_ShapeParamsAndMaxScatterDists[profileIndex].a; -> max scatter dist
+	float  metersPerUnit = 0.01; //_WorldScalesAndFilterRadiiAndThicknessRemaps[profileIndex].x;
 
 	// Reconstruct the view-space position corresponding to the central sample.
 	vec2 centerPosNDC = vUV;
 	vec2 cornerPosNDC = vUV + 0.5 * texelSize;
-	vec3 centerPosVS  = vec3(0.); // TODO -> ComputeViewSpacePosition(centerPosNDC, centerDepth, UNITY_MATRIX_I_P);
-	vec3 cornerPosVS  = vec3(0.); // TODO -> ComputeViewSpacePosition(cornerPosNDC, centerDepth, UNITY_MATRIX_I_P);
+	vec3 centerPosVS  = vec3(centerPosNDC * viewportSize, 1.0) * centerDepth; // ComputeViewSpacePosition(centerPosNDC, centerDepth, UNITY_MATRIX_I_P);
+	vec3 cornerPosVS  = vec3(cornerPosNDC * viewportSize, 1.0) * centerDepth; // ComputeViewSpacePosition(cornerPosNDC, centerDepth, UNITY_MATRIX_I_P);
 
 	// Rescaling the filter is equivalent to inversely scaling the world.
 	float mmPerUnit  = 1000. * (metersPerUnit * rcp(distScale));
@@ -97,7 +101,7 @@ void main(void)
 
 	// Compute the view-space dimensions of the pixel as a quad projected onto geometry.
 	// Assuming square pixels, both X and Y are have the same dimensions.
-	float unitsPerPixel = 2 * abs(cornerPosVS.x - centerPosVS.x);
+	float unitsPerPixel = 2. * abs(cornerPosVS.x - centerPosVS.x);
 	float pixelsPerMm   = rcp(unitsPerPixel) * unitsPerMm;
 
 	// Area of a disk.
@@ -105,8 +109,8 @@ void main(void)
 	int  sampleCount  = (int)(filterArea * rcp(SSS_PIXELS_PER_SAMPLE));
 	int  sampleBudget = _SssSampleBudget;
 
-	int   texturingMode = 0; // GetSubsurfaceScatteringTexturingMode(profileIndex);
-	vec3 albedo  = vec3(1.) // texture2D(albedoSampler, vUV); //ApplySubsurfaceScatteringTexturingMode(texturingMode, sssData.diffuseColor);
+	int texturingMode = 0; // GetSubsurfaceScatteringTexturingMode(profileIndex);
+	vec3 albedo  = vec3(0.5) // texture2D(albedoSampler, vUV); //ApplySubsurfaceScatteringTexturingMode(texturingMode, sssData.diffuseColor);
 
 	if (distScale == 0. || sampleCount < 1)
 	{
@@ -132,7 +136,7 @@ void main(void)
     {
         // Integrate over the image or tangent plane in the view space.
         EvaluateSample(i, n, S, d, centerPosVS, mmPerUnit, pixelsPerMm,
-                       phase, tangentX, tangentY, projMatrix,
+                       phase, tangentX, tangentY,
                        totalIrradiance, totalWeight);
     }
 
@@ -144,9 +148,10 @@ void main(void)
 	// gl_FragColor = mix(texture2D(textureSampler, vUV), centerIrradiance, 0.5);
 }
 
+// TODO : inout vec3 totalIrradiance, inout vec3 totalWeight
 void EvaluateSample(uint i, uint n, vec3 S, float d, vec3 centerPosVS, float mmPerUnit, float pixelsPerMm,
-                    float phase, vec3 tangentX, vec3 tangentY, mat projMatrix,
-                    inout vec3 totalIrradiance, inout vec3 totalWeight)
+                    float phase, vec3 tangentX, vec3 tangentY,
+                    vec3 totalIrradiance, vec3 totalWeight)
 {
     // The sample count is loop-invariant.
     const float scale  = rcp(n);
@@ -181,7 +186,7 @@ void EvaluateSample(uint i, uint n, vec3 S, float d, vec3 centerPosVS, float mmP
     // position = vUV + round(vec * pixelsPerMm);
     // Note that (int) truncates towards 0, while floor() truncates towards -Inf!
 
-    position = vUV + (int2)round((pixelsPerMm * r) * vec2(cosPsi, sinPsi)) / texelSize;
+    position = vUV + round((pixelsPerMm * r) * vec2(cosPsi, sinPsi)) * texelSize;
     xy2      = r * r;
 
     vec4 textureSample = sampler2D(irradianceSampler, position);