WebGLRenderer outputEncoding

Hey, Im having a scene in a angular component which is being created and destroyed on button click. When the component is loaded, threejs is used to build up a scene and dynamically create a object. On destroy the scene is disposed.

Now when activating the component the first time, everything is more dark and strong color and shiny.
When killing the scene and activating it again, everything looks more foggy and white.

I traced the problem down to these two lines:

this._renderer.gammaFactor = 2.2
this._renderer.outputEncoding = THREE.GammaEncoding

When deleting them, everything looks as Im activating the component the first time, dark and strong color, everytime.

That means that the encoding affect my scene only on second activation. But why? Is it because the models are cached and loaded faster, I had troubles sometimes with onLoad functions.

I tried to update the materials manually with mat.needsUpdate = true. And the same for all the textures tex.encoding = THREE.sRGBEncoding; tex.needsUpdate = true; but nothing works.

I just need some hints. Its kinda hard to show code.

// Renderer

        this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
        this.renderer.outputEncoding = THREE.GammaEncoding
        this.renderer.gammaFactor = 2.2 
        this.renderer.setPixelRatio(window.devicePixelRatio)
        this.renderer.setSize(window.innerWidth, window.innerHeight)
        this.renderer.shadowMap.enabled = true
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap

// Material creation and texture loading example 
       `static getMaterial(color: string, texturePath: string, texture?: THREE.Texture, options?: Options) : THREE.MeshStand`ardMaterial {

        if(options == undefined)

            options = {}

        let mat = new THREE.MeshStandardMaterial({
            color: color,
            transparent: options.alpha ? true : false,
            side: options.alpha ? THREE.DoubleSide : THREE.FrontSide,
            envMap: Color._reflectionMap,
            envMapIntensity: 1,
            metalness: .1,
            roughness: .43,
        })

        if(texture != null) {

            texture.anisotropy = Color.anisotropy
            mat.map = texture

                       
            if(options.bumpMap) {

                mat.bumpMap = texture
                mat.bumpScale = .001
            }

            mat.needsUpdate = true
        }

        if(texturePath != null) {
             // TextureManager is basicly a TextureLoader and a array 
             // of key(texture) value(model.clone()) pairs. Its cloned if it already exists
             // or loaded if not
            TextureManager.instance.load(Globals.PATH + texturePath, (texture)=> {

                texture.wrapT = THREE.RepeatWrapping;
                texture.wrapS = THREE.RepeatWrapping;
                texture.repeat.set(1, 1);
                texture.encoding = THREE.sRGBEncoding
                texture.anisotropy = Color.anisotropy
                texture.needsUpdate = true
                mat.map = texture

                if(options.bumpMap) {
                    
                    mat.bumpMap = texture
                    mat.bumpScale = .001

                }

                mat.needsUpdate = true
            })

        }

        return mat
    }

If you are using textures with sRGBEncoding encoding, you achieve the best true-color result by using this setting:

this._renderer.outputEncoding = THREE.sRGBEncoding;

Otherwise images in Photoshop will always look different compared to your application.

It’s important to understand if this issue is related to Angular. Can you reproduce the issue with a simple three.js live example? I mean without Angular?

Hey, i can confirm, it is not angular related. I just found the problem by completely disabling my post processing SSAA and SAO. This is the code Im using.

    this.composer = new EffectComposer(this._renderer)
    this.composer.renderer.outputEncoding = THREE.sRGBEncoding
    this.composer.setSize( this.w, this.h )

    this.ssaaRenderPass = new SSAARenderPass(this._scene, this.camera, 0x000000, 0)
    this.ssaaRenderPass.setSize(this.w, this.h)
    this.ssaaRenderPass.unbiased = true
    this.composer.addPass(this.ssaaRenderPass)

    this.SAO = new SAOPass(this._scene, this.camera, true, true)
    this.SAO.resolution.set(2048, 2048)
    this.composer.addPass(this.SAO)

Im only rendering the SSAA/SAO effect once on mouseUp.
composer.render()
All other events are rendered normally.
renderer.render(scene, camera)
Could it be possible that the composer uses the default outputEncoding? Or is it possible to set encoding on the SSAA-/SAO-Pass?

