Search code examples
three.jsglslshaderreact-three-fiber

Why does EffectsComposer unintentionally make a custom shaderMaterial brighter by default?


In Three.js, it seems that if you add an empty EffectsComposer, your shaderMaterials unintentionally get brighter and look blown out.

https://codesandbox.io/s/effects-composer-shader-conflict-mysyn


Solution

  • It seems adding a composer also enables color space conversion. To fix the issues, you have to do a couple of things:

    • Set the encoding property of your texture to sRGBEncoding
    • Use the three.js function mapTexelToLinear() to convert texels to linear color space
    • For consistency, add the #include <encodings_fragment> shader chunk to your code (not necessary to fix the issue but important if you use no post processing but change the output encoding of the renderer).
    • Assign the texture to ShaderMaterial.map so the renderer can configure mapTexelToLinear() with the correct code.
    import './styles.css';
    import { TextureLoader, ShaderMaterial, sRGBEncoding } from 'three';
    import { useMemo } from 'react';
    import { EffectComposer } from '@react-three/postprocessing';
    
    export default function App() {
      const texture = new TextureLoader().load('crate.gif');
      texture.encoding = sRGBEncoding;
    
      const shaderMaterial = useMemo(
        () =>
          new ShaderMaterial({
            fragmentShader: `
            uniform sampler2D u_Txt1;
            varying vec2 vUv;
            void main() {
              gl_FragColor = mapTexelToLinear( texture2D(u_Txt1, vUv) );
              #include <encodings_fragment>
            }`,
            uniforms: {
              u_Txt1: { value: texture }
            },
            vertexShader: `#include <common>
            varying vec2 vUv;
            void main () {
              vUv = uv;
              gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
            }`
          }),
        [texture]
      );
      shaderMaterial.map = texture;
    
      return (
        <>
          <mesh name="screen" scale={[10, 10, 1]} material={shaderMaterial}>
            {/* <meshBasicMaterial map={texture} color={0xffffff} /> */}
            <planeBufferGeometry args={[1, 1]} />
          </mesh>
          <EffectComposer />
        </>
      );
    }