Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Canvas Size/Resolution will not scale when using Linear color space

Discussion in 'Web' started by OChrisJonesO, Dec 11, 2017.

  1. OChrisJonesO

    OChrisJonesO

    Joined:
    Jun 27, 2015
    Posts:
    13
    I've already submitted a bug report for this issue, but I wanted to see if anyone had a workaround in the meantime?

    From: https://fogbugz.unity3d.com/default.asp?977579_jjs2s2bc03s94lu5

    1. What happened
    When using the linear color space option in the player settings of a WebGL build, the <canvas> element is not resize/rescalable. However, with the older Gamma option, it is.

    2. How we can reproduce it using the example you attached
    1)Start with an empty project or any project of your choosing. Make two WebGL builds, one using Gamma color space, and one using Linear(player settings > other settings > color space. You'll also need to un-check auto graphics API, and make sure WebGL 2.0 is the only option in the list when doing the Linear build).

    2)Run the built project in a web browser. The <canvas> element Unity renders to has a set width/height values. You can change these values, either with a set number in the HTML attribute, or by using CSS to apply 100% width and 100% height of it's parent container, which then allows it to stretch to fill the window size. The gamma color space build will adjust it's resolution accordingly to fill the new canvas size correctly. The linear color space build, however will not. Whatever resolution / size it loaded in initially, it will be stuck in for the session, whereas the gamma color space build will dynamically adjust.

    It's worth noting that using gameInstance.SetFullscreen() (clicking the fullscreen button in the default WebGL template) DOES work and will resize and change resolution on both the Gamma and Linear build. However, when you exit fullscreen mode, the <canvas> element itself is still not resizing / re-rendering correctly on the Linear build only, as stated above.

    It appears as if Unity itself is in fact re-sizing correctly, it's just not rendering the new size correctly. If you start with a smaller canvas, and try to increase it's size, you can still interact with Unity by clicking anywhere inside the new canvas size and it will go through. (So for instance, click in the background of the newly re-sized canvas's rectangle. Your clicks will in fact go through to Unity and you can interact with the scene, so it's as if it somehow did re-size correctly, it's just not rendering it at that new size.
     
  2. Marco-Trivellato

    Marco-Trivellato

    Unity Technologies

    Joined:
    Jul 9, 2013
    Posts:
    1,654
    I think it might be this problem: https://issuetracker.unity3d.com/is...ce-camera-viewport-rect-problem-in-built-game

    as a workaround, could you try to disable HDR and MSAA on the camera?
     
  3. OChrisJonesO

    OChrisJonesO

    Joined:
    Jun 27, 2015
    Posts:
    13
    Just tried this, still doesn't fix. Disabled HDR and MSAA on the camera and re-built. The canvas still does not resize / render at the correct resolution when you try to change it's size after Unity initializes (whereas Gamma does). The dark grey background / letterbox around the canvas is the result of this.

     
  4. OChrisJonesO

    OChrisJonesO

    Joined:
    Jun 27, 2015
    Posts:
    13
  5. OChrisJonesO

    OChrisJonesO

    Joined:
    Jun 27, 2015
    Posts:
    13
    Any update on this? Possibly fixed whenever Unity 2018 is released? This is a show-stopper for us currently as we use Unity WebGL in a responsive full-browser-width/height template. We're stuck using Gamma until this gets fixed.
     
  6. Marco-Trivellato

    Marco-Trivellato

    Unity Technologies

    Joined:
    Jul 9, 2013
    Posts:
    1,654
    Your bug report hasn't been processed because it's missing a repro project. I understand you can repro with an empty project but it would be helpful if you could attach it anyway, including the WebGLTemplate or the html code that modifies the canvas size.
     
  7. OChrisJonesO

    OChrisJonesO

    Joined:
    Jun 27, 2015
    Posts:
    13
    After some further discussion via email on the bug report, it looks like this has been re-opened.
     
  8. OChrisJonesO

    OChrisJonesO

    Joined:
    Jun 27, 2015
    Posts:
    13
    We've done alot of work on dealing with this bug in the past week, including coming up with a workaround, and I thought I'd share my findings. Please excuse anything I say that's not 100% accurate, I'm by no means a graphics programmer.

    We've tried a TON of different things. Manually specifying the Canvas & GameContainer sizes, manually setting Unity's resolution using Screen.SetResolution(), modifying CSS / JS, trying to make native WebGL graphical calls to resize (see https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html), trying to use gameInstance.Module.setCanvasSize(), etc all to no avail.

    However currently (2017.2 / 2017.3 / 2018.1 beta), you can actually get a Linear WebGL build that works / resizes properly, well, sort of. We noticed there are quite a few things inside the project that can break the scaling, if enabled. The similarities between these seems to be related to using floating point buffers (as per https://docs.unity3d.com/Manual/HDR.html). Any effect used that utilizes these buffers doesn't seem to get resized properly, and will stay at the size/resolution that it gets initialized at when Unity first starts. These effects include (but likely aren't limited to):

    - "Allow HDR" on camera component
    - "Allow MSAA" on camera component
    - Anti Aliasing in Quality Settings
    - Using elements of the Post Processing v2 Stack (https://github.com/Unity-Technologies/PostProcessing)

    If your project is not using any of the above, your build will actually resize correctly without having any scaling or aspect ratio issues. Turn any of them on, and your build will not scale correctly. Even if you turn them on at runtime (example: make a toggle that turns HDR on and off and you will break the scaling / fix the scaling with each time you toggle the effect).

    However, you'll notice that this scaling ONLY works if you scale down (have a large window size, and then resize your window to be smaller). If you try to go the other way around, you'll notice that it won't scale past the size that your window was when unity initializes, and the extra window space will just be filled in with whatever background color your template uses. So again, big -> small works, small -> big does not.

    This is where the workarounds begin. First, we removed anything from our project that we knew would instantly break scaling (disabled HDR, AA, and the PostFX v2 stack). Next, we wanted to get resizing small to big to work. As stated previously, it seems that you're capped at whatever size the canvas initializes itself at, and you can't go past that. So what you can do is resize your canvas to be huge (way bigger than the window size, say an arbitrary value like 4k), and then after a few frames render, resize back down to the window size. This is a little tricky since the canvas gets instantiated at runtime inside the gameContainer div and no longer exists statically in the HTML file. Where your current UnityLoader.instantiate call is located, replace it with this:

    Code (JavaScript):
    1. var gameInstance = UnityLoader.instantiate("gameContainer", "/Build/UnityWebGL.json", {
    2. onProgress: CustomLoadingMethodIfYouHaveOne,
    3. Module: {
    4. TOTAL_MEMORY: WhateverYouSetYourMemoryTo,
    5. onRuntimeInitialized: UnityLoaderCompletionHandler,
    6. },
    7. });
    And then add this function to your JavaScript:

    Code (JavaScript):
    1. function UnityLoaderCompletionHandler() {
    2. var canvas = document.getElementById("#canvas");
    3. canvas.id = "canvasLarge";
    4. setTimeout(function(){
    5. canvas.id = "canvas"
    6. canvas.setAttribute("tabIndex", "1");
    7. }, 3000);
    8. }
    What this does is once Unity is initialized, gets the canvas element, and changes it's ID from #canvas to canvasLarge. After 3 seconds, it then sets it back to canvas. (Notice it's setting it back to "canvas" and not "#canvas". This is a result of having an ID with a "#" in the name - it's impossible to reference via CSS and I believe this is a bug Unity should also fix. Canvas's default ID should be "canvas" and not "#canvas"). Why are we swapping the ID back and forth? To apply the following CSS styles, which will also need to be in your index.html.


    Code (JavaScript):
    1. #canvasLarge{width: 3840px; height: 2160px;}
    2. #canvas{width: 100%; height: 100%;}
    Setting the canvas ID to canvasLarge will set it to 3840x2160 (4K) and render that for a few frames/seconds, and then after 3 seconds sets it back to 100% width/height (which will go back to whatever the window size is. This allows us to scale from small -> big, because our "big" max size is now 4K (and obviously you can set whatever value you want here).

    So at this point, we've got a Linear build that allows for dynamic resizing of the window/canvas size from small->big and big->small, and everything works! Unfortunately, without HDR, Anti-Aliasing, and the PostFX v2, things don't look the nearly as good...
     
    Last edited: Jan 25, 2018
    SearchTree, De-Panther and arumiat like this.
  9. OChrisJonesO

    OChrisJonesO

    Joined:
    Jun 27, 2015
    Posts:
    13
    (continuing from last post) So let's fix that! Again as mentioned previously, all of these effects/eye candy seem to break scaling as the floating point buffers don't get resized (although it appears the color, depth, and stencil buffers do) and so the aspect ratio gets all messed up when you resize, as it wants to keep its buffer size at whatever Unity initializes at (whatever you set your canvasLarge ID width/height to in the css). No amount of clearing buffers on the WebGL side using WebGLRenderingContext.Clear() or .flush() (per https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/clear) or clearing buffers on the Unity side using GL.Clear(); or GL.Flush(); (per https://docs.unity3d.com/ScriptReference/GL.html) seemed to work, likely because we were clearing the wrong buffers. From the JS side for example, we could clear/flush the Color, Depth, and Stencil buffers. Even on the unity side, we couldn't find any way to resize the "floating point buffer" that these effects seemed to be using.

    HOWEVER

    With what we have so far, we noticed that any Unity UI Canvases would scale correctly to any size, maintaining aspect ratio and also receiving click positions correctly. It was just what the Camera was rendering that wouldn't resize correctly. So it seems that "screen space" was resizing correctly, but "world space" was not? Thankfully, this allows us to have a functional workaround.

    Rather than having your Main Camera render directly to the screen (which we've seen, will NOT resize correctly) we can render it to a Unity UI element using RenderTextures, and that will actually scale correctly. To do this:

    1) Add this script to your project, attach it to a GameObject named ScreenManager

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.Rendering;
    6. using UnityEngine.UI;
    7.  
    8. public class ScreenManager : MonoBehaviour
    9. {
    10.     public Camera camera;
    11.     public RawImage fullscreenRawImage;
    12.     void Awake() {
    13.         RenderTexture renderTexture = new RenderTexture (Screen.width, Screen.height, 24, RenderTextureFormat.DefaultHDR);
    14.         renderTexture.name = "MainCameraRenderTexture";
    15.         camera.targetTexture = renderTexture;
    16.         fullscreenRawImage.texture = renderTexture;
    17.     }
    18.  
    19.     public void SetRenderTextureResolution(string message) {
    20.         string[] split = message.Split('x');
    21.         int width = int.Parse (split [0]);
    22.         int height = int.Parse (split [1]);
    23.         Debug.Log ("Setting resolution to: " + width + "x" + height);
    24.  
    25.         camera.targetTexture.Release ();
    26.         RenderTexture resizedRenderTexture = new RenderTexture (width, height, 24, RenderTextureFormat.DefaultHDR);
    27.         resizedRenderTexture.name = "MainCameraRenderTexture";
    28.         camera.targetTexture = resizedRenderTexture;
    29.         fullscreenRawImage.texture = resizedRenderTexture;
    30.     }
    31. }
    2) In your existing Unity UI Canvas (create one if you don't have one already) create a RawImage object, and set it's position and pivot to center and fullscreen (hold alt + shift and click when clicking the bottom right option in rect transform stretch).

    3) Set the Cam property on your ScreenManager to point to your Main Camera. Set the Fullscreen RawImage property to the RawImage element we just created.

    This will prevent your main camera from rendering to the "Screen" and instead will render it to a RenderTexture. This RenderTexture is then fed into the RawImage's Texture property, which displays the camera view. Since we set the position/pivot/stretch to be elastic and scale to the window size, this'll get it to scale correctly! At this point, we're almost done. We have a scalable window, however the RenderTexture will just be stretching to fill the size, and not actually changing resolution (so aspect ratio won't be maintained, and things will look bad).

    In your index.html / JS, you simply need to detect window resizes, and then do a SendMessage call to that ScreenManager to set the correct resolution. (RenderTextures aren't resizable once created, so the ScreenManager will destroy the old one and create a new one at the specified resolution. To do this, add this JS:

    Code (JavaScript):
    1. window.onresize = function(event) {
    2. clearTimeout(resizeEndDelay);
    3. resizeEndDelay = setTimeout(function() {
    4. var canvas = document.getElementById("canvas");
    5. if (canvas != null) {
    6. var width = canvas.width;
    7. var height = canvas.height;
    8. var resolution = width + "x" + height;
    9. if (width != 0 && height != 0){
    10. Unity.SendMessage("ScreenManager", "SetRenderTextureResolution", resolution);
    11. }
    12. }
    13. }, 250);
    14. }
    Using this, we should now have everything working! To explain the snippet above, using clear/setTimeout will prevent a million SendMessage calls from being sent on window resize. JS doesn't have a "onResizeEnd" method to hook into, so we can simply offset it a short amount of time to wait for the resize to finish, and then set the proper resolution. And the if (canvas != null) will prevent errors from being thrown if you resize the window while Unity is initializing (because it starts with a canvas id of "#canvas" as previously mentioned, so the var canvas = document.getElementId("canvas") will return null and error out otherwise).
     
    Last edited: Jan 25, 2018
    arumiat likes this.
  10. OChrisJonesO

    OChrisJonesO

    Joined:
    Jun 27, 2015
    Posts:
    13
    Here's a working example of all of that ^^

    Working build (index.html + build folder): https://drive.google.com/file/d/11eOAmLqnJdCgaitItASIFWx9bDFT0kj6/view?usp=sharing

    Project source: https://drive.google.com/open?id=1FifVWY4kqhT_DdCxAhzxHq0x6D8IAD5S
    (Note: I didn't create WebGLTemplate for this, so just build out and manually replace the index.html with the one from the "working build" listed above.)

    Live Demo (no loading bar, screen will be grey until its ready): http://mxt-static-websites.s3-website-us-east-1.amazonaws.com/LinearScalingBug/
     
    Last edited: Jan 25, 2018
    Deleted User and arumiat like this.
  11. OChrisJonesO

    OChrisJonesO

    Joined:
    Jun 27, 2015
    Posts:
    13
    Update:

    I've put all the fixes we've worked on into a simple-to-use WebGLTemplate that you can select in your Player Settings, build out, and not have to worry.

    There are two versions: one with basic responsive canvas size for Gamma, and one for Linear with all the extra fixes in place. Choose whichever one you are building out for.

    If you're doing Linear, you'll need to use the ScreenManager class included in that .zip. Follow the steps above to set that up.
     

    Attached Files:

  12. tswalk

    tswalk

    Joined:
    Jul 27, 2013
    Posts:
    1,109
    well then.. i just needed to switch back to gamma color space for this to stop mucking up the render.

    thanks for the detailed write up... going to go try your screen manager solution for linear.
     
  13. tswalk

    tswalk

    Joined:
    Jul 27, 2013
    Posts:
    1,109
    Only problem here is if you have any dependency to the canvas (e.g: canvasLarge ID in your script with a large CSS property for width and height). Such as any mouse or touch related actions that work directly with panel, image, or other actionable on the canvas. An example I have is an invisible panel which detects mouse pointers in a very specific and confined space that is proportional to the aspect ratio of the panel.

    Arbitrarily setting the canvas to an extreme value messes up any of that capability :(

    It may have something to do with the fact I'm using Webassembly.. but i don't think so.

    Anyway, this is a huge bug in Unity's WebGL exporter/code that seriously needs a fixing.

    Anything that @OChrisJonesO is doing here in this thread is just an attempted workaround for a real issue with WebGL exporter.
     
  14. ANTONBORODA

    ANTONBORODA

    Joined:
    Nov 16, 2017
    Posts:
    52
    Is this going to be fixed? Come on, this bug is 4 versions old already and it's STILL in 2018.2.
    What's the point of having a linear rendering when it's broken?
     
  15. Vapid-Linus

    Vapid-Linus

    Joined:
    Aug 6, 2013
    Posts:
    64
    Still an issue ._.
     
    Toadilein likes this.
  16. Marco-Trivellato

    Marco-Trivellato

    Unity Technologies

    Joined:
    Jul 9, 2013
    Posts:
    1,654
    Working on a fix now... Should be fixed in 2018.3.
     
  17. ForMiSoft

    ForMiSoft

    Joined:
    Dec 5, 2016
    Posts:
    8
    Still not working in 2018.3.0b11.
     
  18. BartholomewIU

    BartholomewIU

    Joined:
    Sep 13, 2012
    Posts:
    2
    Tested on 2018.3.1f1, still not working.
     
    Last edited: Jan 17, 2019
  19. sumpfkraut

    sumpfkraut

    Joined:
    Jan 18, 2013
    Posts:
    242
    it works since 2018.3 (not beta)
     
    stuepfnick, SearchTree and JamesArndt like this.