Parcourir la source

Merge pull request #1122 from sebavan/ColorGrading

Color grading
David Catuhe il y a 9 ans
Parent
commit
6cb4995095

+ 1 - 0
Tools/Gulp/config.json

@@ -212,6 +212,7 @@
       "../../src/tools/hdr/babylon.tools.pmremGenerator.js",
       "../../src/materials/textures/babylon.hdrcubetexture.js",
       "../../src/debug/babylon.skeletonViewer.js",
+      "../../src/Materials/Textures/babylon.colorGradingTexture.js",
       "../../src/materials/babylon.pbrmaterial.js"
     ]
   }

+ 169 - 0
src/Materials/Textures/babylon.colorGradingTexture.js

@@ -0,0 +1,169 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * This represents a color grading texture. This acts as a lookup table LUT, useful during post process
+     * It can help converting any input color in a desired output one. This can then be used to create effects
+     * from sepia, black and white to sixties or futuristic rendering...
+     *
+     * The only supported format is currently 3dl.
+     * More information on LUT: https://en.wikipedia.org/wiki/3D_lookup_table/
+     */
+    var ColorGradingTexture = (function (_super) {
+        __extends(ColorGradingTexture, _super);
+        /**
+         * Instantiates a ColorGradingTexture from the following parameters.
+         *
+         * @param url The location of the color gradind data (currently only supporting 3dl)
+         * @param scene The scene the texture will be used in
+         */
+        function ColorGradingTexture(url, scene) {
+            _super.call(this, scene);
+            if (!url) {
+                return;
+            }
+            this._textureMatrix = BABYLON.Matrix.Identity();
+            this.name = url;
+            this.url = url;
+            this.hasAlpha = false;
+            this.isCube = false;
+            this._texture = this._getFromCache(url, true);
+            if (!this._texture) {
+                if (!scene.useDelayedTextureLoading) {
+                    this.loadTexture();
+                }
+                else {
+                    this.delayLoadState = BABYLON.Engine.DELAYLOADSTATE_NOTLOADED;
+                }
+            }
+        }
+        /**
+         * Returns the texture matrix used in most of the material.
+         * This is not used in color grading but keep for troubleshooting purpose (easily swap diffuse by colorgrading to look in).
+         */
+        ColorGradingTexture.prototype.getTextureMatrix = function () {
+            return this._textureMatrix;
+        };
+        /**
+         * Occurs when the file being loaded is a .3dl LUT file.
+         */
+        ColorGradingTexture.prototype.load3dlTexture = function () {
+            var _this = this;
+            var mipLevels = 0;
+            var floatArrayView = null;
+            var texture = this.getScene().getEngine().createRawTexture(null, 1, 1, BABYLON.Engine.TEXTUREFORMAT_RGB, false, false, BABYLON.Texture.NEAREST_SAMPLINGMODE);
+            this._texture = texture;
+            var callback = function (text) {
+                var buffer;
+                var data;
+                var line;
+                var lines = text.split('\n');
+                var size = 0, pixelIndex = 0;
+                for (var i = 0; i < lines.length; i++) {
+                    line = lines[i];
+                    if (!ColorGradingTexture._noneEmptyLineRegex.test(line))
+                        continue;
+                    if (line.indexOf('#') === 0)
+                        continue;
+                    var words = line.split(" ");
+                    if (size === 0) {
+                        // Number of space + one
+                        size = words.length;
+                        buffer = new ArrayBuffer(size * size * size * 3); // volume texture of side size and rgb 8
+                        data = new Uint8Array(buffer);
+                        continue;
+                    }
+                    if (size != 0) {
+                        var r = parseInt(words[0]);
+                        var g = parseInt(words[1]);
+                        var b = parseInt(words[2]);
+                        r = Math.round(Math.max(Math.min(255, r), 0));
+                        g = Math.round(Math.max(Math.min(255, g), 0));
+                        b = Math.round(Math.max(Math.min(255, b), 0));
+                        // Transpose ordering from RGB to BGR (naming here might also be transposed).
+                        var indexR = pixelIndex % size;
+                        var indexG = (pixelIndex / size) % size;
+                        var indexB = (pixelIndex / size) / size;
+                        var pixelStorageIndex = indexR * (size * size * 3) + indexG * size * 3 + indexB * 3;
+                        data[pixelStorageIndex + 0] = r; //255;//r;
+                        data[pixelStorageIndex + 1] = g; //0;//g;
+                        data[pixelStorageIndex + 2] = b; //255;//b;
+                        pixelIndex++;
+                    }
+                }
+                _this.getScene().getEngine().updateRawTexture(texture, data, BABYLON.Engine.TEXTUREFORMAT_RGB, false);
+                _this.getScene().getEngine().updateTextureSize(texture, size * size, size);
+            };
+            BABYLON.Tools.LoadFile(this.url, callback);
+            return this._texture;
+        };
+        /**
+         * Starts the loading process of the texture.
+         */
+        ColorGradingTexture.prototype.loadTexture = function () {
+            if (this.url && this.url.toLocaleLowerCase().indexOf(".3dl") == (this.url.length - 4)) {
+                this.load3dlTexture();
+            }
+        };
+        /**
+         * Clones the color gradind texture.
+         */
+        ColorGradingTexture.prototype.clone = function () {
+            var newTexture = new ColorGradingTexture(this.url, this.getScene());
+            // Base texture
+            newTexture.level = this.level;
+            return newTexture;
+        };
+        /**
+         * Called during delayed load for textures.
+         */
+        ColorGradingTexture.prototype.delayLoad = function () {
+            if (this.delayLoadState !== BABYLON.Engine.DELAYLOADSTATE_NOTLOADED) {
+                return;
+            }
+            this.delayLoadState = BABYLON.Engine.DELAYLOADSTATE_LOADED;
+            this._texture = this._getFromCache(this.url, true);
+            if (!this._texture) {
+                this.loadTexture();
+            }
+        };
+        /**
+         * Parses a color grading texture serialized by Babylon.
+         * @param parsedTexture The texture information being parsedTexture
+         * @param scene The scene to load the texture in
+         * @param rootUrl The root url of the data assets to load
+         * @return A color gradind texture
+         */
+        ColorGradingTexture.Parse = function (parsedTexture, scene, rootUrl) {
+            var texture = null;
+            if (parsedTexture.name && !parsedTexture.isRenderTarget) {
+                texture = new BABYLON.ColorGradingTexture(parsedTexture.name, scene);
+                texture.name = parsedTexture.name;
+                texture.level = parsedTexture.level;
+            }
+            return texture;
+        };
+        /**
+         * Serializes the LUT texture to json format.
+         */
+        ColorGradingTexture.prototype.serialize = function () {
+            if (!this.name) {
+                return null;
+            }
+            var serializationObject = {};
+            serializationObject.name = this.name;
+            serializationObject.level = this.level;
+            return serializationObject;
+        };
+        /**
+         * Empty line regex stored for GC.
+         */
+        ColorGradingTexture._noneEmptyLineRegex = /\S+/;
+        return ColorGradingTexture;
+    }(BABYLON.BaseTexture));
+    BABYLON.ColorGradingTexture = ColorGradingTexture;
+})(BABYLON || (BABYLON = {}));

