three.js: Incorrect brightness when gl_FragColor is semi-transparent.

Right now in the ThreeJS GLSL shaders, if there is a bright specular highlight on a transparent material, the highlight is not rendered correctly.

For example let say that at the end of the shader, the gl_FragColor = ( 8, 8, 8, 0.1 ). This should result in a bright highlight of let’s say (0.8,0.8,0.8) being written to the frame buffer. But in fact, the values of gl_FragColor are clamped into the range of [0,1] before the value is passed to the gl.BlendFunc, thus even if you write gl_FragColor = (8,8,8, 0.1), it is actually equivalent to writing gl_FragColor = ( 1.0, 1.0. 1.0, 0.1 ), because the values will be clamped, which will result in a value of (0.1, 0.1, 0.1) being written to the frame buffer, much lower than the correct (0.8,0.8, 0.8) value.

I found a way to get around this artificial clamping of brightness on semi-transparent objects. The trick is to pre-multiply. Thus one would do this:

gl_FragColor = vec4( gl_FragColor.rgb * gl_FragColor.a, gl_FragColor.a )

And then change the blending modes for the material as follows:

    material.blending = THREE.CustomBlending;
    material.blendSrc = THREE.OneFactor;    // output of shader must be premultiplied
    material.blendDst = THREE.OneMinusSrcAlphaFactor;
    material.blendEquation = THREE.AddEquation;

The above settings do the standard blending but assumes a pre-multiplied gl_FragColor as input and it retained the whole range of acceptable intensities.

Here is a comparison of the improved results possible by using pre-multipled alpha in the gl_FragColor for high intensity semi-transparent materials – this change is already live on Clara.io:

physicallybasedsemitransparentobject

I am unsure if there are side effects to using pre-multiplied alpha. I suspect because the gl_FragColor.a is unchanged, there is few side effects.

About this issue

  • Original URL
  • State: closed
  • Created 10 years ago
  • Comments: 29 (21 by maintainers)

Most upvoted comments

I don’t know why I’m responding and I probably don’t know all the issues but …

It does seem like it would be nice if there was an option to have three.js render premultiplied alpha values given that’s what the browser wants (in general) and matches the browser’s use of .png images and the 2d canvas. The HTML spec assumes premultiplied alpha. WebGL added an exception (premultipledAlpha: false) because we knew the majority of older OpenGL apps (games) don’t use premultiplied alpha.

In other words, if I want to render something over the page, like this doing the correct thing should be easier?

That seems pretty easy. Just like bhouston suggested add a material.premultipliedAlpha option that if true then when shaders are generated appends

gl_FragColor = vec4(gl_FragColor.rgb * gl_FragColor.a, gl_FragColor.a); 

That seems a lot nicer than requiring users to write all custom shaders if they want to use premultiplied alpha.