Skip to content

Best practices for color space management #3509

@donmccurdy

Description

@donmccurdy

A-Frame 0.8.0 brings in some upstream fixes to THREE.GLTFLoader that are technically correct, but potentially inconsistent with existing scenes and examples. Pre-0.8.0, gltf-model assumed all textures were in linear color space — material.color and material.src still make this same assumption today. That's incorrect — images are usually sRGB, and the glTF spec explicitly requires color and emissive textures to use sRGB.

Following best practice:

  1. Textures are converted sRGB -> linear at the beginning of the fragment shader if the image encoding is set to sRGB.
  2. Renderer calculations are done in linear space.
  3. Renderer output is converted linear -> sRGB for the screen if renderer.gammaOutput=true.

By default in A-Frame and three.js, renderer.gammaOutput is false and step (3) never happens. As a result, glTF models look darker than they should. Textures using material.src look mostly right, because we don't set the encoding to sRGB, even though the images usually are sRGB. So the colors are being treated as linear in lighting calculations, which practically isn't very noticeable.

User danilo in Slack created this PDF, which illustrates the problem nicely:

https://drive.google.com/file/d/1BRdCU9nDzUHOfDYYXdbAV1cMW60Md6uv/view

I don't have any quick answers on what we should do here... in short, I think the three.js defaults assume some familiarity with color spaces, and ideally we would abstract this away from our users, or at least provide some best practices as a reference for users who are going to care about matching a color palette precisely.

If we were to change our defaults, here's a straw proposal:

  • By default, set renderer="gammaOutput: true".
  • By default, set src and emissiveMap both to sRGB encoding. Perhaps provide srcColorSpace and emissiveColorSpace to override this, but I think those properties would be an escape hatch to restore legacy behavior and not otherwise useful.

The one thing this doesn't fix is material.color values, which are assumed to be already linear in three.js, and don't have an encoding setting. So either (1) we could provide guidance about converting an sRGB hex code to linear, or (2) we would automatically do this in the material component.

I'm also not sure what this all means for vertex colors...

My vote for first step would be to add srcColorSpace and emissiveColorSpace in a PR, verify that it improves outcomes, and then enable them without changing any defaults. It would be nice to have feedback from someone more familiar with color spaces and/or rendering pipelines. 🙂

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions