Skip to content

Commit

Permalink
Fixed render scale issue (google#3795)
Browse files Browse the repository at this point in the history
* updated examples

* reduce targeted framerate

* fixed render rescale issue
  • Loading branch information
elalish authored Sep 10, 2022
1 parent 010b676 commit 801db36
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 116 deletions.
211 changes: 101 additions & 110 deletions packages/model-viewer/src/three-components/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ export interface ContextLostEvent extends Event {

// Between 0 and 1: larger means the average responds faster and is less smooth.
const DURATION_DECAY = 0.2;
const LOW_FRAME_DURATION_MS = 18;
const HIGH_FRAME_DURATION_MS = 26;
const MAX_AVG_CHANGE_MS = 2;
const LOW_FRAME_DURATION_MS = 40;
const HIGH_FRAME_DURATION_MS = 60;
const MAX_AVG_CHANGE_MS = 5;
const SCALE_STEPS = [1, 0.79, 0.62, 0.5, 0.4, 0.31, 0.25];
const DEFAULT_LAST_STEP = 3;

Expand Down Expand Up @@ -172,6 +172,75 @@ export class Renderer extends EventDispatcher {
this.updateRendererSize();
}

registerScene(scene: ModelScene) {
this.scenes.add(scene);

scene.forceRescale();

if (this.canRender && this.scenes.size > 0) {
this.threeRenderer.setAnimationLoop(
(time: number, frame?: any) => this.render(time, frame));
}

if (this.debugger != null) {
this.debugger.addScene(scene);
}
}

unregisterScene(scene: ModelScene) {
this.scenes.delete(scene);

if (this.canvas3D.parentElement === scene.canvas.parentElement) {
scene.canvas.parentElement!.removeChild(this.canvas3D);
}

if (this.canRender && this.scenes.size === 0) {
this.threeRenderer.setAnimationLoop(null);
}

if (this.debugger != null) {
this.debugger.removeScene(scene);
}
}

displayCanvas(scene: ModelScene): HTMLCanvasElement {
return this.multipleScenesVisible ? scene.element[$canvas] : this.canvas3D;
}

/**
* The function enables an optimization, where when there is only a single
* <model-viewer> element, we can use the renderer's 3D canvas directly for
* display. Otherwise we need to use the element's 2D canvas and copy the
* renderer's result into it.
*/
private countVisibleScenes() {
const {canvas3D} = this;
let visibleScenes = 0;
let canvas3DScene = null;
for (const scene of this.scenes) {
const {element} = scene;
if (element.modelIsVisible && scene.externalRenderer == null) {
++visibleScenes;
}
if (canvas3D.parentElement === scene.canvas.parentElement) {
canvas3DScene = scene;
}
}
const multipleScenesVisible = visibleScenes > 1;

if (canvas3DScene != null) {
const newlyMultiple =
multipleScenesVisible && !this.multipleScenesVisible;
const disappearing = !canvas3DScene.element.modelIsVisible;
if (newlyMultiple || disappearing) {
const {width, height} = this.sceneSize(canvas3DScene);
this.copyPixels(canvas3DScene, width, height);
canvas3D.parentElement!.removeChild(canvas3D);
}
}
this.multipleScenesVisible = multipleScenesVisible;
}

/**
* Updates the renderer's size based on the largest scene and any changes to
* device pixel ratio.
Expand Down Expand Up @@ -242,8 +311,35 @@ export class Renderer extends EventDispatcher {
}
}

dispatchRenderScale(scene: ModelScene) {
private shouldRender(scene: ModelScene): boolean {
if (!scene.shouldRender()) {
// The first frame we stop rendering the scene (because it stops moving),
// trigger one extra render at full scale.
if (scene.scaleStep != 0) {
scene.scaleStep = 0;
this.rescaleCanvas(scene);
} else {
return false;
}
} else if (scene.scaleStep != this.scaleStep) {
// Update render scale
scene.scaleStep = this.scaleStep;
this.rescaleCanvas(scene);
}
return true;
}

private rescaleCanvas(scene: ModelScene) {
const scale = SCALE_STEPS[scene.scaleStep];
const width = Math.ceil(this.width / scale);
const height = Math.ceil(this.height / scale);

const {style} = scene.canvas;
style.width = `${width}px`;
style.height = `${height}px`;
this.canvas3D.style.width = `${width}px`;
this.canvas3D.style.height = `${height}px`;

const renderedDpr = this.dpr * scale;
const reason = scale < 1 ? 'GPU throttling' :
this.dpr !== window.devicePixelRatio ? 'No meta viewport tag' :
Expand All @@ -260,111 +356,6 @@ export class Renderer extends EventDispatcher {
}));
}

registerScene(scene: ModelScene) {
this.scenes.add(scene);

scene.forceRescale();

if (this.canRender && this.scenes.size > 0) {
this.threeRenderer.setAnimationLoop(
(time: number, frame?: any) => this.render(time, frame));
}

if (this.debugger != null) {
this.debugger.addScene(scene);
}
}

unregisterScene(scene: ModelScene) {
this.scenes.delete(scene);

if (this.canvas3D.parentElement === scene.canvas.parentElement) {
scene.canvas.parentElement!.removeChild(this.canvas3D);
}

if (this.canRender && this.scenes.size === 0) {
this.threeRenderer.setAnimationLoop(null);
}

if (this.debugger != null) {
this.debugger.removeScene(scene);
}
}

displayCanvas(scene: ModelScene): HTMLCanvasElement {
return this.multipleScenesVisible ? scene.element[$canvas] : this.canvas3D;
}

/**
* The function enables an optimization, where when there is only a single
* <model-viewer> element, we can use the renderer's 3D canvas directly for
* display. Otherwise we need to use the element's 2D canvas and copy the
* renderer's result into it.
*/
private countVisibleScenes() {
const {canvas3D} = this;
let visibleScenes = 0;
let canvas3DScene = null;
for (const scene of this.scenes) {
const {element} = scene;
if (element.modelIsVisible && scene.externalRenderer == null) {
++visibleScenes;
}
if (canvas3D.parentElement === scene.canvas.parentElement) {
canvas3DScene = scene;
}
}
const multipleScenesVisible = visibleScenes > 1;

if (canvas3DScene != null) {
const newlyMultiple =
multipleScenesVisible && !this.multipleScenesVisible;
const disappearing = !canvas3DScene.element.modelIsVisible;
if (newlyMultiple || disappearing) {
const {width, height} = this.sceneSize(canvas3DScene);
this.copyPixels(canvas3DScene, width, height);
canvas3D.parentElement!.removeChild(canvas3D);
}
}
this.multipleScenesVisible = multipleScenesVisible;
}

private rescaleCanvas(scene: ModelScene): boolean {
const {style} = scene.canvas;
if (!scene.shouldRender()) {
// The first frame we stop rendering the scene (because it stops moving),
// trigger one extra render at full scale.
if (scene.scaleStep != 0) {
scene.scaleStep = 0;
style.width = `${this.width}px`;
style.height = `${this.height}px`;
this.dispatchRenderScale(scene);
if (!this.multipleScenesVisible) {
this.canvas3D.style.width = `${this.width}px`;
this.canvas3D.style.height = `${this.height}px`;
}
} else {
return true; // Skip rendering
}
} else if (scene.scaleStep != this.scaleStep) {
// Update render scale
scene.scaleStep = this.scaleStep;
const scale = this.scaleFactor;

const width = Math.ceil(this.width / scale);
const height = Math.ceil(this.height / scale);

style.width = `${width}px`;
style.height = `${height}px`;
if (!this.multipleScenesVisible) {
this.canvas3D.style.width = `${width}px`;
this.canvas3D.style.height = `${height}px`;
}
this.dispatchRenderScale(scene);
}
return false; // Perform rendering
}

private sceneSize(scene: ModelScene) {
const {dpr} = this;
const scaleFactor = SCALE_STEPS[scene.scaleStep];
Expand Down Expand Up @@ -454,7 +445,7 @@ export class Renderer extends EventDispatcher {

this.preRender(scene, t, delta);

if (this.rescaleCanvas(scene)) {
if (!this.shouldRender(scene)) {
continue;
}

Expand Down
6 changes: 4 additions & 2 deletions packages/modelviewer.dev/examples/loading/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ <h4>Our renderer tries to keep the frame rate between 38 and 60 frames per secon
reason.textContent = event.detail.reason;
});

window.addEventListener('DOMContentLoaded', () => {
const setup = () => {
const minScale = document.querySelector('#min-scale-value');
// The static API must be queried after the element loads. Note that static properties affect all the <model-vieweer> elements on the page.
const ModelViewerStatic = customElements.get('model-viewer');
Expand All @@ -430,7 +430,9 @@ <h4>Our renderer tries to keep the frame rate between 38 and 60 frames per secon
ModelViewerStatic.minimumRenderScale = event.target.value;
minScale.textContent = event.target.value;
});
}, {once: true});
};

customElements.whenDefined('model-viewer').then(setup);
</script>
</template>
</example-snippet>
Expand Down
2 changes: 1 addition & 1 deletion packages/modelviewer.dev/examples/scenegraph/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ <h2 class="demo-title">Materials API</h2>
</div>
<example-snippet stamp-to="pickMaterialExample" highlight-as="html">
<template>
<model-viewer id="pickMaterial" shadow-intensity="1" camera-controls touch-action="pan-y" src="../../shared-assets/models/glTF-Sample-Models/2.0/Buggy/glTF-Binary/Buggy.glb" ar ar-modes="webxr scene-viewer" scale="0.001 0.001 0.001" alt="A Material Picking Example">
<model-viewer id="pickMaterial" shadow-intensity="1" camera-controls touch-action="pan-y" disable-tap src="../../shared-assets/models/glTF-Sample-Models/2.0/Buggy/glTF-Binary/Buggy.glb" ar ar-modes="webxr scene-viewer" scale="0.001 0.001 0.001" alt="A Material Picking Example">
</model-viewer>
<script type="module">
const modelViewer = document.querySelector("model-viewer#pickMaterial");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ <h4></h4>
</div>
<example-snippet stamp-to="cameraOrbit" highlight-as="html">
<template>
<model-viewer camera-controls touch-action="pan-y" camera-orbit="45deg 55deg 2.5m" src="../../shared-assets/models/Astronaut.glb" alt="A 3D model of an astronaut"></model-viewer>
<model-viewer camera-controls touch-action="pan-y" camera-orbit="45deg 55deg 4m" src="../../shared-assets/models/Astronaut.glb" alt="A 3D model of an astronaut"></model-viewer>
</template>
</example-snippet>
</div>
Expand All @@ -89,7 +89,7 @@ <h4></h4>
</div>
<example-snippet stamp-to="orbitAndScroll" highlight-as="html">
<template>
<model-viewer camera-controls touch-action="pan-y" camera-orbit="calc(-1.5rad + env(window-scroll-y) * 4rad) calc(0deg + env(window-scroll-y) * 180deg) calc(3m - env(window-scroll-y) * 1.5m)" src="../../shared-assets/models/Astronaut.glb" alt="A 3D model of an astronaut"></model-viewer>
<model-viewer camera-controls touch-action="pan-y" camera-orbit="calc(-1.5rad + env(window-scroll-y) * 4rad) calc(0deg + env(window-scroll-y) * 180deg) calc(5m - env(window-scroll-y) * 10m)" src="../../shared-assets/models/Astronaut.glb" alt="A 3D model of an astronaut"></model-viewer>
</template>
</example-snippet>
</div>
Expand Down Expand Up @@ -233,7 +233,7 @@ <h4>Event listeners can cooperate with camera-controls</h4>
</div>
<example-snippet stamp-to="turnSkybox" highlight-as="html">
<template>
<model-viewer id="envlight-demo" camera-controls touch-action="pan-y" oncontextmenu="return false;" src="../../shared-assets/models/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf" skybox-image="../../shared-assets/environments/spruit_sunrise_1k_HDR.hdr" alt="A 3D model of a damaged helmet"></model-viewer>
<model-viewer id="envlight-demo" camera-controls touch-action="pan-y" disable-pan oncontextmenu="return false;" src="../../shared-assets/models/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf" skybox-image="../../shared-assets/environments/spruit_sunrise_1k_HDR.hdr" alt="A 3D model of a damaged helmet"></model-viewer>

<script type="module">
const modelViewer = document.querySelector("#envlight-demo");
Expand Down

0 comments on commit 801db36

Please sign in to comment.