+ 220 - 0
src/Materials/Textures/babylon.colorGradingTexture.ts

@@ -0,0 +1,220 @@
+module BABYLON {
+
+    /**
+     * This represents a color grading texture. This acts as a lookup table LUT, useful during post process
+     * It can help converting any input color in a desired output one. This can then be used to create effects
+     * from sepia, black and white to sixties or futuristic rendering...
+     * 
+     * The only supported format is currently 3dl.
+     * More information on LUT: https://en.wikipedia.org/wiki/3D_lookup_table/
+     */
+    export class ColorGradingTexture extends BaseTexture {
+
+        /**
+         * The current internal texture size.
+         */        
+        private _size: number;
+        
+        /**
+         * The current texture matrix. (will always be identity in color grading texture)
+         */
+        private _textureMatrix: Matrix;
+        
+        /**
+         * The texture URL.
+         */
+        public url: string;
+
+        /**
+         * Empty line regex stored for GC.
+         */
+        private static _noneEmptyLineRegex = /\S+/;
+        
+        /**
+         * Instantiates a ColorGradingTexture from the following parameters.
+         * 
+         * @param url The location of the color gradind data (currently only supporting 3dl)
+         * @param scene The scene the texture will be used in
+         */
+        constructor(url: string, scene: Scene) {
+            super(scene);
+
+            if (!url) {
+                return;
+            }
+
+            this._textureMatrix = Matrix.Identity();
+            this.name = url;
+            this.url = url;
+            this.hasAlpha = false;
+            this.isCube = false;
+            this.wrapU = Texture.CLAMP_ADDRESSMODE;
+            this.wrapV = Texture.CLAMP_ADDRESSMODE;
+            
+            this._texture = this._getFromCache(url, true);
+
+            if (!this._texture) {
+                if (!scene.useDelayedTextureLoading) {
+                    this.loadTexture();
+                } else {
+                    this.delayLoadState = Engine.DELAYLOADSTATE_NOTLOADED;
+                }
+            }
+        }
+
+        /**
+         * Returns the texture matrix used in most of the material.
+         * This is not used in color grading but keep for troubleshooting purpose (easily swap diffuse by colorgrading to look in).
+         */
+        public getTextureMatrix(): Matrix {
+            return this._textureMatrix;
+        }
+        
+        /**
+         * Occurs when the file being loaded is a .3dl LUT file.
+         */
+        private load3dlTexture() {
+
+            var mipLevels = 0;
+            var floatArrayView: Float32Array = null;
+            var texture = this.getScene().getEngine().createRawTexture(null, 1, 1, BABYLON.Engine.TEXTUREFORMAT_RGB, false, false, Texture.BILINEAR_SAMPLINGMODE);
+            this._texture = texture;
+            
+            var callback = (text: string) => {
+                var data: Uint8Array;
+                var tempData: Float32Array;
+                
+                var line: string;
+                var lines = text.split('\n');
+                var size = 0, pixelIndexW = 0, pixelIndexH = 0, pixelIndexSlice = 0;
+                var maxColor = 0;
+                
+                for (let i = 0; i < lines.length; i++) {
+                    line = lines[i];
+                    
+                    if (!ColorGradingTexture._noneEmptyLineRegex.test(line))
+                        continue;
+                        
+                    if (line.indexOf('#') === 0)
+                        continue;
+                    
+                    var words = line.split(" ");
+                    if (size === 0) {
+                        // Number of space + one
+                        size = words.length;
+                        data = new Uint8Array(size * size * size * 3); // volume texture of side size and rgb 8
+                        tempData = new Float32Array(size * size * size * 3);
+                        continue;
+                    }
+                    
+                    if (size != 0)
+                    {
+                        var r = Math.max(parseInt(words[0]), 0);
+                        var g = Math.max(parseInt(words[1]), 0);
+                        var b = Math.max(parseInt(words[2]), 0);
+                        
+                        maxColor = Math.max(r, maxColor);
+                        maxColor = Math.max(g, maxColor);
+                        maxColor = Math.max(b, maxColor);
+                        
+                        var pixelStorageIndex = (pixelIndexW + pixelIndexSlice * size + pixelIndexH * size * size) * 3;
+                        
+                        tempData[pixelStorageIndex + 0] = r;
+                        tempData[pixelStorageIndex + 1] = g;
+                        tempData[pixelStorageIndex + 2] = b;
+                        
+                        pixelIndexSlice++;
+                        if (pixelIndexSlice % size == 0) {
+                            pixelIndexH++;
+                            pixelIndexSlice = 0;
+                            if (pixelIndexH % size == 0) {
+                                pixelIndexW++;
+                                pixelIndexH = 0;
+                            }
+                        }
+                    }
+                }
+            
+                for (let i = 0; i < tempData.length; i++) {
+                    var value = tempData[i];
+                    data[i] = (value / maxColor * 255);
+                }
+                
+                this.getScene().getEngine().updateTextureSize(texture, size * size, size);
+                this.getScene().getEngine().updateRawTexture(texture, data, BABYLON.Engine.TEXTUREFORMAT_RGB, false);
+            }
+
+            Tools.LoadFile(this.url, callback);
+            return this._texture;
+        }
+
+        /**
+         * Starts the loading process of the texture.
+         */
+        private loadTexture() {
+            if (this.url && this.url.toLocaleLowerCase().indexOf(".3dl") == (this.url.length - 4)) {
+                this.load3dlTexture();
+            }
+        }
+
+        /**
+         * Clones the color gradind texture.
+         */
+        public clone(): ColorGradingTexture {
+            var newTexture = new ColorGradingTexture(this.url, this.getScene());
+
+            // Base texture
+            newTexture.level = this.level;
+
+            return newTexture;
+        }
+
+        /**
+         * Called during delayed load for textures.
+         */
+        public delayLoad(): void {
+            if (this.delayLoadState !== Engine.DELAYLOADSTATE_NOTLOADED) {
+                return;
+            }
+
+            this.delayLoadState = Engine.DELAYLOADSTATE_LOADED;
+            this._texture = this._getFromCache(this.url, true);
+
+            if (!this._texture) {
+                this.loadTexture();
+            }
+        }
+
+        /**
+         * Parses a color grading texture serialized by Babylon.
+         * @param parsedTexture The texture information being parsedTexture
+         * @param scene The scene to load the texture in
+         * @param rootUrl The root url of the data assets to load
+         * @return A color gradind texture
+         */
+        public static Parse(parsedTexture: any, scene: Scene, rootUrl: string): ColorGradingTexture {
+            var texture = null;
+            if (parsedTexture.name && !parsedTexture.isRenderTarget) {
+                texture = new BABYLON.ColorGradingTexture(parsedTexture.name, scene);
+                texture.name = parsedTexture.name;
+                texture.level = parsedTexture.level;
+            }
+            return texture;
+        }
+        
+        /**
+         * Serializes the LUT texture to json format.
+         */
+        public serialize(): any {
+            if (!this.name) {
+                return null;
+            }
+
+            var serializationObject: any = {};
+            serializationObject.name = this.name;
+            serializationObject.level = this.level;
+
+            return serializationObject;
+        }
+    }
+}

