RGBD Lightmap banding

Hi there @sebavan
Continuing my lightmap generation journey starting here
I made two lightmaps from two scenes with the Lightmapper addon for Blender.
Two rooms with similar lighting, although one of them has some warmer lights.
Each maps generated in .HDR format and saved in 2k resolution (original is 4k).
The “Dormitorio” one is coming great when opened in Photoshop (I don’t care Photoshop interprets “D” channel as transparency) as you can see here (expected blown out parts):

This is how it looks in this playground:

But the other lightmap, comes with ugly banding and, or inverted colors (warm lights appears blue):

And this is how it looks in this playground:

I’m using the code posted in the other thread to generate the RGBD png for both lightmaps, so something is wrong but I don’t know what.

For the records, this is the code:

var createEngine = function () {
    return new BABYLON.Engine(canvas, true, { stencil: true, premultipliedAlpha: false });
};

BABYLON.ThinEngine._TextureLoaders.push({
    supportCascades: false,
    canLoad: (extension) => {
        return BABYLON.StringTools.EndsWith(extension, ".hdr");
    },
    loadData: (data, texture, callback) => {
        var bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);

        var hdrInfo = BABYLON.HDRTools.RGBE_ReadHeader(bytes);
        var pixels = BABYLON.HDRTools.RGBE_ReadPixels(bytes, hdrInfo);

        texture.type = BABYLON.Constants.TEXTURETYPE_FLOAT;
        texture.format = BABYLON.Constants.TEXTUREFORMAT_RGB;
        // Mip maps can not be generated on FLOAT RGB textures.
        texture.generateMipMaps = false;
        
        callback(hdrInfo.width, hdrInfo.height, texture.generateMipMaps, false, () => {
            texture.getEngine()._uploadDataToTextureDirectly(texture, pixels, 0, 0, undefined, true);
        });
    }
});

var createScene = function () {

    var url = "https://dl.dropbox.com/s/rt3dk5k8mhqlwv8/Dormitorio_v2_2k.hdr";

    var scene = new BABYLON.Scene(engine);
    var texture = new BABYLON.Texture(url, scene, false, false, BABYLON.Texture.NEAREST_SAMPLINGMODE);
    texture.wrapU = 0;
    texture.wrapV = 0;

    texture.onLoadObservable.add(() => {
            let rgbdPostProcess = new BABYLON.PostProcess("rgbdEncodeB", "rgbdEncode", null, null, 1, null, BABYLON.Constants.TEXTURE_NEAREST_SAMPLINGMODE, engine, false, undefined, BABYLON.Constants.TEXTURETYPE_UNSIGNED_INT, undefined, null, false);
            rgbdPostProcess.getEffect().executeWhenCompiled(() => {
                rgbdPostProcess.onApply = (effect) => {
                    effect.setTexture("textureSampler", texture);
                };

                // As the process needs to happen on the main canvas, keep track of the current size
                let currentW = engine.getRenderWidth();
                let currentH = engine.getRenderHeight();

                // Set the desired size for the texture
                engine.setSize(texture.getSize().width, texture.getSize().height);
                scene.postProcessManager.directRender([rgbdPostProcess], null);

                // Reading datas from WebGL
                BABYLON.Tools.ToBlob(canvas, (blob) => {
                    BABYLON.Tools.Download(blob, "Dormitorio_lightmap_RGBD.png");

                    // var reader = new FileReader();
                    // reader.onloadend = function() {
                    //     console.log(reader.result);
                    // }
                    // reader.readAsDataURL(blob);
                });

                // Reapply the previous canvas size
                engine.setSize(currentW, currentH);
            });
    });

    scene.createDefaultCamera();

    return scene;
};

Any insights?

Thanks in advance for your help!

1 Like

Instead of:

