Use the Image Filter BJS Control to "colorize" an image that will become a PBR metallic texture

It’s been several months since I posted a question, and I hope that the community is as amazing as it was in the past.

I want to take an image that will become a PBR material metallic texture and programmatically create five versions of it, each version progressively more hue slanted towards red (and therefore more metallic). It seems to me that the new BJS Controls, and Image Filter specifically is perfect for this. We’ve actually also been looking for a fast/simple abstraction layer to do this kind of 2D hue/saturation/luminosity image processing in the WebGL space for performance reasons, so it looks like BJS Controls could be very useful for us, and we’re excited about them!

While I can do some basic BJS/PBR stuff, I have NO 3D or Shader experience. I’ve been trying to follow the instructions, which are really quite nice, for a few hours and am not getting there. I’m not clear on what npm dependencies I would need for this context, and whether I need a post-process effect. I also have no idea what the Shader code to remap RGB channels would look like. any suggestions, playgrounds, or posts would be much appreciated.

Thank you so much for continuing to make this amazing platform even more incredible! What your tram has accomplished in the last year is just really just tremendous!

Warmest regards,
Preston Williams

Pinging @sebavan

Hello I ll try to send over a quick example a bit later today :slight_smile:

Tremendous, thanks so much!

Here is a simple example reusing babylonjs image processing post process and the controls project:

import { Engine } from "@babylonjs/core/Engines/engine";
import { ImageFilter } from "@babylonjs/controls/dist/src/imageFilter";
import { ImageProcessingPostProcess } from "@babylonjs/core/PostProcesses/imageProcessingPostProcess";

import "@babylonjs/core/Loading/loadingScreen";

import { ColorCurves } from "@babylonjs/core/Materials/colorCurves";

// Find our elements
const mainCanvas = document.getElementById("renderCanvas") as HTMLCanvasElement;

// By default Image Filter only creates a thin engine but here as we want to use
// post processes, we need to use our full engine
const engine = new Engine(mainCanvas);

engine.loadingScreen.loadingUIText = "Loading a Big Gif... Don't leave now :'("

const customFilter = new ImageFilter(engine);

// Create the Image Processing post process
const imageProcessingPostProcess = new ImageProcessingPostProcess("process", 1, undefined, undefined, engine);
imageProcessingPostProcess.fromLinearSpace = false;

// Color curves
const curves = new ColorCurves();
curves.globalHue = 100;
curves.globalSaturation = 50;
curves.globalDensity = 100;

// Setup our ColorGrading effect
imageProcessingPostProcess.imageProcessingConfiguration.colorCurves = curves;
imageProcessingPostProcess.imageProcessingConfiguration.colorCurvesEnabled = true;

customFilter.filter("", imageProcessingPostProcess).then(() => {

The dependencies in package.json are simply:

        "@babylonjs/controls": "^1.0.0-alpha.20",
        "@babylonjs/core": "^4.2.0-alpha.10",

Hope that helps

1 Like

Thank you very much for the perfect response, and for it being so fast! This crazy new reality and resulting work kept me from implementing your solution until now. By following your code, I am finally able to use the image processing filter on a utility canvas that I created for it.

But after applying that filter I want to create a new PBR material rawTexture from the filtered canvas image data. Every way that I try to access either a 2d or webgl context for that utility canvas element returns null for the context. I’m trying to set constants to the context both outside of the customFilter.filter method and within the callback ( customFilter.filter(imageToFilter, imageProcessingPostProcess).then(() => { ). I put some example code below. I also tried looking in the ImageFilter object for the context.

In any case, thank you so much again for your help. You got me so much closer to the solution!

Warmest regards and have a great weekend,

---------------------- CODE THAT I’M TRYING ----------------
customFilter.filter(imageToFilter, imageProcessingPostProcess).then(() => {
const textureUtilCanvas2 = document.getElementById(‘utilCanvas’)
const textureUtilCanvasContext = textureUtilCanvas2.getContext(‘webgl’)
// const textureUtilCanvasContext = customFilter.getContext(‘webgl’)
const textureUtilCanvasData = textureUtilCanvasContext.getImageData(0, 0, 800, 800)
const textureUtilCanvasDataData =
return textureUtilCanvasDataData

You could simply wrap the canvas in an htmlelementTexture and directly use it in the pbr :slight_smile: this would be the simplest way of reusing a canvas as a tetxture.

The htmlelementTexture sounds brilliant. I spent a coupe of hours trying different things before bugging you again. I’m hoping that there is just something simple/dumb that I’m doing here. I’m basically trying this:

customFilter.filter(’/src/images/plaster_1/roughness_ao.jpg’, imageProcessingPostProcess).then(() => {
WebGLMaterial.metallicTexture = new HtmlElementTexture(‘HTMLtexture’, textureUtilCanvas)

My console log confirms that the WebGLMaterial reference in the promise success is indeed a BJS PBR material, and textureUtilCanvas is the HTML canvas that I’m using with the images filter But however I tweak things, I keep getting this error:

htmlElementTexture.js:32 Uncaught (in promise) TypeError: Cannot read property ‘scene’ of undefined
at new HtmlElementTexture (htmlElementTexture.js:32)

Thank you for your patience and help as I keep bugging you. I know that I really am very close!

My best,

You should pass either the scene or engine when creating the texture:

new HtmlElementTexture(‘HTMLtexture’, textureUtilCanvas, { scene: scene });
1 Like

That worked! Thank you so much and have a wonderful weekend!