+ 87 - 63
src/Materials/babylon.pbrMaterial.ts

@@ -1,6 +1,4 @@
 module BABYLON {
-    var maxSimultaneousLights = 4;
-
     class PBRMaterialDefines extends MaterialDefines {
         public ALBEDO = false;
         public AMBIENT = false;
@@ -18,40 +16,7 @@
         public ALPHAFROMALBEDO = false;
         public POINTSIZE = false;
         public FOG = false;
-        public LIGHT0 = false;
-        public LIGHT1 = false;
-        public LIGHT2 = false;
-        public LIGHT3 = false;
-        public SPOTLIGHT0 = false;
-        public SPOTLIGHT1 = false;
-        public SPOTLIGHT2 = false;
-        public SPOTLIGHT3 = false;
-        public HEMILIGHT0 = false;
-        public HEMILIGHT1 = false;
-        public HEMILIGHT2 = false;
-        public HEMILIGHT3 = false;
-        public POINTLIGHT0 = false;
-        public POINTLIGHT1 = false;
-        public POINTLIGHT2 = false;
-        public POINTLIGHT3 = false;
-        public DIRLIGHT0 = false;
-        public DIRLIGHT1 = false;
-        public DIRLIGHT2 = false;
-        public DIRLIGHT3 = false;
         public SPECULARTERM = false;
-        public SHADOW0 = false;
-        public SHADOW1 = false;
-        public SHADOW2 = false;
-        public SHADOW3 = false;
-        public SHADOWS = false;
-        public SHADOWVSM0 = false;
-        public SHADOWVSM1 = false;
-        public SHADOWVSM2 = false;
-        public SHADOWVSM3 = false;
-        public SHADOWPCF0 = false;
-        public SHADOWPCF1 = false;
-        public SHADOWPCF2 = false;
-        public SHADOWPCF3 = false;
         public OPACITYFRESNEL = false;
         public EMISSIVEFRESNEL = false;
         public FRESNEL = false;
@@ -81,6 +46,7 @@
         public LOGARITHMICDEPTH = false;
         public CAMERATONEMAP = false;
         public CAMERACONTRAST = false;
+        public CAMERACOLORGRADING = false;
         public OVERLOADEDVALUES = false;
         public OVERLOADEDSHADOWVALUES = false;
         public USESPHERICALFROMREFLECTIONMAP = false;
@@ -96,7 +62,7 @@
 
         constructor() {
             super();
-            this._keys = Object.keys(this);
+            this.rebuild();
         }
     }
 
@@ -175,7 +141,17 @@
          */
         @serialize()
         public cameraContrast: number = 1.0;
+        
+        /**
+         * Color Grading 2D Lookup Texture.
+         * This allows special effects like sepia, black and white to sixties rendering style. 
+         */
+        @serializeAsTexture()
+        public cameraColorGradingTexture: BaseTexture = null;
 
+        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);
+         
         private _cameraInfos: Vector4 = new Vector4(1.0, 1.0, 0.0, 0.0);
 
         private _microsurfaceTextureLods: Vector2 = new Vector2(0.0, 0.0);
