Symptoms
- The app content does not appear as I want on Retina display devices when I use the WebGL build.
- The Canvas content appears pixelated in WebGL.
- The Canvas content looks weird (stretched or squashed).
Cause
The browser will increase the size of the Canvas automatically, but its content will keep its size, showing some anti-aliasing errors.
Resolution
A Canvas element in HTML has two different sizes properties and we should use them to adapt our content to High Resolution Screens (Retina included).
- The Canvas size is the size the Canvas element will be displayed on your screen (on the page), and we can change this value using CSS attributes width & height:
<canvas id="CanvasTest" style="width: 400px; height: 300px;" ...></canvas>
- The Canvas Contents size is how many pixels are in the Canvas, and we can modify this value changing the Canvas attributes width and height (in pixels). This size is called drawingBuffer.
<canvas id="CanvasTest" width="100" height="150" ...></canvas>
If you do not use the CSS properties to set the Canvas Size, the Canvas size will take the drawingBuffer size, so in the second example, the Canvas Content size is set to 100x150 (px), and the Canvas size will be set to 100x150 (px) as well.
Setting the Canvas size and fixing the Canvas Content size
We can use these properties to set our content as needed, for example, we can change the Canvas size to fill all browser size using this:
<style>
body
{
margin: 0;
border: 0;
padding: 0;
background-color: white; // It depends of your project.
}
canvas
{
width: 100%;
height: 100%;
position: absolute;
}
</style>
We can set the app to full-screen using this code in a callback triggered by some Input event in C# code
void OnClickEvent ()
{
Screen.fullScreen = true;
}
Please check these links to get more details of this:
- https://docs.unity3d.com/Manual/webgl-cursorfullscreen.html
- https://docs.unity3d.com/ScriptReference/Screen-fullScreen.html
To make the Canvas Content size match with the Canvas size, we use the properties clientWidth and clientHeight and assign the width & height Canvas properties like this:
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
We can use this snippet in a resize callback in JS.
Handling the DPI property on Retina displays.
Finally, we have some cases where our app should run on Retina display devices such as the Macbook Pro with Retina display or mobile devices with Retina display. On these cases, we have to manage the High Pixel Densities (DPI). Unity does not have an internal option or setting to set this property, but we can fix it using JS.
The browser will increase the size of the Canvas automatically, but its content will keep its size giving us some anti-aliasing errors. To fix that, we can use the property devicePixelRatio (window.devicePixelRatio) to know how many real pixels that fill 1 CSS pixel and change our resize callback function to something like:
canvas.width = canvas.clientWidth * window.devicePixelRatio;
canvas.height = canvas.clientHeight * window.devicePixelRatio;
With these two lines, the content will match with our high-resolution device (even Retina) and your content will look as intended.
More Information
https://docs.unity3d.com/Manual/webgl-cursorfullscreen.html
https://docs.unity3d.com/ScriptReference/Screen-fullScreen.html
Comments
12 comments
Hi,
When I do this :
canvas.width = canvas.clientWidth * window.devicePixelRatio;
canvas.height = canvas.clientHeight * window.devicePixelRatio;
On a screen with Windows scaling of 200% (so the window.devicePixelRatio is 2), the app is displayed only on the lower-left quarter of the canvas space, and the rest of the canvas is black.
Did it also happen to you, and do you have any idea of how to fix this?
Hi François,
Have you found a solution to your problem as I am experiencing the same thing. Thanks
Regards,
Clayton
when i change width and height , well also change style.width and style.height
what it so?
This solution is no longer usable and Unity still doesn't support retina on WebGL.
You can also add this style to the body tag on index.html:
<body style="image-rendering: pixelated">
...
</body>
This will keep the size as is but prevent blurred pixels. Good for pixel art.
This answer is not correct it doen't work at all. Every time when you cahnge canvas width/height, or css width/height unity will recreate canvas buffer with last width&height. Unity do it through Browser.setCanvaSize from emscripten.
https://issuetracker.unity3d.com/issues/webgl-builds-dont-allow-separate-control-on-canvas-render-buffer-size
It's fixed in 2019.3 beta.
It's not entirely fixed. It's now hard coded to support retina screens, but that's not solving the problem. The problem is now the opposite:
You don't always want retina support. Some devices are 8x resolution and that creates a massive canvas. Even on a MacBook some webgl games are simply too heavy for the 2x retina resolution. Now if you try to modify the canvas width / height, then unity fights back and keep the retina resolution.
There's literally no way to disable retina (or even limit an 8x device to only render 2x resolution), so no the problem isn't solved.
Ok, I figured out a CSS hack to trick Unity into lowering the resolution.
This will lower a 2x screen to render in 1x resolution.
#canvas {
width: 50% !important;
height: 50% !important;
transform: scale(2);
transform-origin: 0 0;
}
Basically unity is looking at the size of the webgl canvas to determine the resolution of the game, so if we half the canvas size, the resolution will be halved too.
Then we upscale the canvas again and make sure to scale from the top left origin of the canvas.
This upscale shouldn't have a significant bad impact on performance.
Now combine it with a detection of pixel density in javascript and set the css accordingly, so if you want an 8x screen to only render at 2x, then set the width / height to 25% and the scale to 4.
Just checked in 2019.3beta10 nothing is changed, unity does not allow chages of canvas style width & hegiht. It will also change width & height of render buffer.
This is original output (style: 300x300, canvas: 300x300):
This is squished (style: 150x300 canvas: 300x300):
This is stretched (style: 900x300 canvas: 300x300):
As you can see sphere stays the same but should be stretched or squished.
For me K reply did the trick. My game is a 2D game (not pixel art) and everything looked a bit blurry. Added in the body tag this:
<body style="image-rendering: pixelated">
and just like magic everything looked nice and crisp :)
Please sign in to leave a comment.