Forráskód Böngészése

Merge pull request #4841 from sebavan/master

Add ACES tone mapping and integrate image processing in particle system
sebavan 7 éve
szülő
commit
997406c47d

+ 2 - 0
dist/preview release/what's new.md

@@ -88,6 +88,8 @@
 - Added WeightedSound; selects one from many Sounds with random weight for playback. ([najadojo](https://github.com/najadojo))
 - Added HDR support to ReflectionProbe ([Deltakosh](https://github.com/deltakosh))
 - Added Video Recorder [Issue 4708](https://github.com/BabylonJS/Babylon.js/issues/4708) ([sebavan](http://www.github.com/sebavan))
+- Added ACES ToneMapping to the image processing to help getting more parity with other engines ([sebavan](http://www.github.com/sebavan))
+- Added Image Processing to the particle system to allow consistency in one pass forward rendering scenes ([sebavan](http://www.github.com/sebavan))
 
 ### glTF Loader
 

+ 1 - 0
src/Materials/Background/babylon.backgroundMaterial.ts

@@ -81,6 +81,7 @@
         public VIGNETTEBLENDMODEMULTIPLY = false;
         public VIGNETTEBLENDMODEOPAQUE = false;
         public TONEMAPPING = false;
+        public TONEMAPPING_ACES = false;
         public CONTRAST = false;
         public COLORCURVES = false;
         public COLORGRADING = false;

+ 1 - 0
src/Materials/PBR/babylon.pbrBaseMaterial.ts

@@ -115,6 +115,7 @@
         public VIGNETTEBLENDMODEMULTIPLY = false;
         public VIGNETTEBLENDMODEOPAQUE = false;
         public TONEMAPPING = false;
+        public TONEMAPPING_ACES = false;
         public CONTRAST = false;
         public COLORCURVES = false;
         public COLORGRADING = false;

+ 65 - 0
src/Materials/babylon.imageProcessingConfiguration.ts

@@ -10,6 +10,7 @@ module BABYLON {
         VIGNETTEBLENDMODEMULTIPLY: boolean;
         VIGNETTEBLENDMODEOPAQUE: boolean;
         TONEMAPPING: boolean;
+        TONEMAPPING_ACES: boolean;
         CONTRAST: boolean;
         EXPOSURE: boolean;
         COLORCURVES: boolean;
@@ -21,6 +22,31 @@ module BABYLON {
     }
 
     /**
+     * @hidden
+     */
+    export class ImageProcessingConfigurationDefines extends MaterialDefines implements IImageProcessingConfigurationDefines {
+        public IMAGEPROCESSING = false;
+        public VIGNETTE = false;
+        public VIGNETTEBLENDMODEMULTIPLY = false;
+        public VIGNETTEBLENDMODEOPAQUE = false;
+        public TONEMAPPING = false;
+        public TONEMAPPING_ACES = false;
+        public CONTRAST = false;
+        public COLORCURVES = false;
+        public COLORGRADING = false;
+        public COLORGRADING3D = false;
+        public SAMPLER3DGREENDEPTH = false;
+        public SAMPLER3DBGRMAP = false;
+        public IMAGEPROCESSINGPOSTPROCESS = false;
+        public EXPOSURE = false;
+
+        constructor() {
+            super();
+            this.rebuild();
+        }
+    }
+
+    /**
      * This groups together the common properties used for image processing either in direct forward pass
      * or through post processing effect depending on the use of the image processing pipeline in your scene 
      * or not.
@@ -28,6 +54,17 @@ module BABYLON {
     export class ImageProcessingConfiguration {
 
         /**
+         * Default tone mapping applied in BabylonJS.
+         */
+        public static readonly TONEMAPPING_STANDARD = 0;
+
+        /**
+         * ACES Tone mapping (used by default in unreal and unity). This can help getting closer
+         * to other engines rendering to increase portability.
+         */
+        public static readonly TONEMAPPING_ACES = 1;
+
+        /**
          * Color curves setup used in the effect if colorCurvesEnabled is set to true 
          */
         @serializeAsColorCurves()
@@ -160,6 +197,26 @@ module BABYLON {
         }
 
         @serialize()
+        private _toneMappingType = ImageProcessingConfiguration.TONEMAPPING_STANDARD;
+        /**
+         * Gets the type of tone mapping effect.
+         */
+        public get toneMappingType(): number {
+            return this._toneMappingType;
+        }
+        /**
+         * Sets the type of tone mapping effect used in BabylonJS.
+         */
+        public set toneMappingType(value: number) {
+            if (this._toneMappingType === value) {
+                return;
+            }
+
+            this._toneMappingType = value;
+            this._updateParameters();
+        }
+
+        @serialize()
         protected _contrast = 1.0;
         /**
          * Gets the contrast used in the effect.
@@ -356,6 +413,7 @@ module BABYLON {
             if (forPostProcess !== this.applyByPostProcess || !this._isEnabled) {
                 defines.VIGNETTE = false;
                 defines.TONEMAPPING = false;
+                defines.TONEMAPPING_ACES = false;
                 defines.CONTRAST = false;
                 defines.EXPOSURE = false;
                 defines.COLORCURVES = false;
@@ -369,7 +427,14 @@ module BABYLON {
             defines.VIGNETTE = this.vignetteEnabled;
             defines.VIGNETTEBLENDMODEMULTIPLY = (this.vignetteBlendMode === ImageProcessingConfiguration._VIGNETTEMODE_MULTIPLY);
             defines.VIGNETTEBLENDMODEOPAQUE = !defines.VIGNETTEBLENDMODEMULTIPLY;
+            
             defines.TONEMAPPING = this.toneMappingEnabled;
+            switch (this._toneMappingType) {
+                case ImageProcessingConfiguration.TONEMAPPING_ACES:
+                    defines.TONEMAPPING_ACES = true;
+                    break;
+            }
+
             defines.CONTRAST = (this.contrast !== 1.0);
             defines.EXPOSURE = (this.exposure !== 1.0);
             defines.COLORCURVES = (this.colorCurvesEnabled && !!this.colorCurves);

+ 1 - 0
src/Materials/babylon.standardMaterial.ts

@@ -80,6 +80,7 @@ module BABYLON {
         public VIGNETTEBLENDMODEMULTIPLY = false;
         public VIGNETTEBLENDMODEOPAQUE = false;
         public TONEMAPPING = false;
+        public TONEMAPPING_ACES = false;
         public CONTRAST = false;
         public COLORCURVES = false;
         public COLORGRADING = false;

+ 50 - 1
src/Particles/babylon.baseParticleSystem.ts

@@ -415,7 +415,56 @@ module BABYLON {
 
             this._isBillboardBased = value;
             this._reset();
-        }     
+        }
+
+        /**
+         * The scene the particle system belongs to.
+         */
+        protected _scene: Scene;
+
+        /**
+         * Local cache of defines for image processing.
+         */
+        protected _imageProcessingConfigurationDefines = new ImageProcessingConfigurationDefines();
+
+        /**
+         * Default configuration related to image processing available in the standard Material.
+         */
+        protected _imageProcessingConfiguration: ImageProcessingConfiguration;
+
+        /**
+         * Gets the image processing configuration used either in this material.
+         */
+        public get imageProcessingConfiguration(): ImageProcessingConfiguration {
+            return this._imageProcessingConfiguration;
+        }
+
+        /**
+         * Sets the Default image processing configuration used either in the this material.
+         * 
+         * If sets to null, the scene one is in use.
+         */
+        public set imageProcessingConfiguration(value: ImageProcessingConfiguration) {
+            this._attachImageProcessingConfiguration(value);
+        }
+
+        /**
+         * Attaches a new image processing configuration to the Standard Material.
+         * @param configuration 
+         */
+        protected _attachImageProcessingConfiguration(configuration: Nullable<ImageProcessingConfiguration>): void {
+            if (configuration === this._imageProcessingConfiguration) {
+                return;
+            }
+
+            // Pick the scene configuration if needed.
+            if (!configuration) {
+                this._imageProcessingConfiguration = this._scene.imageProcessingConfiguration;
+            }
+            else {
+                this._imageProcessingConfiguration = configuration;
+            }
+        }
 
         /** @hidden */
         protected _reset() {

+ 27 - 7
src/Particles/babylon.gpuParticleSystem.ts

@@ -27,7 +27,6 @@
         private _sourceBuffer: Buffer;
         private _targetBuffer: Buffer;
 
-        private _scene: Scene;
         private _engine: Engine;
 
         private _currentRenderId = -1;    
@@ -101,7 +100,7 @@
             }
 
 
-            if (!this.emitter || !this._updateEffect.isReady() || !this._renderEffect.isReady() || !this.particleTexture || !this.particleTexture.isReady()) {
+            if (!this.emitter || !this._updateEffect.isReady() || !this._imageProcessingConfiguration.isReady() || !this._renderEffect.isReady() || !this.particleTexture || !this.particleTexture.isReady()) {
                 return false;
             }
 
@@ -392,8 +391,8 @@
             this._removeGradient(gradient, this._limitVelocityGradients, this._limitVelocityGradientsTexture);
             (<any>this._limitVelocityGradientsTexture) = null;
 
-            return this;           
-        }           
+            return this;
+        }
 
         /**
          * Instantiates a GPU particle system.
@@ -409,6 +408,9 @@
                     }>, scene: Scene, isAnimationSheetEnabled: boolean = false) {
             super(name);
             this._scene = scene || Engine.LastCreatedScene;
+            // Setup the default processing configuration to the scene.
+            this._attachImageProcessingConfiguration(null);
+            
             this._engine = this._scene.getEngine();
 
             if (!options.randomTextureSize) {
@@ -755,14 +757,27 @@
                 defines += "\n#define ANIMATESHEET";
             }                 
 
+            if (this._imageProcessingConfiguration) {
+                this._imageProcessingConfiguration.prepareDefines(this._imageProcessingConfigurationDefines);
+                defines += "\n" + this._imageProcessingConfigurationDefines.toString();
+            }
+
             if (this._renderEffect && this._renderEffect.defines === defines) {
                 return;
             }
 
+            var uniforms = ["view", "projection", "colorDead", "invView", "vClipPlane", "sheetInfos", "translationPivot", "eyePosition"];
+            var samplers = ["textureSampler", "colorGradientSampler"];
+
+            if (ImageProcessingConfiguration) {
+                ImageProcessingConfiguration.PrepareUniforms(uniforms, this._imageProcessingConfigurationDefines);
+                ImageProcessingConfiguration.PrepareSamplers(samplers, this._imageProcessingConfigurationDefines);
+            }
+
             this._renderEffect = new Effect("gpuRenderParticles", 
                                             ["position", "age", "life", "size", "color", "offset", "uv", "initialDirection", "angle", "cellIndex"], 
-                                            ["view", "projection", "colorDead", "invView", "vClipPlane", "sheetInfos", "translationPivot", "eyePosition"], 
-                                            ["textureSampler", "colorGradientSampler"], this._scene.getEngine(), defines);
+                                            uniforms, 
+                                            samplers, this._scene.getEngine(), defines);
         }        
 
         /**
@@ -986,7 +1001,12 @@
                     invView.invert();
                     this._renderEffect.setMatrix("invView", invView);
                     this._renderEffect.setFloat4("vClipPlane", clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.d);
-                }            
+                }
+
+                // image processing
+                if (this._imageProcessingConfiguration && !this._imageProcessingConfiguration.applyByPostProcess) {
+                    this._imageProcessingConfiguration.bind(this._renderEffect);
+                }
 
                 // Draw order
                 switch(this.blendMode)

+ 23 - 4
src/Particles/babylon.particleSystem.ts

@@ -56,7 +56,6 @@
         private _particles = new Array<Particle>();
         private _epsilon: number;
         private _capacity: number;
-        private _scene: Scene;
         private _stockParticles = new Array<Particle>();
         private _newPartsExcess = 0;
         private _vertexData: Float32Array;
@@ -109,7 +108,7 @@
          */
         public getClassName(): string {
             return "ParticleSystem";
-        }        
+        }
 
         /**
          * Instantiates a particle system.
@@ -131,6 +130,9 @@
 
             this._scene = scene || Engine.LastCreatedScene;
 
+            // Setup the default processing configuration to the scene.
+            this._attachImageProcessingConfiguration(null);
+
             this._customEffect = customEffect;
 
             this._scene.particleSystems.push(this);
@@ -1003,6 +1005,11 @@
                 }
             }
 
+            if (this._imageProcessingConfiguration) {
+                this._imageProcessingConfiguration.prepareDefines(this._imageProcessingConfigurationDefines);
+                defines.push(this._imageProcessingConfigurationDefines.toString());
+            }
+
             // Effect
             var join = defines.join("\n");
             if (this._cachedDefines !== join) {
@@ -1011,6 +1018,13 @@
                 var attributesNamesOrOptions = ParticleSystem._GetAttributeNamesOrOptions(this._isAnimationSheetEnabled, this._isBillboardBased);
                 var effectCreationOption = ParticleSystem._GetEffectCreationOptions(this._isAnimationSheetEnabled);
 
+                var samplers = ["diffuseSampler"];
+
+                if (ImageProcessingConfiguration) {
+                    ImageProcessingConfiguration.PrepareUniforms(effectCreationOption, this._imageProcessingConfigurationDefines);
+                    ImageProcessingConfiguration.PrepareSamplers(samplers, this._imageProcessingConfigurationDefines);
+                }
+
                 this._effect = this._scene.getEngine().createEffect(
                     "particles",
                     attributesNamesOrOptions,
@@ -1033,7 +1047,7 @@
                 var effect = this._getEffect();
 
                 // Check
-                if (!this.emitter || !effect.isReady() || !this.particleTexture || !this.particleTexture.isReady())
+                if (!this.emitter || !this._imageProcessingConfiguration.isReady() || !effect.isReady() || !this.particleTexture || !this.particleTexture.isReady())
                     return;
 
                 if (this._currentRenderId === this._scene.getRenderId()) {
@@ -1131,7 +1145,7 @@
          */
         public isReady(): boolean {
             var effect = this._getEffect();
-            if (!this.emitter || !effect.isReady() || !this.particleTexture || !this.particleTexture.isReady()) {
+            if (!this.emitter || !this._imageProcessingConfiguration.isReady() || !effect.isReady() || !this.particleTexture || !this.particleTexture.isReady()) {
                 return false;
             }
 
@@ -1184,6 +1198,11 @@
 
             engine.bindBuffers(this._vertexBuffers, this._indexBuffer, effect);
 
+            // image processing
+            if (this._imageProcessingConfiguration && !this._imageProcessingConfiguration.applyByPostProcess) {
+                this._imageProcessingConfiguration.bind(effect);
+            }
+
             // Draw order
             switch(this.blendMode)
             {

+ 1 - 0
src/PostProcess/babylon.imageProcessingPostProcess.ts

@@ -303,6 +303,7 @@
             VIGNETTEBLENDMODEMULTIPLY: false,
             VIGNETTEBLENDMODEOPAQUE: false,
             TONEMAPPING: false,
+            TONEMAPPING_ACES: false,
             CONTRAST: false,
             COLORCURVES: false,
             COLORGRADING: false,

+ 49 - 2
src/Shaders/ShadersInclude/imageProcessingFunctions.fx

@@ -49,6 +49,49 @@
 	}
 #endif
 
+#ifdef TONEMAPPING_ACES
+	// https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl
+	// Thanks to MJP for all the invaluable tricks found in his repo.
+	// As stated there, the code in this section was originally written by Stephen Hill (@self_shadow), who deserves all
+	// credit for coming up with this fit and implementing it. Buy him a beer next time you see him. :)
+
+	// sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT
+	const mat3 ACESInputMat = mat3(
+		vec3(0.59719, 0.07600, 0.02840),
+		vec3(0.35458, 0.90834, 0.13383),
+		vec3(0.04823, 0.01566, 0.83777)
+	);
+
+	// ODT_SAT => XYZ => D60_2_D65 => sRGB
+	const mat3 ACESOutputMat = mat3(
+		vec3( 1.60475, -0.10208, -0.00327),
+		vec3(-0.53108,  1.10813, -0.07276),
+		vec3(-0.07367, -0.00605,  1.07602)
+	);
+
+	vec3 RRTAndODTFit(vec3 v)
+	{
+		vec3 a = v * (v + 0.0245786) - 0.000090537;
+		vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;
+		return a / b;
+	}
+
+	vec3 ACESFitted(vec3 color)
+	{
+		color = ACESInputMat * color;
+
+		// Apply RRT and ODT
+		color = RRTAndODTFit(color);
+
+		color = ACESOutputMat * color;
+
+		// Clamp to [0, 1]
+		color = clamp(color, 0.0, 1.0);
+
+		return color;
+	}
+#endif
+
 vec4 applyImageProcessing(vec4 result) {
 
 #ifdef EXPOSURE
@@ -77,8 +120,12 @@ vec4 applyImageProcessing(vec4 result) {
 #endif
 	
 #ifdef TONEMAPPING
-	const float tonemappingCalibration = 1.590579;
-	result.rgb = 1.0 - exp2(-tonemappingCalibration * result.rgb);
+	#ifdef TONEMAPPING_ACES
+		result.rgb = ACESFitted(result.rgb);
+	#else
+		const float tonemappingCalibration = 1.590579;
+		result.rgb = 1.0 - exp2(-tonemappingCalibration * result.rgb);
+	#endif
 #endif
 
 	// Going back to gamma space

+ 17 - 0
src/Shaders/gpuRenderParticles.fragment.fx

@@ -11,10 +11,27 @@ out vec4 outFragColor;
 in float fClipDistance;
 #endif
 
+#include<imageProcessingDeclaration>
+
+#include<helperFunctions>
+
+#include<imageProcessingFunctions>
+
 void main() {
 #ifdef CLIPPLANE
 	if (fClipDistance > 0.0)
 		discard;
 #endif  
   outFragColor = texture(textureSampler, vUV) * vColor;
+
+// Apply image processing if relevant. As this applies in linear space, 
+// We first move from gamma to linear.
+#ifdef IMAGEPROCESSINGPOSTPROCESS
+	outFragColor.rgb = toLinearSpace(outFragColor.rgb);
+#else
+	#ifdef IMAGEPROCESSING
+		outFragColor.rgb = toLinearSpace(outFragColor.rgb);
+		outFragColor = applyImageProcessing(outFragColor);
+	#endif
+#endif
 }

+ 19 - 1
src/Shaders/particles.fragment.fx

@@ -8,12 +8,30 @@ uniform sampler2D diffuseSampler;
 varying float fClipDistance;
 #endif
 
+#include<imageProcessingDeclaration>
+
+#include<helperFunctions>
+
+#include<imageProcessingFunctions>
+
 void main(void) {
 #ifdef CLIPPLANE
 	if (fClipDistance > 0.0)
 		discard;
 #endif
 	vec4 baseColor = texture2D(diffuseSampler, vUV);
+	baseColor = (baseColor * textureMask + (vec4(1., 1., 1., 1.) - textureMask)) * vColor;
+
+// Apply image processing if relevant. As this applies in linear space, 
+// We first move from gamma to linear.
+#ifdef IMAGEPROCESSINGPOSTPROCESS
+	baseColor.rgb = toLinearSpace(baseColor.rgb);
+#else
+	#ifdef IMAGEPROCESSING
+		baseColor.rgb = toLinearSpace(baseColor.rgb);
+		baseColor = applyImageProcessing(baseColor);
+	#endif
+#endif
 
-	gl_FragColor = (baseColor * textureMask + (vec4(1., 1., 1., 1.) - textureMask)) * vColor;
+	gl_FragColor = baseColor;
 }

+ 1 - 1
src/Shaders/pbr.fragment.fx

@@ -836,7 +836,7 @@ void main(void) {
     // this also limits the brightness which helpfully reduces over-sparkling in bloom (native handles this in the bloom blur shader)
     finalColor.rgb = clamp(finalColor.rgb, 0., 30.0);
 #else
-    // Alway run even to ensure going back to gamma space.
+    // Alway run to ensure we are going back to gamma space.
     finalColor = applyImageProcessing(finalColor);
 #endif