@@ -432,10 +408,19 @@
          */
         @serialize()
         public parallaxScaleBias = 0.05;
-
+        
+        /**
+         * If sets to true, disables all the lights affecting the material.
+         */
         @serialize()
         public disableLighting = false;
 
+        /**
+         * Number of Simultaneous lights allowed on the material.
+         */
+        @serialize()
+        public maxSimultaneousLights = 4;  
+
         private _renderTargets = new SmartArray<RenderTargetTexture>(16);
         private _worldViewProjectionMatrix = Matrix.Zero();
         private _globalAmbientColor = new Color3(0, 0, 0);
@@ -538,9 +523,8 @@
         private static _scaledReflectivity = new Color3();
         private static _scaledEmissive = new Color3();
         private static _scaledReflection = new Color3();
-        private static _lightRadiuses = [1, 1, 1, 1];
 
-        public static BindLights(scene: Scene, mesh: AbstractMesh, effect: Effect, defines: MaterialDefines, useScalarInLinearSpace: boolean) {
+        public static BindLights(scene: Scene, mesh: AbstractMesh, effect: Effect, defines: MaterialDefines, useScalarInLinearSpace: boolean, maxSimultaneousLights: number, usePhysicalLightFalloff: boolean) {
             var lightIndex = 0;
             var depthValuesAlreadySet = false;
             for (var index = 0; index < scene.lights.length; index++) {
@@ -554,15 +538,13 @@
                     continue;
                 }
 
-                this._lightRadiuses[lightIndex] = light.radius;
-
                 MaterialHelper.BindLightProperties(light, effect, lightIndex);
 
                 // GAMMA CORRECTION.
                 this.convertColorToLinearSpaceToRef(light.diffuse, PBRMaterial._scaledAlbedo, useScalarInLinearSpace);
 
                 PBRMaterial._scaledAlbedo.scaleToRef(light.intensity, PBRMaterial._scaledAlbedo);
-                effect.setColor4("vLightDiffuse" + lightIndex, PBRMaterial._scaledAlbedo, light.range);
+                effect.setColor4("vLightDiffuse" + lightIndex, PBRMaterial._scaledAlbedo, usePhysicalLightFalloff ? light.radius : light.range);
 
                 if (defines["SPECULARTERM"]) {
                     this.convertColorToLinearSpaceToRef(light.specular, PBRMaterial._scaledReflectivity, useScalarInLinearSpace);
@@ -581,11 +563,6 @@
                 if (lightIndex === maxSimultaneousLights)
                     break;
             }
-
-            effect.setFloat4("vLightRadiuses", this._lightRadiuses[0],
-                this._lightRadiuses[1],
-                this._lightRadiuses[2],
-                this._lightRadiuses[3]);
         }
 
         public isReady(mesh?: AbstractMesh, useInstances?: boolean): boolean {
@@ -766,6 +743,14 @@
                         }
                     }
                 }
+            
+                if (this.cameraColorGradingTexture) {
+                    if (!this.cameraColorGradingTexture.isReady()) {
+                        return false;
+                    } else {
+                        this._defines.CAMERACOLORGRADING = true;
+                    }
+                }
             }
 
             // Effect
@@ -826,7 +811,7 @@
             }
 
             if (scene.lightsEnabled && !this.disableLighting) {
-                needNormals = MaterialHelper.PrepareDefinesForLights(scene, mesh, this._defines) || needNormals;
+                needNormals = MaterialHelper.PrepareDefinesForLights(scene, mesh, this._defines, this.maxSimultaneousLights) || needNormals;
             }
 
             if (StandardMaterial.FresnelEnabled) {
@@ -938,7 +923,7 @@
                     fallbacks.addFallback(0, "LOGARITHMICDEPTH");
                 }
 
-                MaterialHelper.HandleFallbacksForShadows(this._defines, fallbacks);
+                MaterialHelper.HandleFallbacksForShadows(this._defines, fallbacks, this.maxSimultaneousLights);
 
                 if (this._defines.SPECULARTERM) {
                     fallbacks.addFallback(0, "SPECULARTERM");
@@ -988,30 +973,32 @@
                     shaderName = "legacypbr";
                 }
                 var join = this._defines.toString();
