What does render exactly do and why not calling it still seems to use perfs?

Hello everyone,

I’m working on a project where I have to make every single stones of a huge historical building interractable.
This part is mostly solved, now i’m trying to make it so my phone doesn’t get too hot when using the app, and not wasting resources or battery life when nothing move on screen.

So i thought of not calling
renderer.render(scene, camera)
when the camera has not moved, thinking it would result in a basically cost free animation frame (since not rendering).
Yet i see my Stat.js is still not showing 60fps, as if some heavy rendering and computing was still occuring.

Is the canvas element still rendering / updating every frame somehow even when not calling three.js render ?

Just in case, this is my “animate” function equivalent :

  update() {
    if (!this.inited) return

    if (this.debugMode) {
      this.stats.begin()
    }

    if (this.controls) {
      this.controls.update()
    }

    TWEEN.update()

    this.render() // here i just call renderer.render(scene, camera) if camera has updated, i know it works ,  logging stuff when camera moving only as expected.

    if (this.debugMode) {
      this.stats.end()
    }

    window.requestAnimationFrame(this.update)
  }

While searching the net for an explanation I stumbled uppon, this forum on a related toopic where someone suggested to render the canvas to an image , but i can’t do that, i still need to render as soon as the user interact with the 3D view i guess disposing and restoring context is quite slow, and the interraction with 3D view in my app are quite frequent.

I thought once i called render() the framebuffer was computed and basically free to show every animationFrame, it doesn’t seems to be the case, am I doing something wrong ?

Thank you !

My guess is that you are tweening a lot of things

Nope. Rendering (and all logic required to do it) is executed only when you call render method - so indeed, your approach is correct, not calling render when there’s no movement or update in the scene is a good optimisation in fitting scenarios.

Shouldn’t be (unless you mean you’re actually disposing the context manually someway - and then recreate by creating a new renderer on each frame - then that could be expensive indeed.)

But if that’s the case, disabling rendering can cause annoying frame stutters when user is interacting with the scene.

For mobile devices (esp. not the newest ones), first thing to make sure is setting renderer pixelRatio always to 1.0. Besides that - just try to optimise your scene, rendering ultra-realistic models by bruteforcing them onto the scene can rarely work, especially for mobile. Hide things that are not visible, scale down the textures to max. 2048 (and ofc lower, for smaller objects.) etc.

Skipping rendering is good, but the your animation loop is still running.

Have you tried a more brutal approach → when there is no change, stop calling requestAnimationFrame. Start calling it again if there is a change or if there is some user interaction (e.g. PointerDown event).

1 Like

Something looks fishy in that animation loop.

If there is an early exit on “inited” then how does the render loop ever get restarted, and if there is something periodically trying to restart it, I would suspect there may be multiple RAFs running.

I would remove the if(!this.inited) check and ONLY start the render loop when you know the app has been initialized, and then only start it once.

It should look more like:

update() {
    window.requestAnimationFrame(this.update) //Call this early so that you're immediately rescheduled, rather than waiting until you are done rendering to reschedule... 

    this.debugMode && this.stats.begin();

    this.controls && this.controls.update();

    TWEEN.update()

    this.needsRender && this.render() // here i just call renderer.render(scene, camera) if camera has updated, i know it works ,  logging stuff when camera moving only as expected.

    this.debugMode && this.stats.end();
  }

Or…
Instead, consider using the

renderer.setAnimationLoop( ()=>this.update() )

method at the end of your initialization instead… It handles the calling of RAF for you, and should prevent accidentally firing up multiple RAFs…

Thank you for your quick and detailed answer, then I guess something else is happening in my animation loop.

Yeah, that naive approach worked for the 150 first stones but when I received the 15000 others the POC fell appart and hardly reached 1 fps on a gaming computer, so I remeshed the stones into ~ 80 chunks and baked everything I could on diffuse map + a id map for picking, which now run smoothly on most recent mobile devices, i’m just trying to make the experience on lower end device more enjoyable and preserve battery life a bit.