Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[USAGE TIP] Workaround for low perceived quality on devices with high DPI #46

Open
tmarti opened this issue Apr 8, 2019 · 14 comments
Open

Comments

@tmarti
Copy link
Contributor

tmarti commented Apr 8, 2019

On devices where window.devicePixelRatio returns a value greater than 1 , there is a visual reduction of perceived quality on the 3d rendering.

This happens on most mobile devices (pads and phones), where the canvas size as calculated by the viewer/scene/canvas/Canvas.js class can have a much lower pixel count than what's the phyisical pixel count of the canvas, due to DPI scaling by the browser.

One simple solution seems to be allowing to externally provide the desired devicePixelRatio (ideally taken from window.devicePixelRatio) when creating the instance of the Viewer class and propagate it all the way down to the Canvas class (falling back to 1, for example) so that it can take into account that "scaling factor" when setting the canvas width/height and the viewport bounds.

I've did a quick test and that seems to have the effect of now having nice sharp edges on the mobile viewer (both tablet and phone), although I still have some issues with it (e.g. performance picking seems to suffer a clipping efffect (it only works within a region of the viewer canvas)).

If you want, I can create a PR with those changes (propagating the devicePixelRatio down to the Canvas) so that we can collaborate further in solving this 😃

@xeolabs
Copy link
Member

xeolabs commented Apr 11, 2019

Would it be possible to encapsulate this within Viewer so that it's transparent to the user?

@tmarti
Copy link
Contributor Author

tmarti commented May 1, 2019

Hello 🙋‍

I managed to solve the problem with performance picking 😀.

The idea, as you @xeolabs suggest is to provide a new devicePixelRatio parameter to the configuration options during Viewer creation, which will fallback to 1.0 if not provided (this is, same behaviour as without this change), and from there automatically propagayted to where it's needed. This will avoid introducing and additional dependency to the window object inside xeokit's code.

What I will try to do now is to implement it with a setter/getter, as this could then act as a dynamically adjustable "render quality" switch (although only for devices where window.devicePixelRatio > 1).

I did some quick tests with its performance, and for example on an iPad with retina display where window.devicePixelRatio is 2 (this means rendering as much as 2*2 times the number of pixels), the freame rate drop is only about 30% but with much finer perceived render detail (the render then does not seem "downsampled").

Just stay tuned, I will try to send a PR for this one at the end of this week 😀.

@david-San
Copy link

Hi! I would like to know if this functionality that tmarti describes has been implemented into XeoKit. Or how to implement it if it is not. Could anyone point me in the right direction?

@tmarti
Copy link
Contributor Author

tmarti commented Oct 11, 2019

Hey @david-San, I had this code prepared somewhere but within our private fork of xeokit, so I can't really share the code.

BUT the idea was to propagatewindow.devicePixelRatio all the way down from the scene creation into the canvas.

Things that I remember to take into account:

  • all rendering target surfaces must be scaled according to that parameter
  • the canvas resize listener also needa to take into account the parameter
  • ray picking also needs to take into accpubt that parameter

I think those were all important aspects to take into account to have nice sharp edges :)

@tmarti
Copy link
Contributor Author

tmarti commented Dec 4, 2019

Just found a really easy solution to this problem, tested on Chrome, Firefox and Safari:

<style>

body {
    position: absolute;
    margin: auto;
    transform-origin: top left;
}

#xeokit_canvas {
    width: 100%;
    height: 100%;
}

</style>

<body id="the_body">
    <canvas id="xeokit_canvas"></canvas>
</body>

<script>

the_body.style.transform = "scale(" + (1/window.devicePixelRatio) + ")";
the_body.style.width = (window.devicePixelRatio*100) + "%";
the_body.style.height = (window.devicePixelRatio*100) + "%";

</script>

The point is that by default the viewport is downscaled according to DPI scaling (window.devicePixelRatio).

So if for example that factor is 2.5, the viewport is downscaled by that number... then the trick is to upscale the body by that factor 😉 (adjusting width and height). Doing that alone would not improve the pixel density (even worse, would introduce scrollbars), and for this reason we apply a visual downscale with the same factor (transform.scale=...).

We need a combination of CSS styling and JS code because the devicePixelRatio value is only known to the Javascript engine (I believe it cannot be accessed and used to style the body from CSS).

Enjoy! 😃

@david-San
Copy link

Hi tmarti! Thank you so much for sharing this.

This is excellent! I tried it and it works. I can now get a very high resolution rather than the grainy effect.

The only thing that I have not found a solution when implementing your code is that everything on the screen looks very small since text is following the window.devicePixelRatio change.

Do you have any ideas how to overcome this problem? Changing the font size to huge seem like the evident solution but I am not sure if there is a more elegant solution.

@tmarti
Copy link
Contributor Author

tmarti commented Dec 9, 2019 via email