-                this._effect = scene.getEngine().createEffect(shaderName,
-                    attribs,
-                    ["world", "view", "viewProjection", "vEyePosition", "vLightsType", "vAmbientColor", "vAlbedoColor", "vReflectivityColor", "vEmissiveColor", "vReflectionColor",
-                        "vLightData0", "vLightDiffuse0", "vLightSpecular0", "vLightDirection0", "vLightGround0", "lightMatrix0",
-                        "vLightData1", "vLightDiffuse1", "vLightSpecular1", "vLightDirection1", "vLightGround1", "lightMatrix1",
-                        "vLightData2", "vLightDiffuse2", "vLightSpecular2", "vLightDirection2", "vLightGround2", "lightMatrix2",
-                        "vLightData3", "vLightDiffuse3", "vLightSpecular3", "vLightDirection3", "vLightGround3", "lightMatrix3",
+                
+                var uniforms = ["world", "view", "viewProjection", "vEyePosition", "vLightsType", "vAmbientColor", "vAlbedoColor", "vReflectivityColor", "vEmissiveColor", "vReflectionColor",
                         "vFogInfos", "vFogColor", "pointSize",
                         "vAlbedoInfos", "vAmbientInfos", "vOpacityInfos", "vReflectionInfos", "vEmissiveInfos", "vReflectivityInfos", "vBumpInfos", "vLightmapInfos", "vRefractionInfos",
                         "mBones",
                         "vClipPlane", "albedoMatrix", "ambientMatrix", "opacityMatrix", "reflectionMatrix", "emissiveMatrix", "reflectivityMatrix", "bumpMatrix", "lightmapMatrix", "refractionMatrix",
-                        "shadowsInfo0", "shadowsInfo1", "shadowsInfo2", "shadowsInfo3", "depthValues",
+                        "depthValues",
                         "opacityParts", "emissiveLeftColor", "emissiveRightColor",
-                        "vLightingIntensity", "vOverloadedShadowIntensity", "vOverloadedIntensity", "vCameraInfos", "vOverloadedAlbedo", "vOverloadedReflection", "vOverloadedReflectivity", "vOverloadedEmissive", "vOverloadedMicroSurface",
+                        "vLightingIntensity", "vOverloadedShadowIntensity", "vOverloadedIntensity", "vOverloadedAlbedo", "vOverloadedReflection", "vOverloadedReflectivity", "vOverloadedEmissive", "vOverloadedMicroSurface",
                         "logarithmicDepthConstant",
                         "vSphericalX", "vSphericalY", "vSphericalZ",
                         "vSphericalXX", "vSphericalYY", "vSphericalZZ",
                         "vSphericalXY", "vSphericalYZ", "vSphericalZX",
-                        "vMicrosurfaceTextureLods", "vLightRadiuses"
-                    ],
+                        "vMicrosurfaceTextureLods",
+                        "vCameraInfos", "vCameraColorGradingInfos", "vCameraColorGradingScaleOffset"
+                    ];
+                
+                MaterialHelper.PrepareUniformsListForList(uniforms, this._defines, this.maxSimultaneousLights); 
+                
+                this._effect = scene.getEngine().createEffect(shaderName,
+                    attribs, uniforms,
                     ["albedoSampler", "ambientSampler", "opacitySampler", "reflectionCubeSampler", "reflection2DSampler", "emissiveSampler", "reflectivitySampler", "bumpSampler", "lightmapSampler", "refractionCubeSampler", "refraction2DSampler",
-                        "shadowSampler0", "shadowSampler1", "shadowSampler2", "shadowSampler3"
+                        "shadowSampler0", "shadowSampler1", "shadowSampler2", "shadowSampler3",
+                        "cameraColorGrading2DSampler"
                     ],
-                    join, fallbacks, this.onCompiled, this.onError);
+                    join, fallbacks, this.onCompiled, this.onError, {maxSimultaneousLights: this.maxSimultaneousLights});
             }
             if (!this._effect.isReady()) {
                 return false;
@@ -1188,6 +1175,35 @@
                     if ((this.reflectionTexture || this.refractionTexture)) {
                         this._effect.setFloat2("vMicrosurfaceTextureLods", this._microsurfaceTextureLods.x, this._microsurfaceTextureLods.y);
                     }
+                    
+                    if (this.cameraColorGradingTexture) {
+                        this._effect.setTexture("cameraColorGrading2DSampler", this.cameraColorGradingTexture);
+                        
+                        this._cameraColorGradingInfos.x = this.cameraColorGradingTexture.level;                     // Texture Level
+                        this._cameraColorGradingInfos.y = this.cameraColorGradingTexture.getSize().height;          // Texture Size example with 8
+                        this._cameraColorGradingInfos.z = this._cameraColorGradingInfos.y - 1.0;                    // SizeMinusOne 8 - 1
+                        this._cameraColorGradingInfos.w = 1 / this._cameraColorGradingInfos.y;                      // Space of 1 slice 1 / 8
+                        
+                        this._effect.setFloat4("vCameraColorGradingInfos", 
+                            this._cameraColorGradingInfos.x,
+                            this._cameraColorGradingInfos.y,
+                            this._cameraColorGradingInfos.z,
+                            this._cameraColorGradingInfos.w);
+                        
+                        var slicePixelSizeU = this._cameraColorGradingInfos.w / this._cameraColorGradingInfos.y;    // Space of 1 pixel in U direction, e.g. 1/64
+                        var slicePixelSizeV = 1.0 / this._cameraColorGradingInfos.y;							    // Space of 1 pixel in V direction, e.g. 1/8
+                        this._cameraColorGradingScaleOffset.x = this._cameraColorGradingInfos.z * slicePixelSizeU;  // Extent of lookup range in U for a single slice so that range corresponds to (size-1) texels, for example 7/64
+                        this._cameraColorGradingScaleOffset.y = this._cameraColorGradingInfos.z / 
+                            this._cameraColorGradingInfos.y;							                            // Extent of lookup range in V for a single slice so that range corresponds to (size-1) texels, for example 7/8
+                        this._cameraColorGradingScaleOffset.z = 0.5 * slicePixelSizeU;						        // Offset of lookup range in U to align sample position with texel centre, for example 0.5/64 
+                        this._cameraColorGradingScaleOffset.w = 0.5 * slicePixelSizeV;						        // Offset of lookup range in V to align sample position with texel centre, for example 0.5/8
+                        
+                        this._effect.setFloat4("vCameraColorGradingScaleOffset", 
+                            this._cameraColorGradingScaleOffset.x,
+                            this._cameraColorGradingScaleOffset.y,
+                            this._cameraColorGradingScaleOffset.z,
+                            this._cameraColorGradingScaleOffset.w);
+                    }
                 }
 
                 // Clip plane
