How can I make the canvas transparent on WebGL?

How can I make the canvas transparent on WebGL?

Reading Time: 5 minutes

Transparent canvas on WebGL using Unity?

When doing development for WebGL, there is sometimes this question. Can we make the canvas transparent? Can we make HTML elements be in front of the canvas, but also can we make them behind the canvas?

The answer to this question is a surprising yes! This is true not only for Legacy Renderer but also for URP!

A webGL player in Unity with a 3D model of a bike on a transparent background showing HTML background
https://prographers.com – transparent background in WebGL player

Transparent canvas for Legacy Renderer in WebGL

In the legacy renderer, it’s pretty simple to achieve that. There are 2 parts to make this work.

  1. JavaScript code in *.jslib
  2. The camera clear mode changed

I recommend starting it first in an “empty” project, to remove all possible dependencies on the first try!

JavaScript injection

Create a new file called transparentBackground.jslib and put there this code

// Create new module that we will inject into framework.js file
var transparentBackground = {
    // Create a method glClear - this will overwrite default behaviour
    glClear: function(mask)
    {
        // If mask of the layer is set to "depth only"
        if (mask == 0x00004000)
        {
            var v = GLctx.getParameter(GLctx.COLOR_WRITEMASK);
            // And the color is "transparent"
            if (!v[0] && !v[1] && !v[2] && v[3])
                // We do nothing
                return;
        }
        // else we clear the mask to prevent tearing
        GLctx.clear(mask);
    }
};
// Merge our module into framework.js
mergeInto(LibraryManager.library, transparentBackground); 

Camera clear mode

Take all cameras that should render with a transparent background and change their “Clear Mode” to “DepthOnly”

To do this from within Unity you can just select a given option or use code on a camera component

void Start()
{
    GetComponent<Camera>().clearFlags = CameraClearFlags.Depth;
}

Transparent canvas for Universal Rendering Pipeline (URP)

Here first 2 steps look exactly the same. We need to inject the same JS code and set camera clear flags to DepthOnly. But here it’s a bit more complicated than that…

JavaScript injection

Create a new file called transparentBackground.jslib and put there this code

// Create new module that we will inject into framework.js file
var transparentBackground = {
    // Create a method glClear - this will overwrite default behaviour
    glClear: function(mask)
    {
        // If mask of the layer is set to "depth only"
        if (mask == 0x00004000)
        {
            var v = GLctx.getParameter(GLctx.COLOR_WRITEMASK);
            // And the color is "transparent"
            if (!v[0] && !v[1] && !v[2] && v[3])
                // We do nothing
                return;
        }
        // else we clear the mask to prevent tearing
        GLctx.clear(mask);
    }
};
// Merge our module into framework.js
mergeInto(LibraryManager.library, transparentBackground); 

Camera clear mode and …

As before we need to set the camera mode to Depth Only, but in URP this setting is hidden, and cannot be selected from the normal menu.

Image in Unity3D that shows missing background type  - Depth only
Missing background type – Depth only

In order to change Camera mode manually, you need to switch inspector to debug mode

Gif showing on how to change inspector to normal mode and camera mode to depth only
How to set camera to Depth Only

As before this can be changed from code mutch easier

void Start()
{
    GetComponent<Camera>().clearFlags = CameraClearFlags.Depth;
}

But this time this is not enough!

Camera post-processing

Unfortunately in URP postprocessing transforms all pixels – including transparent ones and forces the background to be rendered and our JavaScript injection cannot detect it, so we need to disable that as well.

To do that we can do it per camera from settings or via script. In the camera settings, we need to find Post Processing and toggle it Off.

Highlighted post-processing toggle is disabled
Post-processing settings is disabled

From the code, since this is part of URP we need to access UniversalAdditionalCameraData component and disable it there.

var universalAdditionalCameraData = GetComponent<UniversalAdditionalCameraData>() as UniversalAdditionalCameraData;
        universalAdditionalCameraData.renderPostProcessing = false;

The full solution for URP

The full solution to do this per camera would look like this

using UnityEngine;

public class SetupCameraForWebGLTransparency : MonoBehaviour
{
    private void Awake()
    {
        GetComponent<Camera>().clearFlags = CameraClearFlags.Depth;
        var universalAdditionalCameraData = GetComponent<UniversalAdditionalCameraData>() as UniversalAdditionalCameraData;
        universalAdditionalCameraData.renderPostProcessing = false;
    }
}

Pitfalls of a transparent canvas

Unfortunately, this method has its downsides that are hard to overcome but can be overcome with a “smart” design.

Transparency

This is the biggest issue that we encountered. If you opted for a transparent canvas all your transparent pixels will be gone! Even ones that are 99% solid. If they have any alpha they will just disappear. Unless they will be rendered above solid objects.

Object transparency visibility with transparent webGL canvas
1, Solid Opaque object – Fully visible
2. Transparent object – Invisible
3. Solid Opaque object – Fully visible (tinted blue because of the transparent object ahead)

So as described in the image above, to make objects visible you need to make them either 100% solid (they can have a transparent shader, but it’s not recommended). Or make them as part of the opaque background.

You can think of this as such.
House windows view from the outside will have the house as a background, so they will be visible. But when we enter the house and we will look through the window to the outside (and there will be no environment) the windows will be gone.

Unity UI

Since we want to have a transparent background so that we can have immersive HTML UI, this point probably doesn’t bother anyone. But if you already have your UI in Unity and not HTML then you should know that you will have problems with transparency as stated before. All transparent (even 99% solid) pixels would be invisible in this scenario. So you would have to redesign your UI to make sure that you have only hard edges that are not fading by transparency.

Post-processing

Unfortunately, all post-processing in URP and some in Legacy will cause to have a background from Unity rendered. This is because, the post-processing pipeline (at least in URP) will by default take the color of the background, even though we want it to have it cleared.

You can recreate some effects using HTML like vignette, and color grading. For others, you would need to resort to custom shaders, but some effects will not be achievable at all because of the Transparency issue. Bloom, Chromatic abbreviation, etc… And even if they would be possible, they would require a custom post-processing pipeline that would respect transparent pixels. Not to mention that post-processing on the Web would kill mobile performance.

Summary

Transparent HTML canvas in Unity WebGL is possible! No more changes are required, and the other good part is that this is a fully dynamic effect. You can toggle it on and off based on your current needs. Need to render it transparent to showcase a “product” but then you want to “visualize” it with background and with all the post-processes? It’s all possible!

Have trouble with implementation? Reach out to us! We will be glad to help 🙂
https://prographers.com/Contact

This effect was possible because of Ricardo Arango who made a blog post on it back in the day on Unity forum.

This effect works on all versions of Unity from Unity 5 to latest 2022 (currently beta) and I expect that it will work for a long time until we get some major rework of the WebGL player.

, ,