Vector3 vs Color

I noticed that an unlit shader graph seems to show different colors depending on data type. The left cube is using Color(0.5, 0.5, 0.5) (displays correctly) while the right cube uses Vector3(0.5, 0.5, 0.5) (incorrect). The vector3 value seems to display as (0.74, 0.74, 0.74). I’m using Unity 2021.2.5f1 with URP.

ColorTest unlit shader. Color(0.5, 0.5, 0.5)

VectorTest unlite shader. Vector3(0.5, 0.5, 0.5)

Is this a bug or am I just missing something?

I don’t have a thorough answer, but I immediately assume Color is literal, while Vector3 is going through a gamma correction or a perceptive luminance calculation, instead of a simple linear relationship.

1 Like

Whenever I hit things like this I’ll toss on the Colorspace Conversion node to find out what’s going on.

  1. Color
  2. Vector3 (Linear to RGB)
  3. Vector3 (RGB to Linear)
1 Like

Thanks @halley1 and @Ben_at_Work . That does seem to be the issue. The Colorspace Conversion node looks useful, I’ll have to try it in the future.

Technically they’re both “correct”.

This is on the right track … except it’s backwards. Color is going through a color conversion, and Vector3 is not! The thing you’re missing is when using linear color space rendering, which the URP and HDRP default to, the final image goes through a linear to gamma conversion before being displayed on screen.

Here’s the documentation for the Color node.
https://docs.unity3d.com/Packages/com.unity.shadergraph@10.2/manual/Color-Node.html

Here’s the code example for that node:

float4 _Color = IsGammaSpace() ? float4(1, 2, 3, 4) : float4(SRGBToLinear(float3(1, 2, 3)), 4);

Basically if a color conversion is needed, it does the gamma (sRGB) to linear conversion. If you were using gamma color space rendering, the color and vector3 nodes would end up looking exactly the same.

1 Like

I just noticed the exact opposite of this in script shaders. I’ve been pulling my hair out for the past day trying to figure out why the values in a stack of data-transformation blitting shaders I’ve written (as unlit generic surface shaders, because it’s a quicker pipeline when they’re working on full textures) were coming out wrong.

A shader designed to simultaneously write data to the alpha channel and initialize the RGB channels, when provided a Color property pickered to (128,128,128,0), was outputting (55,55,55,0) to the RenderTexture. Doing nothing but changing the type of that float4 property to Vector and setting it to (0.5,0.5,0.5,0), the shader now outputs the expected (128,128,128,0) at a data level.

This is incredibly counterintuitive, especially when, for actively hypothesizing and searching for the detail, I could find no clear way to specify at either the shader level or the RenderTexture (in-Editor) level that I really did want linear color. Docs basically just implied that yeah, Unity will correct sRGB to RGB transparently into the shader if and specifically if a regular Texture is imported as sRGB, otherwise it’s all 100% linear color shader-side and generatively.

I wouldn’t say it’s “implied” by the documentation, it’s explicitly called out in multiple places.

If this method’s target property is in gamma space and the project is in linear space, value is converted to linear space before being stored. Otherwise, value is stored without modification. For more information on color spaces, refer to Color spaces in Unity.

Description

Sets a value for a named vector in the material.

Four component vectors and colors are the same in Unity shaders. SetVector does almost exactly the same as SetColor just the input data type is different (xyzw in the vector becomes rgba in the color). The only difference is that color values are be converted from sRGB to Linear value, when using linear color space (see properties in shader programs).

Color spaces and color/vector shader data

When using Linear color space, all material color properties are supplied as sRGB colors, but are converted into linear values when passed into shaders.

For example, if your Properties shader block contains a Color property called “*MyColor“, then the corresponding ”*MyColor” HLSL variable will get the linear color value.

For properties that are marked as Float or Vector type, no color space conversions are done by default; it is assumed that they contain non-color data. It is possible to add [Gamma] attribute for float/vector properties to indicate that they are specified in sRGB space, just like colors (see Properties).

For content creation, this is what you want, because artists are going to want the color values they copy from other applications, which all work in sRGB color space, to be what they see on screen in the end. It’s setup to be intuitive for the most common use case, which is artists creating content from within the editor.

I will absolutely concede that it’s confusing when you’re getting into doing graphics programming, and will continue to be for a long time. I still can’t immediately remember which way should be doing pow 2.2 vs pow 0.4545 for approximated gamma conversions. (It’s 2.2 for sRGB → Linear, 0.4545 for Linear → sRGB, but I have to double check every time…)

And this all is exactly why I did not go down the route of adjusting the project color space since it’s more of a hack than a solve, and will also get ugly as soon as this lands in a project with actual artist involvement.

It’s just frustrating that the docs call out project-level sRGB and static texture level sRGB, and throw a lot of “when using xxxx space” which inevitably links back to texture importer & project level settings and how the texture settings only matter if the texture is contrary to the project settings, very little of which applies when I’m working in RenderTextures and raw inputs. Even had I thought to expect that the Editor would just be making SetColor calls under the hood and looked up that API specifically, the explicitly documented behavior would leave me second-guessing the docs’ accuracy: if the shader guts are explicitly linear space, and the project is explicitly linear space, only then are Color inputs inferred to be sRGB, while if the project is sRGB, Color inputs are I guess assumed to be raw and not converted.
It feels like the same category of mind-bend as camera transforms stacking upwards from scene space vs downwards from world space, or ye olde C++ multidimensional array indexing being declared as array-of-X-[Y-length-arrays]s but then indexed as give-me-the-yth-element-of-array-x.

Now that you point it out, it’s also weird that there’s no way to flag a texture or color input as “please use linear” and/or “please do not convert data” other than declaring a color as a Vector and losing picker access, while there is a way to flag a Vector input as [Gamma].

When Unity was first written, linear color space rendering wasn’t a thing that existed, because early GPUs didn’t support it. Everything was always in “sRGB” space, though really the only people that cared about that were the people working on low level stuff as everyone else just worked in sRGB space blissfully unaware of any other option.

At that time, there was only SetColor(), as there was no concept of gamma space, or HDR, or anything else.

Once linear color space rendering became “a thing”, and GPUs started adding support for it, Unity’s existing rendering and material system had to be appended to to add support for it. Even things like the texture importer, the sRGB flag is a convenience thing that doesn’t actually exist under the hood. A texture being sRGB or not is determined by the format of the texture, not some separate sRGB flag. But for ease of use they added the sRGB option to automatically pick between the linear and sRGB versions of the texture formats.

For render textures this originally got obsfucated behind the RenderTextureReadWrite setting when creating a render texture object.

Though you can now use the GraphicsFormat to set this explicitly. This is a partially an undocumented feature, though it is there in the documentation buried under the RenderTextureDescriptor form of the render texture constructor, which also lets you more explicitly set the sRGB flag that controls how data is written to it.

There’s also the GL.sRGBWrite setting that lets you force the write behavior.

As for why the Vector parameter type has a [Gamma] option… I have no idea. That’s a relatively new addition that I have zero explanation for since that’s what a color property is. Color doesn’t have a [Linear] because Vector exists, and also AFAIK Vector predates the [] property modifiers being a thing at all.