@@ -1224,7 +1240,7 @@
 
                 // Lights
                 if (this._myScene.lightsEnabled && !this.disableLighting) {
-                    PBRMaterial.BindLights(this._myScene, mesh, this._effect, this._defines, this.useScalarInLinearSpace);
+                    PBRMaterial.BindLights(this._myScene, mesh, this._effect, this._defines, this.useScalarInLinearSpace, this.maxSimultaneousLights, this.usePhysicalLightFalloff);
                 }
 
                 // View
@@ -1318,6 +1334,10 @@
             if (this.refractionTexture && this.refractionTexture.animations && this.refractionTexture.animations.length > 0) {
                 results.push(this.refractionTexture);
             }
+            
+            if (this.cameraColorGradingTexture && this.cameraColorGradingTexture.animations && this.cameraColorGradingTexture.animations.length > 0) {
+                results.push(this.cameraColorGradingTexture);
+            }
 
             return results;
         }
@@ -1359,6 +1379,10 @@
                 if (this.refractionTexture) {
                     this.refractionTexture.dispose();
                 }
+                
+                if (this.cameraColorGradingTexture) {
+                    this.cameraColorGradingTexture.dispose();
+                }
             }
 
             super.dispose(forceDisposeEffect, forceDisposeTextures);

+ 38 - 5
src/Shaders/ShadersInclude/pbrFunctions.fx

@@ -118,11 +118,15 @@ float computeDiffuseTerm(float NdotL, float NdotV, float VdotH, float roughness)
 
 float adjustRoughnessFromLightProperties(float roughness, float lightRadius, float lightDistance)
 {
-    // At small angle this approximation works. 
-    float lightRoughness = lightRadius / lightDistance;
-    // Distribution can sum.
-    float totalRoughness = clamp(lightRoughness + roughness, 0., 1.);
-    return totalRoughness;
+    #ifdef USEPHYSICALLIGHTFALLOFF
+        // At small angle this approximation works. 
+        float lightRoughness = lightRadius / lightDistance;
+        // Distribution can sum.
+        float totalRoughness = clamp(lightRoughness + roughness, 0., 1.);
+        return totalRoughness;
+    #else
+        return roughness;
+    #endif
 }
 
 float computeDefaultMicroSurface(float microSurface, vec3 reflectivityColor)
@@ -194,4 +198,33 @@ 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

+ 7 - 7
src/Shaders/ShadersInclude/pbrLightFunctions.fx

@@ -7,7 +7,7 @@ struct lightingInfo
     #endif
 };
 
-lightingInfo computeLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 specularColor, float range, float roughness, float NdotV, float lightRadius, out float NdotL) {
+lightingInfo computeLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 specularColor, float rangeRadius, float roughness, float NdotV, out float NdotL) {
     lightingInfo result;
 
     vec3 lightDirection;
@@ -19,7 +19,7 @@ lightingInfo computeLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData,
     {
         vec3 lightOffset = lightData.xyz - vPositionW;
         float lightDistanceSquared = dot(lightOffset, lightOffset);
-        attenuation = computeLightFalloff(lightOffset, lightDistanceSquared, range);
+        attenuation = computeLightFalloff(lightOffset, lightDistanceSquared, rangeRadius);
         
         lightDistance = sqrt(lightDistanceSquared);
         lightDirection = normalize(lightOffset);
@@ -32,7 +32,7 @@ lightingInfo computeLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData,
     }
     
     // Roughness
-    roughness = adjustRoughnessFromLightProperties(roughness, lightRadius, lightDistance);
+    roughness = adjustRoughnessFromLightProperties(roughness, rangeRadius, lightDistance);
     
     // diffuse
     vec3 H = normalize(viewDirectionW + lightDirection);
@@ -53,7 +53,7 @@ lightingInfo computeLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData,
     return result;
 }
 
