They’re linear if they’re in glTF format. Beyond that there are no guarantees, I don’t think FBX or OBJ/MTL specify color space for these at all.
It was from here.
I know the glTF spec says the vertex colors should be linear, but at least in the tests I did back then I don’t think these models are spec compliant.
Yeah exactly. You always would have to use two colors in this situation, color management or no.
This was fixed a while back, it was just a conversion issue going from the old JSON format: Examples: Use linear workflow for examples with ROME models. by donmccurdy · Pull Request #15888 · mrdoob/three.js · GitHub
I’ll respond to the Github issue in a bit but some quickfire thoughts, some of which have already been mentioned:
If you create a Color instance manually then you’re responsible for managing it yourself, but if you do new MeshStandardMaterial({color: 0xff00ff}) it’ll be converted automatically to linear space for you.
I don’t feel like it’s so simple. The way users interact with colors in r3f is not the same as how they do in three.js. Auto-converting colors passed in the constructor will result in inconsistencies when the user tries to change that color. These two material instantiations will result in different results which in my opinion is more confusing:
const material = new MeshStandardMaterial( { color: 0xaa0000 } );
const material = new MeshStandardMaterial();
material.color.set( 0xaa0000 );
Well, maybe the renderer is not the right place for it. Maybe THREE.ColorManagement, similar to THREE.Cache?
I’m pretty against these types of globals for all of three.js because it means that pre-built viewers can’t operate in a self contained way. What if I have my viewer on the page that expects color management to be disabled while at the same time a <model-viewer>
element is loaded that expects it to be enabled? I think it’s only reasonable for it to be attached to the renderer in some way if it’s there at all.
Won’t that break backwards compatibility?
I can’t speak to whether backwards compatibility is the blocker for moving anything forward. Three.js breaks things all the time as long as it’s noticeable. My feeling is that as long as there’s a clear path to getting things back to the way they were it shouldn’t be a huge deal. I feel like striving too hard for backwards compatibility here is going to wind up with things being more complicated for users and to maintain.
What about a THREE.Color used for a uniform on a ShaderMaterial?
I don’t think anything should be automatically done to colors on a ShaderMaterial. It’s already the responsibility of the shader author to manage color transformations for textures if they’re writing their own shader.
They’re linear if they’re in glTF format. Beyond that there are no guarantees, I don’t think FBX or OBJ/MTL specify color space for these at all.
My assumption has always been that colors are in sRGB unless otherwise specified which seems like a safe one to make with OBJ, at least, considering how old the format is (~1990?). It looks like FBX is from 2006, though.
I’d forgotten about the textures issue before but history and backwards compatibility concerns aside I think this set of changes would cover everything? Is there a scenario that’s missing?
- WebGLRenderer.outputEncoding is set to sRGBEncoding by default
- Textures are set to use sRGBEncoding by default, which is the typically how images are stored for display (afaik)
- By the time colors get to the render they should be assumed to be a single color space so users, loaders, and other extensions can ensure their colors have been converted to the appropriate one always and not have to worry.
- Right now material colors and vertex colors are assumed to be Linear which is probably best because that means they don’t need to be transformed (and requires no further changes)
- If it’s preferred that material colors be always specified in sRGB then I’d think it would be easier to handle this sRGB → Linear transformation in the shader
- All loaders will have to be adjusted to ensure their output conforms to the above assumption
I’d really like to avoid a mess of flags and conditions that materials, loaders, etc all have to be aware of to make everything work right.
There needs to be a way to switch between the existing “gamma workflow” and the proposed “linear workflow” – I’m not in favor of making three.js require a linear workflow. Not everything is PBR, and sometimes these cases work better with a gamma workflow. So I think this feature needs at least one flag, but something as simple as ColorManagement.enabled
does not need to be messy. Whether it is on by default (i.e. a breaking change) is not particularly important to me at this stage.
Right now material colors and vertex colors are assumed to be Linear which is probably best because that means they don’t need to be transformed (and requires no further changes)
I’m hoping to see an outcome where a user writes material.color.setHex(0x888888)
on an object and background: #888888
on an HTML element and sees the same color on the page, which doesn’t work if we assume Linear. Avoiding an internal sRGB → linear conversion does not seem like the right basis for this decision to me, compared with providing a good workflow.
There needs to be a way to switch between the existing “gamma workflow” and the proposed “linear workflow” – I’m not in favor of making three.js require a linear workflow.
Can you explain the functional difference a user should expect when switching between these two? Is it just that the renderer wouldn’t implicitly convert to a Linear colors in order to perform lighting calculations? Or that a user should be able to supply either sRGB or Linear colors to a material if desired? From the Unity Documentation their workflow flag just affects whether colors and textures are converted to linear color space before rendering which seems like it would be an easy flag to add to the renderer assuming all colors were provided in sRGB originally anyway.
I would just like to avoid global flags and having content loaders or other extensions need to be aware of what that flag is set to in order for them to work.
Avoiding an internal sRGB → linear conversion does not seem like the right basis for this decision to me, compared with providing a good workflow.
I’m fine with colors being assumed to be in sRGB I just think one needs to be picked. From a user-facing perspective it seems like sRGB would be the better choice.
Yeah, that’s the problem with overloaded methods. They always end up making things complicated down the line…
Color.set
calls Color.copy(color)
, Color.setHex( value )
, or Color.setStyle( string )
.
With material.color.set( 0xaa0000 );
you end up calling Color.setHex
in which case, yes, it should be converted.
where a user writes
material.color.setHex(0x888888)
on an object andbackground: #888888
on an HTML element and sees the same color on the page
This is also what I’d like to see, and the user shouldn’t need to have a deep understanding of color spaces to make it happen.
In the case of FBX it’s not specified in the format but you are supposed to put vertex colors in linear space. Of course, this leads to all the confusion you would expect.
I can’t speak to whether backwards compatibility is the blocker for moving anything forward. Three.js breaks things all the time as long as it’s noticeable.
Well, I’m not personally against a breaking change here. Just that I had seen this raised as a blocker on this issue previously.
At the end of the day, if we do end up switching to sRGB as the default for all colors/textures, while not ideal, at least it’ll be an improvement. I think the error you get from incorrectly setting an aoMap to sRGB is probably less than from incorrectly setting a color map to linear.
Normal maps may be more sensitive, however.
Many three.js applications do not need lighting calculations: things like 2D experiences, data visualization, or heavily stylized rendering. In these cases there’s little benefit to dealing with the extra conversions, you just want to provide colors and have them painted as-is on screen. Presumably some 3D applications still opt for a gamma workflow too, but I suspect that’s more for legacy and maintenance reasons.
I don’t think any user has a desire to supply colors in a linear colorspace – they want to know that all of the various pieces of an experience (3D models, shaders, HTML/CSS on the page, and image assets from photoshop) will work together in a consistent way.
I believe it’s fair to say that the colors you’re using in any other context (Blender, CSS, Photoshop, …) are treated as sRGB by default. But when renderer.outputEncoding = sRGBEncoding
, three.js is considering colors linear by default (implicitly — the only thing that actually changes is output encoding), creating a confusing disparity. This wasn’t an explicit decision, so much as an intermediate place we’ve gotten into along the path to supporting a linear PBR workflow.
Approved this one, save me a lot of time.
Demn Solved My Problem in just One Minute. I was using an image loaded as a texture and the image colors were badly inaccurate , just added this and boom correct colors Thanks