BABYLON.ThinEngine._TextureLoaders.push({

you should use:

BABYLON.ThinEngine._TextureLoaders.splice(0, 0, {

because else your new loader won’t be used, as another .hdr loader does exist in 5.0 and will be found in BABYLON.ThinEngine._TextureLoaders before your one.

Also, in your PG, you should indicate that the texture is RGBD (myLightmapTexture.isRGBD = true;).

I’m sorry @Evgeni_Popov but regenerating the PNG with your code leads to the same banding effect:

I guess there is something wrong in the source HDR file that prevents it from creating correct result

Do you have the original .hdr file for us to look at?

I don’t really understand the large black areas you have in the .png file…

Yep, here you are: https://dl.dropbox.com/s/rt3dk5k8mhqlwv8/Dormitorio_v2_2k.hdr

For comparison purposes, you can check the source HDR file for the other lightmap that generates correctly in RGBD format here: https://dl.dropbox.com/s/0kdvl62e9awy5d3/Salon_lightmap.hdr

Here’s what I end up with when encoding the .hdr file to .png using the code from @sebavan (https://playground.babylonjs.com/#95Y4D7#10) with the splice fix:

With this picture, the rendering is ok, assuming you set myLightmapTexture.isRGBD = true. Applying tone mapping is a must too.

Note that you have some flickering in your scene because of z-fighting: there are some geometries that are duplicated, it seems.

[EDIT] For a basis of comparison, here is the scene using the .hdr file, with tone mapping enabled:

Yep, the flickering comes from the fact that there are several versions of the same room in the same place, we are showing each one via code, everything is fine then.

So, what is the complete correct code I must use to convert the images to HDR then and why I didn’t have to do anything with the other room lightmap?

I’m not a coding guy, so I really don’t know or understand what I’m doing LOL

Use the link I have given above to convert a .hdr to .png file (https://playground.babylonjs.com/#95Y4D7#10): that’s what I used to do the conversion myself.

I don’t know what you did to convert the .hdr to .png files, so I can’t tell why it worked for one file and not for the other.

Thank you very much. I’ll send feedback when tested.
Cheers.

It really would be great if RGBD png files coulb be exported from softwares like Photoshop or Nuke (Nuke is great for channels manipulations and saving, so I guess it could create such files without problem)

1 Like

In the end, you might have struggled with it a little :sweat_smile: :wink: but then, know that (in my opinion) the result looks just beautiful. :heart_eyes: GJ,

Thank you @mawa for your kind words.

OK, I remade the lightmap with better quality and put the " myLightmapTexture.isRGBD = true" line.

So, with a tonemapping set to “Standard” it looks like this:

And here with “ACES”:

It looks darker with ACES, I like Standard more.

It’s a pitty we don’t have access to any other tonemapping tools, like softclip or toe controls though.

Hi there,
Bad news here.
I needed to redo the lightmap completely and now, when I convert the HDR file into RGBD I get banding again!
What can be the problem?

Here you have the original HDR file.

Here is the code I’m using to generate the RGBD file:

var createEngine = function () {
	return new BABYLON.Engine(canvas, true, { stencil: true, premultipliedAlpha: false });
};

BABYLON.ThinEngine._TextureLoaders.splice(0, 0, {
	supportCascades: false,
	canLoad: (extension) => {
    	return BABYLON.StringTools.EndsWith(extension, ".hdr");
	},
	loadData: (data, texture, callback) => {
    	var bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);

    	var hdrInfo = BABYLON.HDRTools.RGBE_ReadHeader(bytes);
    	var pixels = BABYLON.HDRTools.RGBE_ReadPixels(bytes, hdrInfo);

    	texture.type = BABYLON.Constants.TEXTURETYPE_FLOAT;
    	texture.format = BABYLON.Constants.TEXTUREFORMAT_RGB;
    	// Mip maps can not be generated on FLOAT RGB textures.
    	texture.generateMipMaps = false;
   	 
    	callback(hdrInfo.width, hdrInfo.height, texture.generateMipMaps, false, () => {
        	texture.getEngine()._uploadDataToTextureDirectly(texture, pixels, 0, 0, undefined, true);
    	});
	}
});

var createScene = function () {

	var url = "https://dl.dropbox.com/s/1vf6mry9rbgnecs/Salon_lightmap_v2_2k.hdr";

	var scene = new BABYLON.Scene(engine);
	var texture = new BABYLON.Texture(url, scene, false, false, BABYLON.Texture.NEAREST_SAMPLINGMODE);
	texture.wrapU = 0;
	texture.wrapV = 0;

	texture.onLoadObservable.add(() => {
        	let rgbdPostProcess = new BABYLON.PostProcess("rgbdEncodeB", "rgbdEncode", null, null, 1, null, BABYLON.Constants.TEXTURE_NEAREST_SAMPLINGMODE, engine, false, undefined, BABYLON.Constants.TEXTURETYPE_UNSIGNED_INT, undefined, null, false);
        	rgbdPostProcess.getEffect().executeWhenCompiled(() => {
            	rgbdPostProcess.onApply = (effect) => {
                	effect.setTexture("textureSampler", texture);
            	};

            	// As the process needs to happen on the main canvas, keep track of the current size
            	let currentW = engine.getRenderWidth();
            	let currentH = engine.getRenderHeight();

            	// Set the desired size for the texture
            	engine.setSize(texture.getSize().width, texture.getSize().height);
            	scene.postProcessManager.directRender([rgbdPostProcess], null);

            	// Reading datas from WebGL
            	BABYLON.Tools.ToBlob(canvas, (blob) => {
                	BABYLON.Tools.Download(blob, "Salon_lightmap_RGBD.png");

                	// var reader = new FileReader();
                	// reader.onloadend = function() {
                	// 	console.log(reader.result);
                	// }
                	// reader.readAsDataURL(blob);
            	});

            	// Reapply the previous canvas size
            	engine.setSize(currentW, currentH);
        	});
	});

	scene.createDefaultCamera();

	return scene;
};

What is the .png file which is generated?

It would be simpler to find the problem if you also provide the PG with the final lightmapped scene.

This the generated PNG. As you can see, the banding is horrendous.

And here is the code I’m using for the playground.

var createScene = function () {
	//
	var scene = new BABYLON.Scene(engine);
	//
	var camera = new BABYLON.UniversalCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
	camera.setTarget(BABYLON.Vector3.Zero());
	camera.attachControl(canvas, true);
	//
    var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
	light.intensity = 0.7;
	light.groundColor = new BABYLON.Color3(1, 1, 1);
  	 
	var hdrEnvironment = BABYLON.CubeTexture.CreateFromPrefilteredData("textures/environment.dds", scene);
	scene.createDefaultSkybox(hdrEnvironment, true);

	//import model into scene
	BABYLON.SceneLoader.ImportMeshAsync("", "https://dl.dropbox.com/s/fejhvgach8pdfcb/SalonCocina.glb")
    	.then(function (result) {
        	//
        	scene.createDefaultCamera(true, true, true);
        	scene.activeCamera.alpha += Math.PI;
        	scene.activeCamera.radius=10;

        	//
        	var myLightmapTexture = new BABYLON.Texture("https://dl.dropbox.com/s/pm2w6qgm2fy5vhy/Salon_lightmap_RGBD_Banding.png", scene);
        	myLightmapTexture.coordinatesIndex = 1;
    	//A continuación tenemos que hacer un espejo del lightmap en V, paso innecesario cuando la escena es glTF
        	//myLightmapTexture.vScale = -1;
        	myLightmapTexture.isRGBD = true
        	myLightmapTexture.level = 1.0;
        	//
        	scene.materials.forEach(material=>{
            	material.lightmapTexture=myLightmapTexture;
            	material.useLightmapAsShadowmap=true;
        	});
    	})
    	.catch(function (error) {
   		 alert(error);
   	 });

	return scene;
};

It’s not the png I get when using https://playground.babylonjs.com/#95Y4D7#13 :

When used on your scene:

(I had to set the level to 0.2, 1.0 was too overexposed)/

1 Like

Mmmm, I’ve repeteaded the same code as yesterday and now the PNG, as you say, is correct. Surely I did something wrong, but I can’t find what is it.
Thanks for your support anyway.
Yes, it is overexposed, although I want to use tonemapping to compensate.
Cheers.

This RGBD lightmap workflow have to be saved in the doc (I unfortunately doesn’t have time to try a first draft.

Your scene will probably looks very nice!