@xeolabs xeolabs changed the title Low perceived quality on devices with high DPI Low perceived quality on devices with high DPI (with workaround) Aug 7, 2020
@AXONE-IO
Copy link

Is there a more recent solution ?
The CSS transform scale adds a lot of latency during the manipulation of the 3D model.

@AXONE-IO
Copy link

I just found this :
this.viewer.scene["canvas"].resolutionScale = window.devicePixelRatio;

@xeolabs xeolabs changed the title Low perceived quality on devices with high DPI (with workaround) [USAGE TIP] Workaround for low perceived quality on devices with high DPI Apr 20, 2024
@NiklasPor
Copy link

NiklasPor commented Nov 26, 2024

The reason I didn't even bother trying out xeokit was that it just looked so blurry on my M3. Would be nice if this would be fixed by default (in the examples?)

@xeolabs
Copy link
Member

xeolabs commented Nov 27, 2024

The reason I didn't even bother trying out xeokit was that it just looked so blurry on my M4. Would be nice if this would be fixed by default (in the examples?)

Hi, do you mean blurry after applying the workaround mentioned above?

Could you provide a code sample of how you've set up the Viewer / HTML / CSS?

PRs always welcome of course.

@NiklasPor
Copy link

I've got this issue on xeo.vision and on any hosted example. The screenshots are from https://xeokit.io/demo.html?projectId=MAP

Default look (in miniature this looks fine, but its just a bit blurry / unsharp if I'm using this full screen to replace ifc.js):
image

Zooming out in the browser window seems to resolve the issue:
image

Zooming in makes it worse:
image


I'll try setting the resolution scale myself, but as I said this discouraged me from using it at all initially (and probably not only me, because everyone looks at the visuals in the first).

@NiklasPor
Copy link

So I think I figured it out, like pointed out above it would look better if the the resolutionScale would be set to devicePixelRatio by default this will fix the blurry look on smaller high resolution screens (like the MacBook display).

But this won't work with the FastNavigationPlugin because you'll need to set it there too. So here's an example:

Blurry rendering with default:

image
// Default navigation plugin from rachouse
new FastNavPlugin(viewer, {
  hideEdges: true,
  hideSAO: true,
  hideColorTexture: false,
  hidePBR: false,
  hideTransparentObjects: false,
  scaleCanvasResolution: false,
  scaleCanvasResolutionFactor: 0.5,
  // The defaultCanvasResolutinoFactor is actually 0.6
  // This leads to the blurry image because some displays have a devicePixelRatio of 2
  // defaultScaleCanvasResolutionFactor: 0.6
  delayBeforeRestore: true,
  delayBeforeRestoreSeconds: 0.4,
});

Sharp rendering with customized with devicePixelRatio:

image
// Adjusted navigation plugin form rachhouse
new FastNavPlugin(viewer, {
  hideEdges: true,
  hideSAO: true,
  hideColorTexture: false,
  hidePBR: false,
  hideTransparentObjects: false,
  scaleCanvasResolution: true,
  // Scale down while navigating
  scaleCanvasResolutionFactor: devicePixelRatio * 0.5,
  // Always high quality image when still
  defaultScaleCanvasResolutionFactor: devicePixelRatio,
  delayBeforeRestore: true,
  delayBeforeRestoreSeconds: 0.4,
});

Proposal: Maybe change the defaults for the examples & plugins to respect the devicePixelRatio? Otherwise xeo appears low-quality in comparison with the other rendering platforms (ifc.js, regular three).

Anyway no worries for me, as I can work with this 👍

@MichalDybizbanskiCreoox
Copy link
Collaborator

Hi @NiklasPor,

Thank you for the report and detailed investigation into the issue.
It’s definitely a good idea to make the better quality renders easier to achieve for the SDK users.

We have just implemented a change to how the FastNavPlugin reverts from the temporary “low quality” settings back to “high quality” (or “regular”) settings.
As you had noticed, the switch used to depend on the defaultScaleCanvasResolutionFactor constructor parameter, which - if not aligned with the Canvas::resolutionScale - would “override” it, and so required extra effort and knowledge to keep in line.
On top of that, in an absence of defaultScaleCanvasResolutionFactor, the FastNavPlugin would default to 1.0.
The current change, as implemented by a PR at #1760, and already merged to the master branch, changes the “revert” value to use in an absence of defaultScaleCanvasResolutionFactor to the original resolutionScale of the Canvas, which we think might be closer to users' expectations.

What this change does not do however, is it doesn’t change the default value of Canvas::resolutionScale.
Such a change of a default is not inconsequential to the users, and we’ll have to internally discuss and consider its potential impact.
For the time being then, with this current change, the SDK still requires setting Canvas::resolutionScale if different than current 1.0 default, but this value is retained by FastNavPlugin, as long as not overwritten by defaultScaleCanvasResolutionFactor.

The change will be included in an upcoming release, looking forward to your feedback.
Best,
Michał

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants