Gray texture loaded via EXRloader has red color

This is what my texture should look like.

But at three it is completely red.

Other full color exr images are displayed well

> Small reproduction

It seems to be a single-channel texture containing only the red channel, marked as texture.format = RedFormat. THREE.MeshBasicMaterial expects a three-channel RGB texture for its .map texture slot. Some alternatives:

  • (a) duplicate the R channel into the GB channels (some memory cost)
  • (b) use a custom THREE.ShaderMaterial that writes the R channel to RGB output
  • (c) use a smaller/simpler texture format like PNG or JPG if you don’t need floating-point precision
1 Like

Thanks for the reply @donmccurdy!

Could you please elaborate on option A?

I used option B, but it makes the texture look more contrasty than it actually is.

Updated codesandbox

const renderToCanvas = (texture) => {
  const { width, height } = texture.image;
  const renderer = new WebGLRenderer();

  renderer.setSize(width, height, false);

  // const fsQuadMaterial = new MeshBasicMaterial({
  //   map: texture,
  // });

  const fsQuadMaterial = new ShaderMaterial({
    uniforms: {
      uTexture: { value: texture },
    },
    vertexShader: `
      varying vec2 vUv;
      void main() {
        vUv = uv;
        gl_Position = vec4(position.xy, 0.0, 1.0);
      }
    `,
    fragmentShader: `
      precision highp float;
      uniform sampler2D uTexture;
      varying vec2 vUv;

      void main() {
        float r = texture2D(uTexture, vUv).r;
        gl_FragColor = vec4(r, r, r, 1.0); 
      }
    `,
  });

  const fsQuad = new FullScreenQuad(fsQuadMaterial);
  fsQuad.render(renderer);

  return { canvas: renderer.domElement };
};

three.js

ref

Found a solution for incorrect gamma


void main() {
        float r = texture2D(uTexture, vUv).r;
        r = pow(1.0 / 2.2);
        gl_FragColor = vec4(r, r, r, 1.0); 
}

Equivalent to the inline gamma correction would be to instead assign the color space of the texture. Not sure which is expected for this texture:

// (A)
texture.colorSpace = THREE.SRGBColorspace;

// (B)
texture.colorSpace = THREE.LinearSRGBColorSpace;

These values ​​don’t seem to work

Ah I see the warning in the console:

THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.

So WebGL can’t do that conversion for you with this texture, your fix in the shader is probably best instead if you’re using a single-channel EXR texture like this.

1 Like

Thanks for the support and direction! It means a lot to me.

P.S. I found a way to make the shader more “generic”. Maybe it will be useful for others.

https://codesandbox.io/p/sandbox/black-cloud-forked-ykm25r?file=%2Fsrc%2FApp.js%3A72%2C62

import {
  WebGLRenderer,
  ShaderMaterial,
  RGBFormat,
  RedFormat,
  RGBAFormat,
  SRGBColorSpace,
  AlphaFormat,
  DepthFormat,
  DepthStencilFormat,
  RGFormat,
  LuminanceFormat,
  LuminanceAlphaFormat,
  RGBAIntegerFormat,
  Uniform,
} from "three";
import { EXRLoader, FullScreenQuad } from "three-stdlib";

function getShaderMaterial(texture) {
  const format = texture.format;

  let colorExpr = "tex";
  switch (format) {
    case RedFormat:
    case AlphaFormat:
    case LuminanceFormat:
    case DepthFormat:
    case DepthStencilFormat:
      colorExpr = "vec4(tex.r, tex.r, tex.r, 1.0)";
      break;
    case RGFormat:
      colorExpr = "vec4(tex.r, tex.g, 0.0, 1.0)";
      break;
    case LuminanceAlphaFormat:
      colorExpr = "vec4(tex.r, tex.r, tex.r, tex.g)";
      break;
    case RGBAIntegerFormat:
      colorExpr = "vec4(tex.r, tex.g, tex.b, tex.a) / 255.0";
      break;
    case RGBFormat:
    case RGBAFormat:
      colorExpr = "tex";
  }

  return new ShaderMaterial({
    uniforms: {
      uTexture: new Uniform(texture),
    },
    defines: {
      SRGB: texture.colorSpace !== SRGBColorSpace,
    },
    vertexShader: `
      varying vec2 vUv;
      void main() {
        vUv = uv;
        gl_Position = vec4(position.xy, 0.0, 1.0);
      }
    `,
    fragmentShader: `
      precision highp float;
      uniform sampler2D uTexture;
      varying vec2 vUv;

      void main() {

        vec4 tex = texture2D(uTexture, vUv);
        vec4 color = ${colorExpr};
        

        // https://discourse.threejs.org/t/whats-this-about-gammafactor/4264
        // https://github.com/mrdoob/three.js/blob/817a222f2d12baf44a38baf256bc9d4f65d82465/examples/jsm/utils/TextureUtils.js#L18
        #ifdef SRGB
          // https://github.com/mrdoob/three.js/blob/b3cb0cd0d6066f7054a76b90904486e40031c2ce/src/renderers/shaders/ShaderChunk/colorspace_pars_fragment.glsl.js#L11
          gl_FragColor = sRGBTransferOETF(color);
        #else
          gl_FragColor = color;
        #endif
      }
    `,
  });
}

const renderToCanvas = (texture) => {
  const { width, height } = texture.image;
  const renderer = new WebGLRenderer();
  renderer.setSize(width, height, false);

  const fsQuadMaterial = getShaderMaterial(texture);

  const fsQuad = new FullScreenQuad(fsQuadMaterial);
  fsQuad.render(renderer);

  return { canvas: renderer.domElement };
};