Standard shader differences in WebGL

Hi,

We’ve been having a few issues with getting the standard shader to show similar results in WebGL as on other platforms.

The main problem is that materials appear way too shiny, and that adjacent triangles are sometimes shown with different smoothness, without a smooth transition.
Triangles also change their smoothness too much in response to changed viewing angles.

While investigating these issues, I’ve experimented with the shader sources and found some of the causes and have tried to remedy them. I would appreciate cmments from Unity regarding these issues, and if they are actively being improved or if our best bet for the future is to keep modifying the standard shader:

First of all, eyeVec is only normalized per vertex. I know this is done for performance reasons on SM2, but WebGL is mainly run on computers that can handle per pixel normalization.
When changing this, smoothness of the surface is much more predictable, both in response to viewing angle, and also between triangles that share edges.

After adjusting eyeVec, surfaces are still too smooth in comparison with other platforms, and there is still a small difference in smoothness between adjacent surfaces in certain viewing angles.
Both these issues have to do with texCUBElod() not being supported on SM2 / WebGL.
I dont know exactly how texCUBEbias works, but I’m guessing that the bias depends on whatever lod is automatically calculated for a given pixel?
Anyway, the lod chosen by the bias function does not correspond to what texCUBElod gives, and surfaces are therefore smoother in WebGL.
I changed the macro that uses texCUBEbias to use lod * 1.3 instead of lod * 1, and we are seeing more similar results.
This feels like a big hack however, and also seems to depend a bit on aniso level of reflection cubemaps.
Any and all ideas about better ways to get higher quality on blurry reflections would be appreciated!

Last of all, transparent materials are not looking the same on WebGL.
This one was easy, the alpha of a semi-transparent pixel is calculated in a much simpler way on SM2.
But the same as before applies here, WebGL is run on computers, and we found no issues using the more expensive calculations targeted at SM3.

So, basically we have a working shader right now that gives acceptable quality. But we would really like to know if any of the proposed changes will make it into the standard shader (or other better solutions).
We will probably use WebGL a lot in the coming year, and we are not comfortable with having to keep separate modified versions of the standard shader for each project, remembering to keep to keep it in sync with the official sources.

There’s some good research there! I have in the past couple of days been experiencing the same issues so any response to this great description would be appreciated.

Shall keep my eye on this thread

Indeed good research. Currently, we have some shader decisions hard-coded per graphics API (WebGL == OpenGLES 2.0) - which unfortunately often does not result in the best choices for WebGL. While I would like to see that change, I cannot make any promises on it, so right now, keeping a separate shader for your project is probably the best solution.

See this post for some more explanations:
Severe banding with WebGL on Chrome

Okay, thank you for responding. We’ll keep custom versions of the standard shader for WebGL for now.
Hopefully you’ll later add custom defines for WebGL so that features that are now turned off because off mobile performance conciderations will not be in the future.

Mistale how would you feel about sharing more info on your fixes? I am not at all familiar with shaders and currently this issue is rendering my current setup unusable on WebGL. Obviously you have put in significant work to get to your current solution so i understand if you do not wish to share it!

Cheers

I can share info about our shader, no problem. I haven’t made any significant changes.
First of all, you need to create a clone of the standard shader to be able to customize it, so download the shader sources for Unity 5 and extract them into any subfolder inside your projects Assets folder.

You can remove all extracted files except the following:

  • CGIncludes/UnityCG.cginc
  • CGIncludes/UnityStandardCore.cginc
  • CGIncludes/UnityStandardUtils.cginc
  • DefaultResourcesExtra/Standard.shader

Next, put the files listed above into the same folder. There are other ways, but this the simplest way of making sure that your shader will reference the correct .cginc-files.

Rename “Standard.shader” to whatever you want your shader to be called, and then open the file inside MonoDevelop or any other editor.
Change the name of the shader (Shader “Standard”) on the first row in the file to whatever you renamed the file to.

