Closed emackey closed 1 month ago
If SP is doing this differently, it would unfortunate. But we can't just use SP as the truth. Unity, for example, does exactly what the formulas say.
Unity built-in shaders can be downloaded here: https://unity3d.com/get-unity/download/archive
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
CGIncludes\UnityStandardConfig.cginc
// Energy conservation for Specular workflow is Monochrome. For instance: Red metal will make diffuse Black not Cyan
#ifndef UNITY_CONSERVE_ENERGY
#define UNITY_CONSERVE_ENERGY 1
#endif
#ifndef UNITY_CONSERVE_ENERGY_MONOCHROME
#define UNITY_CONSERVE_ENERGY_MONOCHROME 1
#endif
CGIncludes\UnityStandardUtils.cginc
// Helper functions, maybe move into UnityCG.cginc
half SpecularStrength(half3 specular)
{
#if (SHADER_TARGET < 30)
// SM2.0: instruction count limitation
// SM2.0: simplified SpecularStrength
return specular.r; // Red channel - because most metals are either monocrhome or with redish/yellowish tint
#else
return max (max (specular.r, specular.g), specular.b);
#endif
}
// Diffuse/Spec Energy conservation
inline half3 EnergyConservationBetweenDiffuseAndSpecular (half3 albedo, half3 specColor, out half oneMinusReflectivity)
{
oneMinusReflectivity = 1 - SpecularStrength(specColor);
#if !UNITY_CONSERVE_ENERGY
return albedo;
#elif UNITY_CONSERVE_ENERGY_MONOCHROME
return albedo * oneMinusReflectivity;
#else
return albedo * (half3(1,1,1) - specColor);
#endif
}
CGIncludes\UnityStandardCore.cginc
inline FragmentCommonData SpecularSetup (float4 i_tex)
{
half4 specGloss = SpecularGloss(i_tex.xy);
half3 specColor = specGloss.rgb;
half smoothness = specGloss.a;
half oneMinusReflectivity;
half3 diffColor = EnergyConservationBetweenDiffuseAndSpecular (Albedo(i_tex), specColor, /*out*/ oneMinusReflectivity);
FragmentCommonData o = (FragmentCommonData)0;
o.diffColor = diffColor;
o.specColor = specColor;
o.oneMinusReflectivity = oneMinusReflectivity;
o.smoothness = smoothness;
return o;
}
I see. Looks like it's configurable with shader defines, I wonder if the SP one is configurable too.
It's a shame there isn't better standardization of spec/gloss PBR.
@emackey, I looked into what you were seeing with the viewport renderer in Substance Painter, and it is due to a lack of shadows in the viewport renderer. There is an option in display settings to enable shadows on the viewport renderer which brings the render in line with the iRay version which we have used for some of our ground truth testing. The reason that you can disable shadows in the viewport render is that most artists won't want to be texturing an asset that has shadows on it as it can make you think your colors are darker than they are and cause your textures to be off as you are compensating for the shadows.
In this case, with your settings for spec-gloss, it appears that the renderer is trying to render an overloaded color over 1 so the green is going yellow because it's reading from an HDR environment and applying specular color. I obviously don't see this issue when working with metal-rough and spend most of my time there.
Here are a couple of comparison screens of the viewport renderer (with the shadows enabled in Display Settings) and iRay:
In this second set, I rotated the hottest light to reflect on the face and you can see it still goes a little more yellow in the viewport render, but that can be reduced a bit by increasing the quality under Shader Parameters as seen in the screen shot.
Lastly, I reduced the glossiness on the shader ball so we can see what more spread on the specular renders like. Again, the viewport has more yellow, but more of note is the diffuse color showing through more in the viewport than in the iRay render. As an artist, I will always look to the ray tracer as my ground truth
@emackey here is another thing you can do if you would like to have Painter's Viewport look like glTF, and that's use this version of the spec-gloss shader in Painter:
Place it in the folder at: C:\Program Files\Allegorithmic\Substance Painter\resources\shelf\allegorithmic\shaders
And then choose that shader in Shader Settings and after turning off shadows in Display Settings you will get:
And in Babylon:
I took a long break from this issue, but I do have some updated notes here. I've researched which platforms (of the ones I have access to) use which formulas for combining PBR diffuse with PBR specular. There are four different formulas so far:
This is the formula specified by glTF. It is used in Windows 3D Viewer, BabylonJS, and CesiumJS. It is unusual (in my experience) that any (non-special-effect) formula for 3D rendering allows different color channels to mingle and influence each other, as the max
statement is doing here. It's being done in the name of overall light energy conservation. But it is not per-frequency energy conservation, and after experimentation I definitely feel like there are situations where this becomes noticeable.
The formula is also used by Unity's shader code posted by @bghgary above, but only when UNITY_CONSERVE_ENERGY
and UNITY_CONSERVE_ENERGY_MONOCHROME
are both defined.
This one also comes from Unity (above), but it appears to be doing this only as a fallback behavior for Shader Model 2.0 (obsolete). So I think this one can be disregarded.
Substance Painter uses this formula in its "PBR - Specular Glossiness (allegorithmic)" profile. It is energy-conserving per-channel, so does not allow different color channels to influence one another.
The above Unity shader will use this formula if UNITY_CONSERVE_ENERGY
is defined and UNITY_CONSERVE_ENERGY_MONOCHROME
is undefined.
This simplistic approach doesn't attempt any energy conservation, or makes the assumption that conservation has been baked into the diffuse map from the start. Blender's Eevee "Specular BSDF" (realtime-only) node takes this approach, as does ThreeJS, and Sketchfab.
The Unity shader will do this one too, when UNITY_CONSERVE_ENERGY
is undefined.
I can't really argue that glTF picked the "wrong" one, as each one has its own merits. But I spent a while experimenting with these formulas today, and the one we selected is the least flexible, in the sense that it can barely do anything that Metal/Rough can't do. By comparison, formula 3 above can do some weirdly interesting things while still claiming per-color-channel energy conservation. But it's formula 4, the non-energy-conserving one, that feels most intuitive to an artist messing around with color selectors, as the artist's selected colors are not tampered with by formula 4.
My larger concern is that various platforms are all over the map as to which one is used. An engine can easily claim compatibility with the SpecGloss extension, without picking the glTF standard formula. I've been working on a test model that will visually highlight which formula is in use, if we think that could help the situation.
FWIW, one can argue that having different diffuse/specular colors is no longer physically based since AFAIK it doesn't occur in real life and thus the asset should be authored with that in mind. The only scenario I've heard where it is appropriate to use different diffuse/specular color is to fake multiple layers being blended due to translucency.
This relates to an extension that has been deprecated / archived.
The spec/gloss formulas are detailed in this section and were also discussed here. The diffuse light formula is given as:
Cdiff =
diffuse.rgb * (1 - max(specular.r, specular.g, specular.b))
Due to the use of
max
, the contents of one color channel can affect the diffuse light for all.Doing some testing in Allegorithmic's Substance Painter 2018, with their stock spec/gloss PBR workflow, I'm seeing different results. As a test I made a model with the diffuse channel set to red [1.0, 0.0, 0.0] and the specular to lime green [0.0, 1.0, 0.0]. The resulting model in Substance Painter's preview window has a bright yellow hue to it. Splitting up a screenshot of that rendering into its component color channels reveals that the red channel looks like a diffuse rendering, and the green channel looks like a specular rendering, which seems quite reasonable.
Back in glTF land though, the same model shows up as shiny specular green, without any hint of red or yellow. This is because that
max
statement has flattened out the red diffuse color on account of the green specular color. Is this really intentional?Looking at my install of SP, I found a file here: Program Files/Allegorithmic/Substance Painter/resources/shader-doc/pbr-spec-gloss.html. It documents the spec/gloss shader snippet as having essentially the same formula, without the
max
statement:Cdiff =
diffuse.rgb * (1 - specular)
This change does seem to make the interaction between diffuse and specular capable of a much wider range of appearances. In metal/rough terms this would be akin to allowing the artist to assign different levels of metalness to different color channels.
Would be great to hear feedback from any artists who have experience with this workflow.
I know we don't yet have a standard process for upgrading extension versions, but I'm starting to believe this one is in strong need of an upgrade. I think the
max
statement should be removed.