浏览代码

adding diffusion profiles

Benjamin Guignabert 5 年之前
父节点
当前提交
fbf3ad2126

+ 0 - 3
src/Materials/Background/backgroundMaterial.ts

@@ -817,9 +817,6 @@ export class BackgroundMaterial extends PushMaterial {
         // Values that need to be evaluated on every frame
         MaterialHelper.PrepareDefinesForFrameBoundValues(scene, engine, defines, useInstances, null, subMesh.getRenderingMesh().hasThinInstances);
 
-        // Deferred
-        MaterialHelper.PrepareDefinesForDeferred(scene, defines);
-
         // Attribs
         if (MaterialHelper.PrepareDefinesForAttributes(mesh, defines, false, true, false)) {
             if (mesh) {

+ 7 - 4
src/Materials/PBR/pbrBaseMaterial.ts

@@ -675,8 +675,12 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         this.markAsDirty(Constants.MATERIAL_TextureDirtyFlag);
     }
 
+    /**
+     * Should this material render to several textures at once
+     */
+    public forceRenderToMRT: boolean = false;
     public get shouldRenderToMRT() {
-        return true;
+        return this.subSurface.isScatteringEnabled || this.forceRenderToMRT;
     }
 
     /**
@@ -1300,6 +1304,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             onError: onError,
             indexParameters: { maxSimultaneousLights: this._maxSimultaneousLights, maxSimultaneousMorphTargets: defines.NUM_MORPH_INFLUENCERS },
             processFinalCode: csnrOptions.processFinalCode,
+            multiTarget: this.shouldRenderToMRT
         }, engine);
     }
 
@@ -1315,9 +1320,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         MaterialHelper.PrepareDefinesForMultiview(scene, defines);
 
         // Deferred
-        if (this.shouldRenderToMRT) {
-            MaterialHelper.PrepareDefinesForDeferred(scene, defines);
-        }
+        MaterialHelper.PrepareDefinesForDeferred(scene, defines, this.shouldRenderToMRT);
 
         // Textures
         defines.METALLICWORKFLOW = this.isMetallicWorkflow();

+ 18 - 1
src/Materials/PBR/pbrSubSurfaceConfiguration.ts

@@ -43,6 +43,12 @@ export interface IMaterialSubSurfaceDefines {
     _areTexturesDirty: boolean;
 }
 
+export enum SubsurfaceDiffusionProfile {
+    NEUTRAL = 0,
+    SKIN = 1,
+    FOLIAGE = 2,
+};
+
 /**
  * Define the code related to the sub surface parameters of the pbr material.
  */
@@ -72,6 +78,12 @@ export class PBRSubSurfaceConfiguration {
     public isScatteringEnabled = false;
 
     /**
+     * Diffusion profile for subsurface scattering.
+     * Useful for better scattering in the skins or foliages.
+     */
+    public scatteringDiffusionProfile = SubsurfaceDiffusionProfile.NEUTRAL;
+
+    /**
      * Defines the refraction intensity of the material.
      * The refraction when enabled replaces the Diffuse part of the material.
      * The intensity helps transitionning between diffuse and refraction.
@@ -369,6 +381,10 @@ export class PBRSubSurfaceConfiguration {
                 }
             }
 
+
+            if (this.isScatteringEnabled) {
+                uniformBuffer.updateFloat("scatteringDiffusionProfile", this.scatteringDiffusionProfile);
+            }
             uniformBuffer.updateColor3("vDiffusionDistance", this.diffusionDistance);
 
             uniformBuffer.updateFloat4("vTintColor", this.tintColor.r,
@@ -554,7 +570,7 @@ export class PBRSubSurfaceConfiguration {
             "vDiffusionDistance", "vTintColor", "vSubSurfaceIntensity",
             "vRefractionMicrosurfaceInfos", "vRefractionFilteringInfo",
             "vRefractionInfos", "vThicknessInfos", "vThicknessParam",
-            "refractionMatrix", "thicknessMatrix");
+            "refractionMatrix", "thicknessMatrix", "scatteringDiffusionProfile");
     }
 
     /**
@@ -581,6 +597,7 @@ export class PBRSubSurfaceConfiguration {
         uniformBuffer.addUniform("vDiffusionDistance", 3);
         uniformBuffer.addUniform("vTintColor", 4);
         uniformBuffer.addUniform("vSubSurfaceIntensity", 3);
+        uniformBuffer.addUniform("scatteringDiffusionProfile", 1);
     }
 
     /**

+ 7 - 0
src/Materials/effect.ts

@@ -69,6 +69,10 @@ export interface IEffectCreationOptions {
      * If provided, will be called two times with the vertex and fragment code so that this code can be updated before it is compiled by the GPU
      */
     processFinalCode?: Nullable<(shaderType: string, code: string) => string>;
+    /**
+     * Is this effect rendering to several color attachments ?
+     */
+    multiTarget?: boolean;
 }
 
 /**
@@ -139,6 +143,8 @@ export class Effect implements IDisposable {
 
     /** @hidden */
     public _bonesComputationForcedToCPU = false;
+    /** @hidden */
+    public _multiTarget: boolean = false;
 
     private static _uniqueIdSeed = 0;
     private _engine: Engine;
@@ -208,6 +214,7 @@ export class Effect implements IDisposable {
             this._fallbacks = options.fallbacks;
             this._indexParameters = options.indexParameters;
             this._transformFeedbackVaryings = options.transformFeedbackVaryings || null;
+            this._multiTarget = !!options.multiTarget;
 
             if (options.uniformBuffersNames) {
                 this._uniformBuffersNamesList = options.uniformBuffersNames.slice();

+ 2 - 2
src/Materials/materialHelper.ts

@@ -300,10 +300,10 @@ export class MaterialHelper {
      * @param scene The scene we are intending to draw
      * @param defines The defines to update
      */
-    public static PrepareDefinesForDeferred(scene: Scene, defines: any) {
+    public static PrepareDefinesForDeferred(scene: Scene, defines: any, shouldRenderToMRT: boolean) {
         var previousDeferred = defines.HIGH_DEFINITION_PIPELINE;
 
-        if (scene.highDefinitionPipeline) {
+        if (scene.highDefinitionPipeline && shouldRenderToMRT) {
             defines.HIGH_DEFINITION_PIPELINE = true;
             defines.SCENE_MRT_COUNT = scene.mrtCount;
         } else {

+ 0 - 5
src/Materials/standardMaterial.ts

@@ -825,11 +825,6 @@ export class StandardMaterial extends PushMaterial {
         // Multiview
         MaterialHelper.PrepareDefinesForMultiview(scene, defines);
 
-        // Deferred
-        if (this.shouldRenderToMRT) {
-            MaterialHelper.PrepareDefinesForDeferred(scene, defines);
-        }
-
         // Textures
         if (defines._areTexturesDirty) {
             defines._needUVs = false;

+ 3 - 3
src/Meshes/mesh.ts

@@ -1798,9 +1798,6 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
             return this;
         }
 
-        // Render to MRT
-        scene.drawBuffers(material);
-
         // Material
         if (!instanceDataStorage.isFrozen || !this._effectiveMaterial || this._effectiveMaterial !== material) {
             if (material._storeEffectOnSubMeshes) {
@@ -1834,6 +1831,9 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
             return this;
         }
 
+        // Render to MRT
+        scene.drawBuffers(effect);
+
         const effectiveMesh = effectiveMeshReplacement || this._effectiveMesh;
 
         var sideOrientation: Nullable<number>;

+ 4 - 8
src/PostProcesses/SubSurfaceScatteringPostProcess.ts

@@ -20,11 +20,8 @@ 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", "filterRadius", "viewportSize"], ["inputSampler", "irradianceSampler", "depthSampler", "albedoSampler"], options, camera, samplingMode || Texture.BILINEAR_SAMPLINGMODE, engine, reusable, null, textureType, "postprocess", undefined, true);
+        super(name, "subSurfaceScattering", ["texelSize", "viewportSize", "metersPerUnit"], ["inputSampler", "irradianceSampler", "depthSampler", "albedoSampler"], options, camera, samplingMode || Texture.BILINEAR_SAMPLINGMODE, engine, reusable, null, textureType, "postprocess", undefined, true);
         this._scene = scene;
 
         const defines = this._getDefines();
@@ -32,18 +29,17 @@ export class SubSurfaceScatteringPostProcess extends PostProcess {
 
         this.onApplyObservable.add((effect: Effect) => {
             var texelSize = this.texelSize;
+            effect.setFloat("metersPerUnit", scene.metersPerUnit);
             effect.setFloat2("texelSize", texelSize.x, texelSize.y);
             effect.setTexture("inputSampler", scene.highDefinitionMRT.textures[4]);
             effect.setTexture("irradianceSampler", scene.highDefinitionMRT.textures[1]);
             effect.setTexture("depthSampler", scene.highDefinitionMRT.textures[2]);
             effect.setTexture("albedoSampler", scene.highDefinitionMRT.textures[3]);
-            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> {
@@ -62,7 +58,7 @@ export class SubSurfaceScatteringPostProcess extends PostProcess {
         return defines;
     }
 
-    private _getDiffusionProfileParameters()
+    public getDiffusionProfileParameters(color: Color3)
     {
         const cdf = 0.997;
         // Importance sample the normalized diffuse reflectance profile for the computed value of 's'.
@@ -72,7 +68,7 @@ export class SubSurfaceScatteringPostProcess extends PostProcess {
         // 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);
+        const maxScatteringDistance = Math.max(color.r, color.g, color.b);
 
         return this._sampleBurleyDiffusionProfile(cdf, maxScatteringDistance);
     }

+ 12 - 0
src/Shaders/ShadersInclude/diffusionProfile.fx

@@ -0,0 +1,12 @@
+struct DiffusionProfile
+{
+  vec3 S;
+  float d;
+  float filterRadius;
+};
+
+DiffusionProfile diffusionProfiles[3] = DiffusionProfile[3](
+	DiffusionProfile(vec3(1., 1., 1.), 1., 16.564398753373407), // neutral
+	DiffusionProfile(vec3(0.7568628, 0.32156864, 0.20000002), 0.7568628, 12.536977220794705), // skin
+	DiffusionProfile(vec3(0.7568628, 0.7019608, 0.24313727), 0.7568628, 12.536977220794705) // foliage
+);

+ 12 - 1
src/Shaders/ShadersInclude/helperFunctions.fx

@@ -1,11 +1,11 @@
 const float PI = 3.1415926535897932384626433832795;
+const float HALF_MIN = 5.96046448e-08; // Smallest positive half.
 
 const float LinearEncodePowerApprox = 2.2;
 const float GammaEncodePowerApprox = 1.0 / LinearEncodePowerApprox;
 const vec3 LuminanceEncodeApprox = vec3(0.2126, 0.7152, 0.0722);
 
 const float Epsilon = 0.0000001;
-
 #define saturate(x)         clamp(x, 0.0, 1.0)
 
 #define absEps(x)           abs(x) + Epsilon
@@ -88,6 +88,17 @@ float getLuminance(vec3 color)
     return clamp(dot(color, LuminanceEncodeApprox), 0., 1.);
 }
 
+vec3 tagLightingForSSS(vec3 color) {
+    color.b = max(color.b, HALF_MIN);
+
+    return color;
+}
+
+bool testLightingForSSS(vec3 color)
+{
+    return color.b > 0.;
+}
+
 // https://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl
 float getRand(vec2 seed) {
     return fract(sin(dot(seed.xy ,vec2(12.9898,78.233))) * 43758.5453);

+ 4 - 0
src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx

@@ -134,4 +134,8 @@ uniform mat4 view;
     uniform vec3 vDiffusionDistance;
     uniform vec4 vTintColor;
     uniform vec3 vSubSurfaceIntensity;
+
+    #ifdef SS_SCATTERING
+        uniform float scatteringDiffusionProfile;
+    #endif
 #endif

+ 1 - 0
src/Shaders/ShadersInclude/pbrUboDeclaration.fx

@@ -69,6 +69,7 @@ uniform Material
     uniform vec3 vDiffusionDistance;
     uniform vec4 vTintColor;
     uniform vec3 vSubSurfaceIntensity;
+    uniform float scatteringDiffusionProfile;
 
     uniform vec4 vDetailInfos;
     uniform mat4 detailMatrix;

+ 6 - 41
src/Shaders/pbr.fragment.fx

@@ -506,51 +506,16 @@ void main(void) {
         #endif
     #endif
 
-    // BJS
-    // vec3 finalDiffuse = diffuseBase; // shadows, lightmap and stuff
-    // finalDiffuse *= surfaceAlbedo.rgb;
-    // finalDiffuse = max(finalDiffuse, 0.0);
-    // finalDiffuse *= vLightingIntensity.x;
-    // finalDiffuse *= ambientOcclusionForDirectDiffuse;
-    
-    // #ifdef REFLECTION
-    //     vec3 finalIrradiance = reflectionOut.environmentIrradiance;
-
-    //     #if defined(CLEARCOAT)
-    //         finalIrradiance *= clearcoatOut.conservationFactor;
-    //         #if defined(CLEARCOAT_TINT)
-    //             finalIrradiance *= clearcoatOut.absorption;
-    //         #endif
-    //     #endif
-
-    //     #if defined(SS_REFRACTION)
-    //         finalIrradiance *= subSurfaceOut.refractionFactorForIrradiance;
-    //     #endif
-
-    //     #if defined(SS_TRANSLUCENCY)
-    //         finalIrradiance += subSurfaceOut.refractionIrradiance;
-    //     #endif
-
-    //     finalIrradiance *= surfaceAlbedo.rgb;
-    //     finalIrradiance *= vLightingIntensity.z;
-    //     finalIrradiance *= aoOut.ambientOcclusionColor;
-    // #endif 
-
-    // UNITY
-    // float3 modifiedDiffuseColor = GetModifiedDiffuseColorForSSS(bsdfData);
-    // diffuseLighting = modifiedDiffuseColor * lighting.direct.diffuse + builtinData.bakeDiffuseLighting + builtinData.emissiveColor;
-    // diffuseLighting = lerp(diffuseLighting, lighting.indirect.specularTransmitted, bsdfData.transmittanceMask * _EnableSSRefraction);
-
-    // finalDiffuse and finalIrradiance are already multiplied by surfaceAlbedo
-    // What about :
-    // Lightmaps ? (can we consider them as pure diffuse ?)
-    // AO and shadows, should they dim the diffuseLight ? (right now they are)
     vec3 sqAlbedo = sqrt(surfaceAlbedo); // for pre and post scatter
 
     // Irradiance is diffuse * surfaceAlbedo
-    gl_FragData[0] = vec4(finalColor.rgb - irradiance, 1.0); // Lit without irradiance
+    gl_FragData[0] = vec4(finalColor.rgb - irradiance, finalColor.a); // Lit without irradiance
     irradiance /= sqAlbedo;
-    gl_FragData[1] = vec4(irradiance, 1.0); // irradiance, for pre and post scatter
+    #ifdef SS_SCATTERING
+    gl_FragData[1] = vec4(tagLightingForSSS(irradiance), scatteringDiffusionProfile / 255.); // Irradiance + SS diffusion profile
+    #else
+    gl_FragData[1] = vec4(irradiance, 1.0); // Irradiance
+    #endif
     gl_FragData[2] = vec4(vViewPos.z, 0.0, 0.0, 1.0); // Linear depth
     gl_FragData[3] = vec4(sqAlbedo, 1.0); // albedo, for pre and post scatter
 #endif

+ 11 - 36
src/Shaders/subSurfaceScattering.fragment.fx

@@ -1,6 +1,7 @@
 // Samplers
 #include<fibonacci>
 #include<helperFunctions>
+#include<diffusionProfile>
 
 varying vec2 vUV;
 uniform vec2 texelSize;
@@ -9,8 +10,8 @@ uniform sampler2D irradianceSampler;
 uniform sampler2D depthSampler;
 uniform sampler2D albedoSampler;
 
-uniform float filterRadius;
 uniform vec2 viewportSize;
+uniform float metersPerUnit;
 
 const float LOG2_E = 1.4426950408889634;
 const float SSS_PIXELS_PER_SAMPLE = 4.;
@@ -82,11 +83,6 @@ vec3 ComputeBilateralWeight(float xy2, float z, float mmPerUnit, vec3 S, float r
     #endif
 }
 
-bool IsSSSMaterial(vec3 irradiance) {
-    return irradiance.b > 0.;
-}
-
-// TODO : inout vec3 totalIrradiance, inout vec3 totalWeight
 void EvaluateSample(int i, int n, vec3 S, float d, vec3 centerPosVS, float mmPerUnit, float pixelsPerMm,
                     float phase, inout vec3 totalIrradiance, inout vec3 totalWeight)
 {
@@ -117,12 +113,6 @@ void EvaluateSample(int i, int n, vec3 S, float d, vec3 centerPosVS, float mmPer
     vec2 position; 
     float xy2;
 
-    // TODO : TANGENT PLANE
-    // floor((vUV + 0.5) + vec * pixelsPerMm)
-    // position = vUV + floor(0.5 + vec * pixelsPerMm);
-    // position = vUV + round(vec * pixelsPerMm);
-    // Note that (int) truncates towards 0, while floor() truncates towards -Inf!
-
     position = vUV + round((pixelsPerMm * r) * vec2(cosPsi, sinPsi)) * texelSize;
     xy2      = r * r;
 
@@ -130,16 +120,12 @@ void EvaluateSample(int i, int n, vec3 S, float d, vec3 centerPosVS, float mmPer
     float viewZ = texture2D(depthSampler, position).r;
     vec3 irradiance    = textureSample.rgb;
 
-    // Check the results of the stencil test.
-    // TODO
-    if (IsSSSMaterial(irradiance))
+    if (testLightingForSSS(irradiance))
     {
         // Apply bilateral weighting.
         float relZ = viewZ - centerPosVS.z;
         vec3 weight = ComputeBilateralWeight(xy2, relZ, mmPerUnit, S, rcpPdf);
 
-        // Note: if the texture sample if off-screen, (z = 0) -> (viewZ = far) -> (weight ≈ 0).
-        // TODO : HANDLE OFFSCREN
         totalIrradiance += weight * irradiance;
         totalWeight     += weight;
     }
@@ -156,10 +142,13 @@ void EvaluateSample(int i, int n, vec3 S, float d, vec3 centerPosVS, float mmPer
 
 void main(void) 
 {
-	vec3 centerIrradiance  = texture2D(irradianceSampler, vUV).rgb;
+	vec4 irradianceAndDiffusionProfile  = texture2D(irradianceSampler, vUV);
+    vec3 centerIrradiance = irradianceAndDiffusionProfile.rgb;
+    int diffusionProfileIndex = int(irradianceAndDiffusionProfile.a * 255.);
+
 	float  centerDepth       = 0.;
     vec4 inputColor = texture2D(textureSampler, vUV);
-	bool passedStencilTest = IsSSSMaterial(centerIrradiance);
+	bool passedStencilTest = testLightingForSSS(centerIrradiance);
 
 	if (passedStencilTest)
 	{
@@ -171,24 +160,10 @@ void main(void)
         return;
     }
 
-
-    // SKIN DIFFUSION PROFILE
-    // profile:
-    //   name: Skin
-    //   scatteringDistance: {r: 0.7568628, g: 0.32156864, b: 0.20000002, a: 1}
-    //   transmissionTint: {r: 0.7568628, g: 0.32156864, b: 0.20000002, a: 1}
-    //   texturingMode: 0
-    //   transmissionMode: 0
-    //   thicknessRemap: {x: 0, y: 8.152544}
-    //   worldScale: 1
-    //   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.7568628; //_ShapeParamsAndMaxScatterDists[profileIndex].a max scatter dist
-	float  metersPerUnit = 0.07; //_WorldScalesAndFilterRadiiAndThicknessRemaps[profileIndex].x;
+	vec3 S             = diffusionProfiles[diffusionProfileIndex].S;
+	float  d             = diffusionProfiles[diffusionProfileIndex].d;
+    float filterRadius = diffusionProfiles[diffusionProfileIndex].filterRadius;
 
 	// Reconstruct the view-space position corresponding to the central sample.
 	vec2 centerPosNDC = vUV;

+ 8 - 2
src/scene.ts

@@ -177,6 +177,12 @@ export class Scene extends AbstractScene implements IAnimatable {
     public ambientColor = new Color3(0, 0, 0);
 
     /**
+     * Defines the ratio real world => scene units.
+     * Used for subsurface scattering
+     */
+    public metersPerUnit: number = 1;
+
+    /**
      * This is use to store the default BRDF lookup for PBR materials in your scene.
      * It should only be one of the following (if not the default embedded one):
      * * For uncorrelated BRDF (pbr.brdf.useEnergyConservation = false and pbr.brdf.useSmithVisibilityHeightCorrelated = false) : https://assets.babylonjs.com/environments/uncorrelatedBRDF.dds
@@ -3693,9 +3699,9 @@ export class Scene extends AbstractScene implements IAnimatable {
         this.setTransformMatrix(this.activeCamera.getViewMatrix(), this.activeCamera.getProjectionMatrix(force));
     }
 
-    public drawBuffers(material: Material) {
+    public drawBuffers(effect: Effect) {
         if (this.highDefinitionPipeline) {
-            if (material.shouldRenderToMRT) {
+            if (effect._multiTarget) {
                 this._engine.renderToAttachments(this.multiRenderAttachments);
             } else {
                 this._engine.renderToAttachments(this.defaultAttachments);