Explorar el Código

Color Curves Basics

= hace 9 años
padre
commit
33def5c20c

+ 1 - 0
Tools/Gulp/config.json

@@ -229,6 +229,7 @@
       "../../src/Materials/Textures/babylon.hdrCubeTexture.js",
       "../../src/Debug/babylon.skeletonViewer.js",
       "../../src/Materials/Textures/babylon.colorGradingTexture.js",
+      "../../src/Materials/babylon.colorCurves.js",
       "../../src/Materials/babylon.pbrMaterial.js",      
       "../../src/Debug/babylon.debugLayer.js"
     ]

+ 529 - 0
src/Materials/babylon.colorCurves.js

@@ -0,0 +1,529 @@
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * The color grading curves provide additional color adjustmnent that is applied after any color grading transform (3D LUT).
+     * They allow basic adjustment of saturation and small exposure adjustments, along with color filter tinting to provide white balance adjustment or more stylistic effects.
+     * These are similar to controls found in many professional imaging or colorist software. The global controls are applied to the entire image. For advanced tuning, extra controls are provided to adjust the shadow, midtone and highlight areas of the image;
+     * corresponding to low luminance, medium luminance, and high luminance areas respectively.
+     */
+    var ColorCurves = (function () {
+        function ColorCurves() {
+            this._dirty = true;
+            this._tempColor = new BABYLON.Color4(0, 0, 0, 0);
+            this._globalCurve = new BABYLON.Color4(0, 0, 0, 0);
+            this._highlightsCurve = new BABYLON.Color4(0, 0, 0, 0);
+            this._midtonesCurve = new BABYLON.Color4(0, 0, 0, 0);
+            this._shadowsCurve = new BABYLON.Color4(0, 0, 0, 0);
+            this._positiveCurve = new BABYLON.Color4(0, 0, 0, 0);
+            this._negativeCurve = new BABYLON.Color4(0, 0, 0, 0);
+            this._globalHue = 30;
+            this._globalDensity = 0;
+            this._globalSaturation = 0;
+            this._globalExposure = 0;
+            this._highlightsHue = 30;
+            this._highlightsDensity = 0;
+            this._highlightsSaturation = 0;
+            this._highlightsExposure = 0;
+            this._midtonesHue = 30;
+            this._midtonesDensity = 0;
+            this._midtonesSaturation = 0;
+            this._midtonesExposure = 0;
+            this._shadowsHue = 30;
+            this._shadowsDensity = 0;
+            this._shadowsSaturation = 0;
+            this._shadowsExposure = 0;
+        }
+        Object.defineProperty(ColorCurves.prototype, "GlobalHue", {
+            /**
+             * Gets the global Hue value.
+             * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+             */
+            get: function () {
+                return this._globalHue;
+            },
+            /**
+             * Sets the global Hue value.
+             * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+             */
+            set: function (value) {
+                this._globalHue = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "GlobalDensity", {
+            /**
+             * Gets the global Density value.
+             * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect.
+             * Values less than zero provide a filter of opposite hue.
+             */
+            get: function () {
+                return this._globalDensity;
+            },
+            /**
+             * Sets the global Density value.
+             * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect.
+             * Values less than zero provide a filter of opposite hue.
+             */
+            set: function (value) {
+                this._globalDensity = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "GlobalSaturation", {
+            /**
+             * Gets the global Saturation value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+             */
+            get: function () {
+                return this._globalSaturation;
+            },
+            /**
+             * Sets the global Saturation value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+             */
+            set: function (value) {
+                this._globalSaturation = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "HighlightsHue", {
+            /**
+             * Gets the highlights Hue value.
+             * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+             */
+            get: function () {
+                return this._highlightsHue;
+            },
+            /**
+             * Sets the highlights Hue value.
+             * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+             */
+            set: function (value) {
+                this._highlightsHue = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "HighlightsDensity", {
+            /**
+             * Gets the highlights Density value.
+             * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect.
+             * Values less than zero provide a filter of opposite hue.
+             */
+            get: function () {
+                return this._highlightsDensity;
+            },
+            /**
+             * Sets the highlights Density value.
+             * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect.
+             * Values less than zero provide a filter of opposite hue.
+             */
+            set: function (value) {
+                this._highlightsDensity = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "HighlightsSaturation", {
+            /**
+             * Gets the highlights Saturation value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+             */
+            get: function () {
+                return this._highlightsSaturation;
+            },
+            /**
+             * Sets the highlights Saturation value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+             */
+            set: function (value) {
+                this._highlightsSaturation = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "HighlightsExposure", {
+            /**
+             * Gets the highlights Exposure value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase exposure and negative values decrease exposure.
+             */
+            get: function () {
+                return this._highlightsExposure;
+            },
+            /**
+             * Sets the highlights Exposure value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase exposure and negative values decrease exposure.
+             */
+            set: function (value) {
+                this._highlightsExposure = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "MidtonesHue", {
+            /**
+             * Gets the midtones Hue value.
+             * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+             */
+            get: function () {
+                return this._midtonesHue;
+            },
+            /**
+             * Sets the midtones Hue value.
+             * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+             */
+            set: function (value) {
+                this._midtonesHue = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "MidtonesDensity", {
+            /**
+             * Gets the midtones Density value.
+             * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect.
+             * Values less than zero provide a filter of opposite hue.
+             */
+            get: function () {
+                return this._midtonesDensity;
+            },
+            /**
+             * Sets the midtones Density value.
+             * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect.
+             * Values less than zero provide a filter of opposite hue.
+             */
+            set: function (value) {
+                this._midtonesDensity = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "MidtonesSaturation", {
+            /**
+             * Gets the midtones Saturation value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+             */
+            get: function () {
+                return this._midtonesSaturation;
+            },
+            /**
+             * Sets the midtones Saturation value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+             */
+            set: function (value) {
+                this._midtonesSaturation = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "MidtonesExposure", {
+            /**
+             * Gets the midtones Exposure value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase exposure and negative values decrease exposure.
+             */
+            get: function () {
+                return this._midtonesExposure;
+            },
+            /**
+             * Sets the midtones Exposure value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase exposure and negative values decrease exposure.
+             */
+            set: function (value) {
+                this._midtonesExposure = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "ShadowsHue", {
+            /**
+             * Gets the shadows Hue value.
+             * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+             */
+            get: function () {
+                return this._shadowsHue;
+            },
+            /**
+             * Sets the shadows Hue value.
+             * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+             */
+            set: function (value) {
+                this._shadowsHue = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "ShadowsDensity", {
+            /**
+             * Gets the shadows Density value.
+             * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect.
+             * Values less than zero provide a filter of opposite hue.
+             */
+            get: function () {
+                return this._shadowsDensity;
+            },
+            /**
+             * Sets the shadows Density value.
+             * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect.
+             * Values less than zero provide a filter of opposite hue.
+             */
+            set: function (value) {
+                this._shadowsDensity = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "ShadowsSaturation", {
+            /**
+             * Gets the shadows Saturation value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+             */
+            get: function () {
+                return this._shadowsSaturation;
+            },
+            /**
+             * Sets the shadows Saturation value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+             */
+            set: function (value) {
+                this._shadowsSaturation = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ColorCurves.prototype, "ShadowsExposure", {
+            /**
+             * Gets the shadows Exposure value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase exposure and negative values decrease exposure.
+             */
+            get: function () {
+                return this._shadowsExposure;
+            },
+            /**
+             * Sets the shadows Exposure value.
+             * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase exposure and negative values decrease exposure.
+             */
+            set: function (value) {
+                this._shadowsExposure = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        /**
+         * Binds the color curves to the shader.
+         * @param colorCurves The color curve to bind
+         * @param effect The effect to bind to
+         */
+        ColorCurves.Bind = function (colorCurves, effect) {
+            if (colorCurves._dirty) {
+                // Fill in global info.
+                colorCurves.getColorGradingDataToRef(colorCurves._globalHue, colorCurves._globalDensity, colorCurves._globalSaturation, colorCurves._globalExposure, colorCurves._globalCurve);
+                // Compute highlights info.
+                colorCurves.getColorGradingDataToRef(colorCurves._highlightsHue, colorCurves._highlightsDensity, colorCurves._highlightsSaturation, colorCurves._highlightsExposure, colorCurves._tempColor);
+                colorCurves._tempColor.multiplyToRef(colorCurves._globalCurve, colorCurves._highlightsCurve);
+                // Compute midtones info.
+                colorCurves.getColorGradingDataToRef(colorCurves._midtonesHue, colorCurves._midtonesDensity, colorCurves._midtonesSaturation, colorCurves._midtonesExposure, colorCurves._tempColor);
+                colorCurves._tempColor.multiplyToRef(colorCurves._globalCurve, colorCurves._midtonesCurve);
+                // Compute shadows info.
+                colorCurves.getColorGradingDataToRef(colorCurves._shadowsHue, colorCurves._shadowsDensity, colorCurves._shadowsSaturation, colorCurves._shadowsExposure, colorCurves._tempColor);
+                colorCurves._tempColor.multiplyToRef(colorCurves._globalCurve, colorCurves._shadowsCurve);
+                // Compute deltas (neutral is midtones).
+                colorCurves._highlightsCurve.subtractToRef(colorCurves._midtonesCurve, colorCurves._positiveCurve);
+                colorCurves._midtonesCurve.subtractToRef(colorCurves._shadowsCurve, colorCurves._negativeCurve);
+            }
+            effect.setFloat4("vCameraColorCurvePositive", colorCurves._positiveCurve.r, colorCurves._positiveCurve.g, colorCurves._positiveCurve.b, colorCurves._positiveCurve.a);
+            effect.setFloat4("vCameraColorCurveNeutral", colorCurves._midtonesCurve.r, colorCurves._midtonesCurve.g, colorCurves._midtonesCurve.b, colorCurves._midtonesCurve.a);
+            effect.setFloat4("vCameraColorCurveNegative", colorCurves._negativeCurve.r, colorCurves._negativeCurve.g, colorCurves._negativeCurve.b, colorCurves._negativeCurve.a);
+        };
+        /**
+         * Prepare the list of uniforms associated with the ColorCurves effects.
+         * @param uniformsList The list of uniforms used in the effect
+         */
+        ColorCurves.PrepareUniforms = function (uniformsList) {
+            uniformsList.push("vCameraColorCurveNeutral", "vCameraColorCurvePositive", "vCameraColorCurveNegative");
+        };
+        /**
+         * Returns color grading data based on a hue, density, saturation and exposure value.
+         * @param filterHue The hue of the color filter.
+         * @param filterDensity The density of the color filter.
+         * @param saturation The saturation.
+         * @param exposure The exposure.
+         * @param result The result data container.
+         */
+        ColorCurves.prototype.getColorGradingDataToRef = function (hue, density, saturation, exposure, result) {
+            if (hue == null) {
+                return null;
+            }
+            hue = ColorCurves.clamp(hue, 0, 360);
+            density = ColorCurves.clamp(density, -100, 100);
+            saturation = ColorCurves.clamp(saturation, -100, 100);
+            exposure = ColorCurves.clamp(exposure, -100, 100);
+            // Remap the slider/config filter density with non-linear mapping and also scale by half
+            // so that the maximum filter density is only 50% control. This provides fine control 
+            // for small values and reasonable range.
+            density = ColorCurves.applyColorGradingSliderNonlinear(density);
+            density *= 0.5;
+            exposure = ColorCurves.applyColorGradingSliderNonlinear(exposure);
+            if (density < 0) {
+                density *= -1;
+                hue = (hue + 180) % 360;
+            }
+            ColorCurves.fromHSBToRef(hue, density, 50 + 0.25 * exposure, result);
+            result.scaleToRef(2, result);
+            result.a = 1 + 0.01 * saturation;
+        };
+        /**
+         * Takes an input slider value and returns an adjusted value that provides extra control near the centre.
+         * @param value The input slider value in range [-100,100].
+         * @returns Adjusted value.
+         */
+        ColorCurves.applyColorGradingSliderNonlinear = function (value) {
+            value /= 100;
+            var x = Math.abs(value);
+            x = Math.pow(x, 2);
+            if (value < 0) {
+                x *= -1;
+            }
+            x *= 100;
+            return x;
+        };
+        /**
+         * Returns an RGBA Color4 based on Hue, Saturation and Brightness (also referred to as value, HSV).
+         * @param hue The hue (H) input.
+         * @param saturation The saturation (S) input.
+         * @param brightness The brightness (B) input.
+         * @result An RGBA color represented as Vector4.
+         */
+        ColorCurves.fromHSBToRef = function (hue, saturation, brightness, result) {
+            var h = ColorCurves.clamp(hue, 0, 360);
+            var s = ColorCurves.clamp(saturation / 100, 0, 1);
+            var v = ColorCurves.clamp(brightness / 100, 0, 1);
+            if (s === 0) {
+                result.r = v;
+                result.g = v;
+                result.b = v;
+            }
+            else {
+                // sector 0 to 5
+                h /= 60;
+                var i = Math.floor(h);
+                // fractional part of h
+                var f = h - i;
+                var p = v * (1 - s);
+                var q = v * (1 - s * f);
+                var t = v * (1 - s * (1 - f));
+                switch (i) {
+                    case 0:
+                        result.r = v;
+                        result.g = t;
+                        result.b = p;
+                        break;
+                    case 1:
+                        result.r = q;
+                        result.g = v;
+                        result.b = p;
+                        break;
+                    case 2:
+                        result.r = p;
+                        result.g = v;
+                        result.b = t;
+                        break;
+                    case 3:
+                        result.r = p;
+                        result.g = q;
+                        result.b = v;
+                        break;
+                    case 4:
+                        result.r = t;
+                        result.g = p;
+                        result.b = v;
+                        break;
+                    default:
+                        result.r = v;
+                        result.g = p;
+                        result.b = q;
+                        break;
+                }
+            }
+            result.a = 1;
+        };
+        /**
+         * Returns a value clamped between min and max
+         * @param value The value to clamp
+         * @param min The minimum of value
+         * @param max The maximum of value
+         * @returns The clamped value.
+         */
+        ColorCurves.clamp = function (value, min, max) {
+            return Math.min(Math.max(value, min), max);
+        };
+        /**
+         * Clones the current color curve instance.
+         * @return The cloned curves
+         */
+        ColorCurves.prototype.clone = function () {
+            return BABYLON.SerializationHelper.Clone(function () { return new ColorCurves(); }, this);
+        };
+        /**
+         * Serializes the current color curve instance to a json representation.
+         * @return a JSON representation
+         */
+        ColorCurves.prototype.serialize = function () {
+            return BABYLON.SerializationHelper.Serialize(this);
+        };
+        /**
+         * Parses the color curve from a json representation.
+         * @param source the JSON source to parse
+         * @return The parsed curves
+         */
+        ColorCurves.Parse = function (source) {
+            return BABYLON.SerializationHelper.Parse(function () { return new ColorCurves(); }, source, null, null);
+        };
+        __decorate([
+            BABYLON.serialize()
+        ], ColorCurves.prototype, "_globalHue", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ColorCurves.prototype, "_globalDensity", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ColorCurves.prototype, "_globalSaturation", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ColorCurves.prototype, "_globalExposure", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ColorCurves.prototype, "_highlightsHue", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ColorCurves.prototype, "_highlightsDensity", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ColorCurves.prototype, "_highlightsSaturation", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ColorCurves.prototype, "_highlightsExposure", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ColorCurves.prototype, "_midtonesHue", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ColorCurves.prototype, "_midtonesDensity", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ColorCurves.prototype, "_midtonesSaturation", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ColorCurves.prototype, "_midtonesExposure", void 0);
+        return ColorCurves;
+    }());
+    BABYLON.ColorCurves = ColorCurves;
+})(BABYLON || (BABYLON = {}));

+ 535 - 0
src/Materials/babylon.colorCurves.ts

@@ -0,0 +1,535 @@
+module BABYLON {
+    
+    /**
+     * The color grading curves provide additional color adjustmnent that is applied after any color grading transform (3D LUT). 
+     * They allow basic adjustment of saturation and small exposure adjustments, along with color filter tinting to provide white balance adjustment or more stylistic effects.
+     * These are similar to controls found in many professional imaging or colorist software. The global controls are applied to the entire image. For advanced tuning, extra controls are provided to adjust the shadow, midtone and highlight areas of the image; 
+     * corresponding to low luminance, medium luminance, and high luminance areas respectively.
+     */
+    export class ColorCurves {
+        
+        private _dirty = true;
+        
+        private _tempColor = new BABYLON.Color4(0, 0, 0, 0);
+        
+        private _globalCurve = new BABYLON.Color4(0, 0, 0, 0);
+        private _highlightsCurve = new BABYLON.Color4(0, 0, 0, 0);
+        private _midtonesCurve = new BABYLON.Color4(0, 0, 0, 0);
+        private _shadowsCurve = new BABYLON.Color4(0, 0, 0, 0);
+        
+        private _positiveCurve = new BABYLON.Color4(0, 0, 0, 0);
+        private _negativeCurve = new BABYLON.Color4(0, 0, 0, 0);
+        
+        @serialize()
+        private _globalHue = 30;
+        
+        @serialize()
+        private _globalDensity = 0;
+        
+        @serialize()
+        private _globalSaturation = 0;
+        
+        @serialize()
+        private _globalExposure = 0;
+        
+        /**
+         * Gets the global Hue value.
+         * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+         */
+        public get GlobalHue(): number {
+            return this._globalHue;
+        }
+        /**
+         * Sets the global Hue value.
+         * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+         */
+        public set GlobalHue(value: number) {
+            this._globalHue = value;
+            this._dirty = true;
+        }
+        /**
+         * Gets the global Density value.
+         * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect. 
+         * Values less than zero provide a filter of opposite hue.
+         */
+        public get GlobalDensity(): number {
+            return this._globalDensity;
+        }
+        /**
+         * Sets the global Density value.
+         * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect. 
+         * Values less than zero provide a filter of opposite hue.
+         */
+        public set GlobalDensity(value: number) {
+            this._globalDensity = value;
+            this._dirty = true;
+        }
+        /**
+         * Gets the global Saturation value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+         */
+        public get GlobalSaturation(): number {
+            return this._globalSaturation;
+        }
+        /**
+         * Sets the global Saturation value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+         */
+        public set GlobalSaturation(value: number) {
+            this._globalSaturation = value;
+            this._dirty = true;
+        }
+        
+        @serialize()
+        private _highlightsHue = 30;
+        
+        @serialize()
+        private _highlightsDensity = 0;
+        
+        @serialize()
+        private _highlightsSaturation = 0;
+        
+        @serialize()
+        private _highlightsExposure = 0;
+        
+        /**
+         * Gets the highlights Hue value.
+         * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+         */
+        public get HighlightsHue(): number {
+            return this._highlightsHue;
+        }
+        /**
+         * Sets the highlights Hue value.
+         * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+         */
+        public set HighlightsHue(value: number) {
+            this._highlightsHue = value;
+            this._dirty = true;
+        }
+        /**
+         * Gets the highlights Density value.
+         * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect. 
+         * Values less than zero provide a filter of opposite hue.
+         */
+        public get HighlightsDensity(): number {
+            return this._highlightsDensity;
+        }
+        /**
+         * Sets the highlights Density value.
+         * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect. 
+         * Values less than zero provide a filter of opposite hue.
+         */
+        public set HighlightsDensity(value: number) {
+            this._highlightsDensity = value;
+            this._dirty = true;
+        }
+        /**
+         * Gets the highlights Saturation value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+         */
+        public get HighlightsSaturation(): number {
+            return this._highlightsSaturation;
+        }
+        /**
+         * Sets the highlights Saturation value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+         */
+        public set HighlightsSaturation(value: number) {
+            this._highlightsSaturation = value;
+            this._dirty = true;
+        }
+        /**
+         * Gets the highlights Exposure value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase exposure and negative values decrease exposure.
+         */
+        public get HighlightsExposure(): number {
+            return this._highlightsExposure;
+        }
+        /**
+         * Sets the highlights Exposure value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase exposure and negative values decrease exposure.
+         */
+        public set HighlightsExposure(value: number) {
+            this._highlightsExposure = value;
+            this._dirty = true;
+        }
+        
+        @serialize()
+        private _midtonesHue = 30;
+        
+        @serialize()
+        private _midtonesDensity = 0;
+        
+        @serialize()
+        private _midtonesSaturation = 0;
+        
+        @serialize()
+        private _midtonesExposure = 0;
+        
+        /**
+         * Gets the midtones Hue value.
+         * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+         */
+        public get MidtonesHue(): number {
+            return this._midtonesHue;
+        }
+        /**
+         * Sets the midtones Hue value.
+         * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+         */
+        public set MidtonesHue(value: number) {
+            this._midtonesHue = value;
+            this._dirty = true;
+        }
+        /**
+         * Gets the midtones Density value.
+         * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect. 
+         * Values less than zero provide a filter of opposite hue.
+         */
+        public get MidtonesDensity(): number {
+            return this._midtonesDensity;
+        }
+        /**
+         * Sets the midtones Density value.
+         * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect. 
+         * Values less than zero provide a filter of opposite hue.
+         */
+        public set MidtonesDensity(value: number) {
+            this._midtonesDensity = value;
+            this._dirty = true;
+        }
+        /**
+         * Gets the midtones Saturation value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+         */
+        public get MidtonesSaturation(): number {
+            return this._midtonesSaturation;
+        }
+        /**
+         * Sets the midtones Saturation value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+         */
+        public set MidtonesSaturation(value: number) {
+            this._midtonesSaturation = value;
+            this._dirty = true;
+        }
+        /**
+         * Gets the midtones Exposure value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase exposure and negative values decrease exposure.
+         */
+        public get MidtonesExposure(): number {
+            return this._midtonesExposure;
+        }
+        /**
+         * Sets the midtones Exposure value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase exposure and negative values decrease exposure.
+         */
+        public set MidtonesExposure(value: number) {
+            this._midtonesExposure = value;
+            this._dirty = true;
+        }
+        
+        private _shadowsHue = 30;
+        private _shadowsDensity = 0;
+        private _shadowsSaturation = 0;
+        private _shadowsExposure = 0;
+        
+        /**
+         * Gets the shadows Hue value.
+         * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+         */
+        public get ShadowsHue(): number {
+            return this._shadowsHue;
+        }
+        /**
+         * Sets the shadows Hue value.
+         * The hue value is a standard HSB hue in the range [0,360] where 0=red, 120=green and 240=blue. The default value is 30 degrees (orange).
+         */
+        public set ShadowsHue(value: number) {
+            this._shadowsHue = value;
+            this._dirty = true;
+        }
+        /**
+         * Gets the shadows Density value.
+         * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect. 
+         * Values less than zero provide a filter of opposite hue.
+         */
+        public get ShadowsDensity(): number {
+            return this._shadowsDensity;
+        }
+        /**
+         * Sets the shadows Density value.
+         * The density value is in range [-100,+100] where 0 means the color filter has no effect and +100 means the color filter has maximum effect. 
+         * Values less than zero provide a filter of opposite hue.
+         */
+        public set ShadowsDensity(value: number) {
+            this._shadowsDensity = value;
+            this._dirty = true;
+        }
+        /**
+         * Gets the shadows Saturation value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+         */
+        public get ShadowsSaturation(): number {
+            return this._shadowsSaturation;
+        }
+        /**
+         * Sets the shadows Saturation value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase saturation and negative values decrease saturation.
+         */
+        public set ShadowsSaturation(value: number) {
+            this._shadowsSaturation = value;
+            this._dirty = true;
+        }
+        /**
+         * Gets the shadows Exposure value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase exposure and negative values decrease exposure.
+         */
+        public get ShadowsExposure(): number {
+            return this._shadowsExposure;
+        }
+        /**
+         * Sets the shadows Exposure value.
+         * This is an adjustment value in the range [-100,+100], where the default value of 0.0 makes no adjustment, positive values increase exposure and negative values decrease exposure.
+         */
+        public set ShadowsExposure(value: number) {
+            this._shadowsExposure = value;
+            this._dirty = true;
+        }
+        
+        /**
+         * Binds the color curves to the shader.
+         * @param colorCurves The color curve to bind
+         * @param effect The effect to bind to
+         */
+        public static Bind(colorCurves: ColorCurves, effect: Effect) : void {
+            if (colorCurves._dirty) {
+                colorCurves._dirty = false;
+                            
+                // Fill in global info.
+                colorCurves.getColorGradingDataToRef(
+                    colorCurves._globalHue,
+                    colorCurves._globalDensity,
+                    colorCurves._globalSaturation,
+                    colorCurves._globalExposure,
+                    colorCurves._globalCurve);
+
+                // Compute highlights info.
+                colorCurves.getColorGradingDataToRef(
+                    colorCurves._highlightsHue,
+                    colorCurves._highlightsDensity,
+                    colorCurves._highlightsSaturation,
+                    colorCurves._highlightsExposure,
+                    colorCurves._tempColor);
+                colorCurves._tempColor.multiplyToRef(colorCurves._globalCurve, colorCurves._highlightsCurve);
+
+                // Compute midtones info.
+                colorCurves.getColorGradingDataToRef(
+                    colorCurves._midtonesHue,
+                    colorCurves._midtonesDensity,
+                    colorCurves._midtonesSaturation,
+                    colorCurves._midtonesExposure,
+                    colorCurves._tempColor);
+                colorCurves._tempColor.multiplyToRef(colorCurves._globalCurve, colorCurves._midtonesCurve);
+                
+                // Compute shadows info.
+                colorCurves.getColorGradingDataToRef(
+                    colorCurves._shadowsHue,
+                    colorCurves._shadowsDensity,
+                    colorCurves._shadowsSaturation,
+                    colorCurves._shadowsExposure,
+                    colorCurves._tempColor);
+                colorCurves._tempColor.multiplyToRef(colorCurves._globalCurve, colorCurves._shadowsCurve);
+                
+                // Compute deltas (neutral is midtones).
+                colorCurves._highlightsCurve.subtractToRef(colorCurves._midtonesCurve, colorCurves._positiveCurve);
+                colorCurves._midtonesCurve.subtractToRef(colorCurves._shadowsCurve, colorCurves._negativeCurve);            
+            }
+            
+            effect.setFloat4("vCameraColorCurvePositive", 
+                colorCurves._positiveCurve.r,
+                colorCurves._positiveCurve.g,
+                colorCurves._positiveCurve.b,
+                colorCurves._positiveCurve.a);
+            effect.setFloat4("vCameraColorCurveNeutral", 
+                colorCurves._midtonesCurve.r,
+                colorCurves._midtonesCurve.g,
+                colorCurves._midtonesCurve.b,
+                colorCurves._midtonesCurve.a);
+            effect.setFloat4("vCameraColorCurveNegative", 
+                colorCurves._negativeCurve.r,
+                colorCurves._negativeCurve.g,
+                colorCurves._negativeCurve.b,
+                colorCurves._negativeCurve.a);
+        }
+        
+        /**
+         * Prepare the list of uniforms associated with the ColorCurves effects.
+         * @param uniformsList The list of uniforms used in the effect
+         */
+        public static PrepareUniforms(uniformsList: string[]): void {
+            uniformsList.push(
+                "vCameraColorCurveNeutral", 
+                "vCameraColorCurvePositive", 
+                "vCameraColorCurveNegative"
+            );
+        }
+        
+        /**
+         * Returns color grading data based on a hue, density, saturation and exposure value.
+         * @param filterHue The hue of the color filter.
+         * @param filterDensity The density of the color filter.
+         * @param saturation The saturation.
+         * @param exposure The exposure.
+         * @param result The result data container.
+         */
+        private getColorGradingDataToRef(hue: number, density: number, saturation: number, exposure: number, result: Color4) : void {
+            if (hue == null) {
+                return null;
+            }
+
+            hue = ColorCurves.clamp(hue, 0, 360);
+            density = ColorCurves.clamp(density, -100, 100);
+            saturation = ColorCurves.clamp(saturation, -100, 100);
+            exposure = ColorCurves.clamp(exposure, -100, 100);
+                
+            // Remap the slider/config filter density with non-linear mapping and also scale by half
+            // so that the maximum filter density is only 50% control. This provides fine control 
+            // for small values and reasonable range.
+            density = ColorCurves.applyColorGradingSliderNonlinear(density);
+            density *= 0.5;
+
+            exposure = ColorCurves.applyColorGradingSliderNonlinear(exposure);
+
+            if (density < 0) {
+                density *= -1;
+                hue = (hue + 180) % 360;
+            }
+            
+            ColorCurves.fromHSBToRef(hue, density, 50 + 0.25 * exposure, result);            
+            result.scaleToRef(2, result);
+            result.a = 1 + 0.01 * saturation;
+        }
+        
+        /**
+         * Takes an input slider value and returns an adjusted value that provides extra control near the centre.
+         * @param value The input slider value in range [-100,100].
+         * @returns Adjusted value.
+         */
+        private static applyColorGradingSliderNonlinear(value: number): number {
+            value /= 100;
+
+            var x: number = Math.abs(value);
+            x = Math.pow(x, 2);
+
+            if (value < 0) {
+                x *= -1;
+            }
+
+            x *= 100;
+
+            return x;
+        }
+        
+        /**
+         * Returns an RGBA Color4 based on Hue, Saturation and Brightness (also referred to as value, HSV).
+         * @param hue The hue (H) input.
+         * @param saturation The saturation (S) input.
+         * @param brightness The brightness (B) input.
+         * @result An RGBA color represented as Vector4.
+         */
+        private static fromHSBToRef(hue: number, saturation: number, brightness: number, result: BABYLON.Color4): void {
+            var h: number = ColorCurves.clamp(hue, 0, 360);
+            var s: number = ColorCurves.clamp(saturation / 100, 0, 1);
+            var v: number = ColorCurves.clamp(brightness / 100, 0, 1);
+
+            if (s === 0) {
+                result.r = v;
+                result.g = v;
+                result.b = v;
+            } else {
+                // sector 0 to 5
+                h /= 60;
+                var i = Math.floor(h);
+
+                // fractional part of h
+                var f = h - i;
+                var p = v * (1 - s);
+                var q = v * (1 - s * f);
+                var t = v * (1 - s * (1 - f));
+
+                switch (i) {
+                    case 0:
+                        result.r = v;
+                        result.g = t;
+                        result.b = p;
+                        break;
+                    case 1:
+                        result.r = q;
+                        result.g = v;
+                        result.b = p;
+                        break;
+                    case 2:
+                        result.r = p;
+                        result.g = v;
+                        result.b = t;
+                        break;
+                    case 3:
+                        result.r = p;
+                        result.g = q;
+                        result.b = v;
+                        break;
+                    case 4:
+                        result.r = t;
+                        result.g = p;
+                        result.b = v;
+                        break;
+                    default:       // case 5:
+                        result.r = v;
+                        result.g = p;
+                        result.b = q;
+                        break;
+                }
+            }
+
+            result.a = 1;
+        }
+        
+        /**
+         * Returns a value clamped between min and max
+         * @param value The value to clamp
+         * @param min The minimum of value
+         * @param max The maximum of value
+         * @returns The clamped value.
+         */
+        private static clamp(value, min, max): number {
+            return Math.min(Math.max(value, min), max);
+        }
+
+        /**
+         * Clones the current color curve instance.
+         * @return The cloned curves
+         */
+        public clone(): ColorCurves {
+            return SerializationHelper.Clone(() => new ColorCurves(), this);
+        }
+
+        /**
+         * Serializes the current color curve instance to a json representation.
+         * @return a JSON representation
+         */
+        public serialize(): any {
+            return SerializationHelper.Serialize(this);
+        }
+
+        /**
+         * Parses the color curve from a json representation.
+         * @param source the JSON source to parse
+         * @return The parsed curves
+         */      
+        public static Parse(source: any) : ColorCurves {
+            return SerializationHelper.Parse(() => new ColorCurves(), source, null, null);
+        }
+    }
+} 

+ 19 - 0
src/Materials/babylon.pbrMaterial.ts

@@ -47,6 +47,7 @@
         public CAMERATONEMAP = false;
         public CAMERACONTRAST = false;
         public CAMERACOLORGRADING = false;
+        public CAMERACOLORCURVES = false;
         public OVERLOADEDVALUES = false;
         public OVERLOADEDSHADOWVALUES = false;
         public USESPHERICALFROMREFLECTIONMAP = false;
@@ -154,6 +155,15 @@
 
         private _cameraColorGradingScaleOffset: Vector4 = new Vector4(1.0, 1.0, 0.0, 0.0);
         private _cameraColorGradingInfos: Vector4 = new Vector4(1.0, 1.0, 0.0, 0.0);
+        
+        /**
+         * The color grading curves provide additional color adjustmnent that is applied after any color grading transform (3D LUT). 
+         * They allow basic adjustment of saturation and small exposure adjustments, along with color filter tinting to provide white balance adjustment or more stylistic effects.
+         * These are similar to controls found in many professional imaging or colorist software. The global controls are applied to the entire image. For advanced tuning, extra controls are provided to adjust the shadow, midtone and highlight areas of the image; 
+         * corresponding to low luminance, medium luminance, and high luminance areas respectively.
+         */
+        @serializeAsColorCurves()
+        public cameraColorCurves: ColorCurves = null;
          
         private _cameraInfos: Vector4 = new Vector4(1.0, 1.0, 0.0, 0.0);
 
@@ -808,6 +818,10 @@
             if (this.cameraExposure != 1) {
                 this._defines.CAMERATONEMAP = true;
             }
+            
+            if (this.cameraColorCurves) {
+                this._defines.CAMERACOLORCURVES = true;
+            }
 
             if (this.overloadedShadeIntensity != 1 ||
                 this.overloadedShadowIntensity != 1) {
@@ -1016,6 +1030,7 @@
                 var samplers = ["albedoSampler", "ambientSampler", "opacitySampler", "reflectionCubeSampler", "reflection2DSampler", "emissiveSampler", "reflectivitySampler", "bumpSampler", "lightmapSampler", "refractionCubeSampler", "refraction2DSampler",
                     "cameraColorGrading2DSampler"];
                 
+                ColorCurves.PrepareUniforms(uniforms); 
                 MaterialHelper.PrepareUniformsAndSamplersList(uniforms, samplers, this._defines, this.maxSimultaneousLights); 
                 
                 this._effect = scene.getEngine().createEffect(shaderName,
@@ -1287,6 +1302,10 @@
                 this._cameraInfos.x = this.cameraExposure;
                 this._cameraInfos.y = this.cameraContrast;
                 this._effect.setVector4("vCameraInfos", this._cameraInfos);
+                
+                if (this.cameraColorCurves) {
+                    ColorCurves.Bind(this.cameraColorCurves, this._effect);
+                }
 
                 this._overloadedIntensity.x = this.overloadedAmbientIntensity;
                 this._overloadedIntensity.y = this.overloadedAlbedoIntensity;

+ 25 - 1
src/Math/babylon.math.ts

@@ -304,7 +304,31 @@
 
             return this;
         }
-
+        
+       /**
+         * Multipy an RGBA Color4 value by another and return a new Color4 object
+         * @param color The Color4 (RGBA) value to multiply by
+         * @returns A new Color4.
+         */
+        public multiply(color: Color4): Color4 {
+            return new Color4(this.r * color.r, this.g * color.g, this.b * color.b, this.a * color.a);
+        }
+        
+        /**
+         * Multipy an RGBA Color4 value by another and push the result in a reference value
+         * @param color The Color4 (RGBA) value to multiply by
+         * @param result The Color4 (RGBA) to fill the result in 
+         * @returns the result Color4.
+         */
+        public multiplyToRef(color: Color4, result: Color4): Color4 {
+            result.r = this.r * color.r;
+            result.g = this.g * color.g;
+            result.b = this.b * color.b;
+            result.a = this.a * color.a;
+            
+            return result;
+        }
+        
         public toString(): string {
             return "{R: " + this.r + " G:" + this.g + " B:" + this.b + " A:" + this.a + "}";
         }

+ 18 - 0
src/Shaders/ShadersInclude/colorCurves.fx

@@ -0,0 +1,18 @@
+const vec3 HDTVRec709_RGBLuminanceCoefficients = vec3(0.2126, 0.7152, 0.0722);
+
+vec3 applyColorCurves(vec3 original) {
+	vec3 result = original;
+
+	// Apply colour grading curves: luma-based adjustments for saturation, exposure and white balance (color filter)
+	// Note: the luma-based ramp is calibrated so that at 50% luma the midtone adjustment is full active, and the shadow/highlight 
+	// adjustments are fully active by 16% and 83% luma, respectively.
+	float luma = dot(result.rgb, HDTVRec709_RGBLuminanceCoefficients);
+
+	vec2 curveMix = clamp(vec2(luma * 3.0 - 1.5, luma * -3.0 + 1.5), vec2(0.0, 0.0), vec2(1.0, 1.0));
+	vec4 colorCurve = vCameraColorCurveNeutral + curveMix.x * vCameraColorCurvePositive - curveMix.y * vCameraColorCurveNegative;
+
+	result.rgb *= colorCurve.rgb;
+	result.rgb = mix(vec3(luma, luma, luma), result.rgb, colorCurve.a);
+
+	return result;
+}

+ 3 - 0
src/Shaders/ShadersInclude/colorCurvesDefinition.fx

@@ -0,0 +1,3 @@
+uniform vec4 vCameraColorCurveNeutral;
+uniform vec4 vCameraColorCurvePositive;
+uniform vec4 vCameraColorCurveNegative;

+ 26 - 0
src/Shaders/ShadersInclude/colorGrading.fx

@@ -0,0 +1,26 @@
+vec4 colorGrades(vec4 color) 
+{    
+    // Dynamic runtime calculations (dependent on input color)
+    float sliceContinuous = color.z * vCameraColorGradingInfos.z;
+    float sliceInteger = floor(sliceContinuous);
+
+    // Note: this is mathematically equivalent to fract(sliceContinuous); but we use explicit subtract
+    // rather than separate fract() for correct results near slice boundaries (matching sliceInteger choice)
+    float sliceFraction = sliceContinuous - sliceInteger; 
+
+    // Calculate UV offset from slice origin (top-left)
+    vec2 sliceUV = color.xy * vCameraColorGradingScaleOffset.xy + vCameraColorGradingScaleOffset.zw;
+
+    // Calculate UV positions into overall texture for neighbouring slices 
+    // (to emulate trilinear filtering on missing 3D hardware texture support)
+    sliceUV.x += sliceInteger * vCameraColorGradingInfos.w;
+    vec4 slice0Color = texture2D(cameraColorGrading2DSampler, sliceUV);
+
+    sliceUV.x += vCameraColorGradingInfos.w;
+    vec4 slice1Color = texture2D(cameraColorGrading2DSampler, sliceUV);
+
+    vec3 result = mix(slice0Color.rgb, slice1Color.rgb, sliceFraction);
+    color.rgb = mix(color.rgb, result, vCameraColorGradingInfos.x);
+
+    return color;
+}

+ 3 - 0
src/Shaders/ShadersInclude/colorGradingDefinition.fx

@@ -0,0 +1,3 @@
+uniform sampler2D cameraColorGrading2DSampler;
+uniform vec4 vCameraColorGradingInfos;
+uniform vec4 vCameraColorGradingScaleOffset;

+ 0 - 29
src/Shaders/ShadersInclude/pbrFunctions.fx

@@ -198,33 +198,4 @@ float computeLightFalloff(vec3 lightOffset, float lightDistanceSquared, float ra
 
         return color;
     }
-#endif
-
-#ifdef CAMERACOLORGRADING
-    vec4 colorGrades(vec4 color, sampler2D texture, vec4 vCameraColorGradingInfos, vec4 vCameraColorGradingScaleOffset) 
-    {
-        // Dynamic runtime calculations (dependent on input color)
-        float sliceContinuous = color.z * vCameraColorGradingInfos.z;
-        float sliceInteger = floor(sliceContinuous);
-
-        // Note: this is mathematically equivalent to fract(sliceContinuous); but we use explicit subtract
-        // rather than separate fract() for correct results near slice boundaries (matching sliceInteger choice)
-        float sliceFraction = sliceContinuous - sliceInteger; 
-
-        // Calculate UV offset from slice origin (top-left)
-        vec2 sliceUV = color.xy * vCameraColorGradingScaleOffset.xy + vCameraColorGradingScaleOffset.zw;
-
-        // Calculate UV positions into overall texture for neighbouring slices 
-        // (to emulate trilinear filtering on missing 3D hardware texture support)
-        sliceUV.x += sliceInteger * vCameraColorGradingInfos.w;
-        vec4 slice0Color = texture2D(texture, sliceUV);
-
-        sliceUV.x += vCameraColorGradingInfos.w;
-        vec4 slice1Color = texture2D(texture, sliceUV);
-
-        vec3 result = mix(slice0Color.rgb, slice1Color.rgb, sliceFraction);
-        color.rgb = mix(color.rgb, result, vCameraColorGradingInfos.x);
-
-        return color;
-    }
 #endif

+ 19 - 4
src/Shaders/pbr.fragment.fx

@@ -149,14 +149,25 @@ uniform mat4 reflectionMatrix;
 #endif
 
 #ifdef CAMERACOLORGRADING
-uniform sampler2D cameraColorGrading2DSampler;
-uniform vec4 vCameraColorGradingInfos;
-uniform vec4 vCameraColorGradingScaleOffset;
+	#include<colorGradingDefinition>
+#endif
+
+#ifdef CAMERACOLORCURVES
+	#include<colorCurvesDefinition>
 #endif
 
 // PBR
 #include<pbrShadowFunctions>
 #include<pbrFunctions>
+
+#ifdef CAMERACOLORGRADING
+	#include<colorGrading>
+#endif
+
+#ifdef CAMERACOLORCURVES
+	#include<colorCurves>
+#endif
+
 #include<harmonicsFunctions>
 #include<pbrLightFunctions>
 
@@ -599,7 +610,11 @@ void main(void) {
 	finalColor.rgb = clamp(finalColor.rgb, 0., 1.);
 
 #ifdef CAMERACOLORGRADING
-	finalColor = colorGrades(finalColor, cameraColorGrading2DSampler, vCameraColorGradingInfos, vCameraColorGradingScaleOffset);
+	finalColor = colorGrades(finalColor);
+#endif
+
+#ifdef CAMERACOLORCURVES
+	finalColor.rgb = applyColorCurves(finalColor.rgb);
 #endif
 
 	// Normal Display.

+ 11 - 0
src/Tools/babylon.decorators.ts

@@ -36,6 +36,10 @@
     export function serializeAsMeshReference(sourceName?: string) {
         return generateSerializableMember(6, sourceName); // mesh reference member
     }
+    
+    export function serializeAsColorCurves(sourceName?: string) {
+        return generateSerializableMember(7, sourceName); // color curves
+    }
 
     export class SerializationHelper {
 
@@ -77,6 +81,9 @@
                         case 6:     // Mesh reference
                             serializationObject[targetPropertyName] = sourceProperty.id;
                             break;
+                        case 7:     // Color Curves
+                            serializationObject[targetPropertyName] = sourceProperty.serialize();
+                            break;
                     }
                 }
             }
@@ -119,6 +126,9 @@
                         case 6:     // Mesh reference
                             destination[property] = scene.getLastMeshByID(sourceProperty);
                             break;
+                        case 7:     // Color Curves
+                            destination[property] = ColorCurves.Parse(sourceProperty);
+                            break;
                     }
                 }
             }
@@ -149,6 +159,7 @@
                         case 3:     // FresnelParameters
                         case 4:     // Vector2
                         case 5:     // Vector3
+                        case 7:     // Color Curves
                             destination[property] = sourceProperty.clone();
                             break;
                     }