-lightingInfo computeSpotLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec4 lightDirection, vec3 diffuseColor, vec3 specularColor, float range, float roughness, float NdotV, float lightRadius, out float NdotL) {
+lightingInfo computeSpotLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec4 lightDirection, vec3 diffuseColor, vec3 specularColor, float rangeRadius, float roughness, float NdotV, out float NdotL) {
     lightingInfo result;
 
     vec3 lightOffset = lightData.xyz - vPositionW;
@@ -68,14 +68,14 @@ lightingInfo computeSpotLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightDa
         
         // Inverse squared falloff.
         float lightDistanceSquared = dot(lightOffset, lightOffset);
-        float attenuation = computeLightFalloff(lightOffset, lightDistanceSquared, range);
+        float attenuation = computeLightFalloff(lightOffset, lightDistanceSquared, rangeRadius);
         
         // Directional falloff.
         attenuation *= cosAngle;
         
         // Roughness.
         float lightDistance = sqrt(lightDistanceSquared);
-        roughness = adjustRoughnessFromLightProperties(roughness, lightRadius, lightDistance);
+        roughness = adjustRoughnessFromLightProperties(roughness, rangeRadius, lightDistance);
         
         // Diffuse
         vec3 H = normalize(viewDirectionW - lightDirection.xyz);
@@ -104,7 +104,7 @@ lightingInfo computeSpotLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightDa
     return result;
 }
 