Its weird because though Im alternating between the renderer and the composer, it stays dark as if using LinearEncoding.

When using post-processing or in general when rendering to a render target, WebGLRenderer.outputEncoding is not evaluated. All what matters is the encoding setting of the respective render target’s texture.

If you perform post-processing and want an sRGB encoded output, it’s recommended to use a gamma correction pass at the end of your pass chain like in this example:

3 Likes

Nice thank you! Post processing has now the desired colors. But there is still a weird thing going on when rendering without post processing. See here

  1. Visit page or refresh
  2. Create shelf
  3. Click 3D
    When orbiting around, it is dark (looks like LinearEncoding) (rendering without processing)
    On MouseUp it has the desired look!! (composer.render())
  4. Go back to 2D
  5. Click 3D again
    When orbiting around, it has the desired look. (rendering without processing)
    On MouseUp, it is way too bright. (Looks like sRGBEncoding & GammaCorrectionShader are added up) (composer.render())

I dont get it really. Its always happening the same thing when clicking the 3D button

Update:
This is my render function. If I set the encoding before rendering, it looks correct all the time, but I have big lags when switching encoding…

public render(force?: boolean) {
    // console.log('rendering...')

    if(Globals.device == 'desktop' && (this.renderSAOOnce === true || force === true)) {
        console.log('SAO ------------')
        this._renderer.outputEncoding = THREE.LinearEncoding
        this.composer.render()
        this.renderSAOOnce = false
    }
    else {
        this._renderer.outputEncoding = THREE.sRGBEncoding
        this._renderer.render(this._scene, this._camera)
    }
}

That happens because all shaders of the scene have to be recompiled when the output encoding is changed.

I have question to your rendering approach in your app: Why don’t you always render with post-processing and just enable the SAOO pass when the scene is static? In this way, you don’t have to change the output encoding of the renderer. Instead you only configure the pass chain depending on the user interaction.

Thats sounds like a good idea.
Your help is always appreciated, @Mugen87 !

1 Like

Solution over here

    // Init post processing

    this.composer = new EffectComposer(this._renderer)

    this.ssaaRenderPass = new SSAARenderPass(this._scene, this.camera, 0x000000, 0)
    this.ssaaRenderPass.setSize(this.w, this.h)
    this.ssaaRenderPass.unbiased = true
    this.composer.addPass(this.ssaaRenderPass)
    this.ssaaRenderPass.enabled = false

    this.renderPass = new RenderPass(this.scene, this.camera)
    this.composer.addPass(this.renderPass)

    this.SAO = new SAOPass(this._scene, this.camera, true, true)
    this.SAO.resolution.set(2048, 2048)
    this.SAO.params.saoBias = .01
    this.SAO.params.saoIntensity = .0025
    this.SAO.params.saoScale = .3
    this.SAO.params.saoKernelRadius = 40
    this.SAO.params.saoBlurRadius = 4
    this.SAO.params.saoMinResolution = 0
    this.composer.addPass(this.SAO)
    this.SAO.enabled = false

    this.gammaCorrection = new ShaderPass(GammaCorrectionShader)
    this.composer.addPass(this.gammaCorrection)

    this.FXAA = new ShaderPass(FXAAShader)
    this.FXAA.material['uniforms']['resolution'].value.x = 1 / (this.w)
    this.FXAA.material['uniforms']['resolution'].value.y = 1 / (this.h)
    this.FXAA.renderToScreen = false
    this.composer.addPass(this.FXAA)


    public render() {

         this.composer.render()
    }


public onMouseDown() {

    this.ssaaRenderPass.enabled = false

    this.SAO.enabled = false

    this.renderPass.enabled = true
}


public onMouseUp() {


    this.ssaaRenderPass.enabled = true

    this.SAO.enabled = true

    this.renderPass.enabled = false

    this.render()
}

public onOrbitChanges() {

     this.render()
}

If you use the latest version of three.js, then this line is not necessary anymore. The last entry in the pass chain is automatically rendered to screen.