If you want to be able to see the same results in the Unity editor as in WebGL, remove the section of the shader that begins with:

    SubShader
    {
        Tags { "RenderType"="Opaque" "PerformanceChecks"="False" }
        LOD 300

Remove all the way up to (but not including) the next “SubShader” tag.

Next, open the file “UnityCG.cginc” in your texteditor of choice.
Search for the phrase “texCUBEbias”.

You’ll find something like this:

// SM2.0 does not support texCUBElod

#if (SHADER_TARGET < 30)

#define SampleCubeReflection(env, dir,  lod) texCUBEbias(env, half4(dir, lod ))

#else

#define SampleCubeReflection(env, dir,  lod) texCUBElod(env, half4(dir,lod))

#endif

Replace that entire section with this:

#defineSampleCubeReflection(env, dir, lod) texCUBEbias(env, half4(dir, lod * 1.3))

Next, open the file “UnityStandardCore.cginc”.
At the top of the file, the two methods “NormalizePerVertexNormal” and “NormalizePerPixelNormal” should be located. If not, search for them.

Replace the two methods with this:

half3 NormalizePerVertexNormal (half3 n)
{
     return n;
}

half3 NormalizePerPixelNormal (half3 n)
{
     return normalize(n);
}

Last of all, open the file “UnityStandardUtils.cginc”.
Locate the method “PreMultiplyAlpha”, and change it like below:

inline half3 PreMultiplyAlpha (half3 diffColor, half alpha, half oneMinusReflectivity, out half outModifiedAlpha)
{
    #if defined(_ALPHAPREMULTIPLY_ON)
        // NOTE: shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)

        // Transparency 'removes' from Diffuse component
        diffColor *= alpha;
              
        outModifiedAlpha = 1-oneMinusReflectivity + alpha*oneMinusReflectivity;
    #else
        outModifiedAlpha = alpha;
    #endif
    return diffColor;
}

This should work, and I think that’s all the changes that I’ve done to our own shader.
It’s not perfect, since texCUBEbias will not match texCUBElod exactly no matter what you change. So there’s still a chance that boundaries between surfaces will be visible under certain conditions.

Note you will probably get somewhat different results than me regarding texCUBEbias and the “magic” lod * 1.3 formula, since texCUBEbias seems to depend on what miplevel has already been calculated and the bias value is a relative shift from that value instead of being an absolute lod level as for texCUBElod (please guys, correct me if I’m wrong here, been searching like crazy for information on this).
So feel free to “fudge” that multiplier.

Edit: Forgot to mention that if you’re using a reflection cubemap, using a high aniso level for it seems to make texCUBEbias behave more like texCUBElod, but probably at a cost.

2 Likes

Thanks mate, i really appreciate that, you clearly know what you’re doing. It seems to have helped get the material looking closer to the editor, but i am getting terrible issues with the smoothing, purely on WebGL.

I don’t think I can help you with that, it doesn’t look like the kind of issues we had. We aren’t using any lightmaps, are you?

No lightmaps on the model no. I may have to create a new thread…

thanks for your help

The Legacy shaders seem to work well in WebGL.
However the problem with them is that Unity5 assigns the new standard shaders on the models import by default and then I have to change all od them manually.

Did this get moved to HLSLSupport.cginc? I’m looking in 5.1.1f1 and it seems that these lines are now in HLSLSupport.cginc

1 Like

what echo4 asked (but in 5.3…)

@Mistale I was checking in 5.2.1f and was wondering the same thing as @echo4papa . Did these lines get moved?

Hi, I’m not actively working with 5.1 or above at the moment, so I cant really say what changes have been made. But if you find texCUBEbias in some other file than the one I specified, just use that instead. The changes I made were just to make sure that results of the bias would be closer to the results of texCUBElod (by scaling lod by 1.3) and to be able to see the same results in the editor as in WebGL (by removing support for texCUBElod).

There is a strong possibility that Unity has changes a lot of stuff in their standard shader since I wrote the initial post, so I cant promise that my ‘fixes’ are still valid, or even still needed.

Happy hunting!

1 Like

is this problem been fixed already?
@jonas-echterhoff_1 any news?

I’m still seeing this in 5.3.4, though 5.3 release notes seems to indicate that it was fixed.

Can you post a repro case?

I hope this is the same issue you are talking the whole time…

I’m using unity 5.4.2f2. While webgl in chrome on a windows machine is looking ok, the smoothness of skybox reflections is completely gone when rendered on webgl, on safari osx, or ios, or android. See the two screenshots.
This is the result in the editor or webgl in chrome browser on a windows machine. Looks as expected.

This is the result on all other platforms. (Webgl on OSX, IOS and Android)

I’m using a product lighting skybox that consist of gray and white planes. In the second screenshot you can really see this planes reflected 1:1 with no reflection smoothness at all.
Is this a bug? Or maybe the standard shader requires a shader model that is not supported on this platforms? Is there a workaround?

Thank you very much,
Michal