-lightingInfo computeHemisphericLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 specularColor, vec3 groundColor, float roughness, float NdotV, float lightRadius, out float NdotL) {
+lightingInfo computeHemisphericLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 specularColor, vec3 groundColor, float roughness, float NdotV, out float NdotL) {
     lightingInfo result;
 
     // Roughness

+ 3 - 3
src/Shaders/ShadersInclude/pbrLightFunctionsCall.fx

@@ -3,13 +3,13 @@
         vec3 vLightSpecular{X} = vec3(0.0);
     #endif
     #ifdef SPOTLIGHT{X}
-        info = computeSpotLighting(viewDirectionW, normalW, vLightData{X}, vLightDirection{X}, vLightDiffuse{X}.rgb, vLightSpecular{X}, vLightDiffuse{X}.a, roughness, NdotV, vLightRadiuses[{X}], NdotL);
+        info = computeSpotLighting(viewDirectionW, normalW, vLightData{X}, vLightDirection{X}, vLightDiffuse{X}.rgb, vLightSpecular{X}, vLightDiffuse{X}.a, roughness, NdotV, NdotL);
     #endif
     #ifdef HEMILIGHT{X}
-        info = computeHemisphericLighting(viewDirectionW, normalW, vLightData{X}, vLightDiffuse{X}.rgb, vLightSpecular{X}, vLightGround{X}, roughness, NdotV, vLightRadiuses[{X}], NdotL);
+        info = computeHemisphericLighting(viewDirectionW, normalW, vLightData{X}, vLightDiffuse{X}.rgb, vLightSpecular{X}, vLightGround{X}, roughness, NdotV, NdotL);
     #endif
     #if defined(POINTLIGHT{X}) || defined(DIRLIGHT{X})
-        info = computeLighting(viewDirectionW, normalW, vLightData{X}, vLightDiffuse{X}.rgb, vLightSpecular{X}, vLightDiffuse{X}.a, roughness, NdotV, vLightRadiuses[{X}], NdotL);
+        info = computeLighting(viewDirectionW, normalW, vLightData{X}, vLightDiffuse{X}.rgb, vLightSpecular{X}, vLightDiffuse{X}.a, roughness, NdotV, NdotL);
     #endif
     
     #ifdef SHADOW{X}

+ 14 - 3
src/Shaders/legacypbr.fragment.fx

@@ -8,7 +8,6 @@ uniform vec3 vEyePosition;
 uniform vec3 vAmbientColor;
 uniform vec4 vAlbedoColor;
 uniform vec3 vReflectionColor;
-uniform vec4 vLightRadiuses;
 
 // CUSTOM CONTROLS
 uniform vec4 vLightingIntensity;
@@ -43,7 +42,7 @@ varying vec4 vColor;
 #endif
 
 // Lights
-#include<lightFragmentDeclaration>[0..3]
+#include<lightFragmentDeclaration>[0..maxSimultaneousLights]
 
 // Samplers
 #ifdef ALBEDO
@@ -84,6 +83,12 @@ uniform sampler2D reflectivitySampler;
 
 #include<clipPlaneFragmentDeclaration>
 
+#ifdef CAMERACOLORGRADING
+    uniform sampler2D cameraColorGrading2DSampler;
+    uniform vec4 vCameraColorGradingInfos;
+    uniform vec4 vCameraColorGradingScaleOffset;
+#endif
+
 // PBR
 #include<pbrFunctions>
 #include<harmonicsFunctions>
@@ -200,7 +205,7 @@ void main(void) {
     float NdotL = -1.;
     lightingInfo info;
 
-#include<pbrLightFunctionsCall>[0..3]
+#include<pbrLightFunctionsCall>[0..maxSimultaneousLights]
 
 #ifdef SPECULARTERM
     lightSpecularContribution *= vLightingIntensity.w;
@@ -316,5 +321,11 @@ vec3 surfaceEmissiveColor = vEmissiveColor;
     finalColor = contrasts(finalColor);
 #endif
 
+    finalColor.rgb = clamp(finalColor.rgb, 0., 1.);
+    
+#ifdef CAMERACOLORGRADING
+    finalColor = colorGrades(finalColor, cameraColorGrading2DSampler, vCameraColorGradingInfos, vCameraColorGradingScaleOffset);
+#endif
+
     gl_FragColor = finalColor;
 }

+ 33 - 22
src/Shaders/pbr.fragment.fx

@@ -16,7 +16,6 @@ uniform vec3 vEyePosition;
 uniform vec3 vAmbientColor;
 uniform vec3 vReflectionColor;
 uniform vec4 vAlbedoColor;
-uniform vec4 vLightRadiuses;
 
 // CUSTOM CONTROLS
 uniform vec4 vLightingIntensity;
@@ -55,7 +54,7 @@ varying vec4 vColor;
 #endif
 
 // Lights
-#include<lightFragmentDeclaration>[0..3]
+#include<lightFragmentDeclaration>[0..maxSimultaneousLights]
 
 // Samplers
 #ifdef ALBEDO
@@ -125,30 +124,36 @@ uniform vec4 emissiveRightColor;
 
 // Reflection
 #ifdef REFLECTION
-uniform vec2 vReflectionInfos;
+    uniform vec2 vReflectionInfos;
 
-#ifdef REFLECTIONMAP_3D
-uniform samplerCube reflectionCubeSampler;
-#else
-uniform sampler2D reflection2DSampler;
-#endif
-
-#ifdef REFLECTIONMAP_SKYBOX
-varying vec3 vPositionUVW;
-#else
-    #ifdef REFLECTIONMAP_EQUIRECTANGULAR_FIXED
-    varying vec3 vDirectionW;
+    #ifdef REFLECTIONMAP_3D
+        uniform samplerCube reflectionCubeSampler;
+    #else
+        uniform sampler2D reflection2DSampler;
     #endif
 
-    #if defined(REFLECTIONMAP_PLANAR) || defined(REFLECTIONMAP_CUBIC) || defined(REFLECTIONMAP_PROJECTION)
-    uniform mat4 reflectionMatrix;
+    #ifdef REFLECTIONMAP_SKYBOX
+        varying vec3 vPositionUVW;
+    #else
+        #ifdef REFLECTIONMAP_EQUIRECTANGULAR_FIXED
+            varying vec3 vDirectionW;
+        #endif
+
+        #if defined(REFLECTIONMAP_PLANAR) || defined(REFLECTIONMAP_CUBIC) || defined(REFLECTIONMAP_PROJECTION)
+            uniform mat4 reflectionMatrix;
+        #endif
     #endif
-#endif
 
-#include<reflectionFunction>
+    #include<reflectionFunction>
 
 #endif
 
+#ifdef CAMERACOLORGRADING
+    uniform sampler2D cameraColorGrading2DSampler;
+    uniform vec4 vCameraColorGradingInfos;
+    uniform vec4 vCameraColorGradingScaleOffset;
+#endif
+
 // PBR
 #include<pbrShadowFunctions>
 #include<pbrFunctions>
@@ -277,7 +282,7 @@ void main(void) {
     float NdotL = -1.;
     lightingInfo info;
 
-#include<pbrLightFunctionsCall>[0..3]
+#include<pbrLightFunctionsCall>[0..maxSimultaneousLights]
 
 #ifdef SPECULARTERM
     lightSpecularContribution *= vLightingIntensity.w;
@@ -584,9 +589,18 @@ vec3 surfaceEmissiveColor = vEmissiveColor;
 
     finalColor.rgb = toGammaSpace(finalColor.rgb);
 
+#include<logDepthFragment>
+#include<fogFragment>(color, finalColor)
+
 #ifdef CAMERACONTRAST
     finalColor = contrasts(finalColor);
 #endif
+    
+    finalColor.rgb = clamp(finalColor.rgb, 0., 1.);
+    
+#ifdef CAMERACOLORGRADING
+    finalColor = colorGrades(finalColor, cameraColorGrading2DSampler, vCameraColorGradingInfos, vCameraColorGradingScaleOffset);
+#endif
 
     // Normal Display.
     // gl_FragColor = vec4(normalW * 0.5 + 0.5, 1.0);
@@ -616,8 +630,5 @@ vec3 surfaceEmissiveColor = vEmissiveColor;
     //vec2 test = vEmissiveUV * 0.5 + 0.5;
     //gl_FragColor = vec4(test.x, test.y, 1.0, 1.0);
 
-#include<logDepthFragment>
-#include<fogFragment>(color, finalColor)
-
     gl_FragColor = finalColor;
 }

+ 7 - 0
src/babylon.engine.ts

@@ -1535,6 +1535,10 @@
             var internalFormat = this._getInternalFormat(format);
             this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
             this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, invertY === undefined ? 1 : (invertY ? 1 : 0));
+            
+            if (texture._width % 4 !== 0) {
+                this._gl.pixelStorei(this._gl.UNPACK_ALIGNMENT, 1);
+            } 
 
             if (compression) {
                 this._gl.compressedTexImage2D(this._gl.TEXTURE_2D, 0, this.getCaps().s3tc[compression], texture._width, texture._height, 0, data);
@@ -1915,6 +1919,9 @@
         public updateTextureSize(texture: WebGLTexture, width: number, height: number) {
             texture._width = width;
             texture._height = height;
+            texture._size = width * height;
+            texture._baseWidth = width;
+            texture._baseHeight = height;
         }
 
         public createRawCubeTexture(url: string, scene: Scene, size: number, format: number, type: number, noMipmap: boolean,