Animation limps

I am really enjoying Threejs. It’s powerful and easy. I am developing an exporter from my OpenGL macOS app to Threejs. Everything seems to go well. But I have still some little trouble.

I load my scene and play the animation at 60 FPS. Sometimes it plays fluently, sometimes the playback limps loosing some frame. In this latter case, I reload the HTML page and sometimes it plays fluently, sometimes I need to reload the HTML twice or three times to get it play fluently.

What could be the cause?

Info:
The animation contains few objects and small textures.
I have been able to play heavier animations, with the same little trouble (fluent/limping).
The original animation on my OpenGL macOS app always plays fluently.

I load some geometries with

geometry.setAttribute(‘position’…
geometry.setAttribute( ‘normal’…
geometry.setIndex(…

I load some materials and textures with
material = new THREE.MeshPhongMaterial(…
const textureLoader = new THREE.TextureLoader();
texture = textureLoader.load(imagePath);

I load a couple of static THREE.PointLight

  1. Could you share some code / preview? It’s easier to help this way, in case something’s not right there.
  2. If animations lose frames / stutter only occasionally - try to delay playing them until after you’re 101% certain the model is loaded and its materials compiled (you can also pre-compile them yourself.) Compilation does sometimes cause rendering to stutter.
1 Like

Thank you mjurczyk.

I have put a control over the texture loading so now I create the renderer only after all the textures have been loaded. It works. Now the animation always plays fluently.

every time I have to load a texture I set
totImageTextures++;

then I load the texture

var imageTexture = textureLoader.load
(
// resource URL
imagePath,

// onLoad callback !!!
function(texture)
{
	totImageTextures--;
	CheckIsReadyToPlay();
},

// onProgress callback currently not supported
undefined,

// onError callback
function(err)
{
	totImageTextures--;
	CheckIsReadyToPlay();
}

);

function CheckIsReadyToPlay()
{
if(totImageTextures == 0 && fileLoaded)
{
renderer = new THREE.WebGLRenderer…
}
}

Do yo think I have to check also when all the geometries and materials have been loaded?

Personally I’d pre-compile the materials (or display the model to let three compile them for you) before playing the animation - material compilation can cause a bit of a frame-rate drop.

But if it works right now, it just as well might not be necessary :blush:

I think I spoke too earlier. The small animations, with a few objects and textures play fluently. The big animations (with many objects) still stutter sometimes.

I noticed that if I initiate the renderer from within a setTimeout with a 1 second delay, the animation plays better, sometimes. Sometimes not. So it’s a matter of loading or it comes from the implementation: my implementation or Three.js implementation. We are going to discover that.

• How could I pre-compile the materials? Is a loading function callback on the material too?
• What do you mean by “display the model”? Just draw it on the screen?

Does the animation loop and is it fine after it’s played a few loops?

The reason I ask is, when threeJS renderer sees a material for the first time it will compile the shaders to render that material. This can cause the computer to “freeze” during compilation.

So if it is pausing on first play through, but then later is fine, it’s probably because of this shader compilation. To combat this I usually set the camera to view the whole scene and render it at start up. Then all the shaders will be compiled before you start looking at things.

You do not need to show what is rendered when you are doing a pre-render like this, there is a method under the renderer called .compileShaders( camera, scene ) (forgive me if I get the params backwards I am going from memory) which will do the shader compiling without actually updating the buffers; but you do need to make sure the camera can see the whole scene.

When the animation stutters, it stutters forever, not only on the first frames. Then I reload the HTML page and sometimes it plays fluently, sometimes it still stutters, so I clean the browser cash, I reload the HTML page again and again until it fluently plays.

I tried to do:

// added all the objects to the scene

renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setPixelRatio(window.devicePixelRatio);
mainDiv.appendChild(renderer.domElement);
renderer.compile(scene, camera); // I get error null is not an object

but on the latest line I get the error

[Error] TypeError: null is not an object (evaluating 'currentRenderState.state')
initMaterial (three.module.js:24597)
(anonymous function) (three.module.js:24197)
traverse (three.module.js:6832)
traverse (three.module.js:6838)
traverse (three.module.js:6838)
traverse (three.module.js:6838)
traverse (three.module.js:6838)
(anonymous function) (three.module.js:24183)

so I removed the latest line and I tried

mainDiv.appendChild(renderer.domElement);
renderer.render(scene, camera);
renderer.clear();

but I get no improvement. I get still the same random stutter.

hmmm, what you describe is beginning to sound more like some kind of memory leak or recursive function running away with itself, rather than an issue in the 3D render pipeline, or perhaps some issue in the delta being calculated for the animation mixer. Could you post the content of your equivellent to the animate() function? The code that handles your render-loop.

My suspiscion is that either there’s something wrong with the way you’re reading the delta for the mixer, or that there’s some kind of recurssive function call.

It might be an idea to get the Task Manager open - the one inside your browser (both Chrome + Firefox have one in their More Tools menus) and see if the CPU/RAM starts running away during runtime.

I run on macOS 10.15 Catalina.

I reloaded the HTML page with the animation on Google Chrome Version 88.0.4324.96 (Official Build) (x86_64) and it played flawlessly 8 times on 10.

On Safari Version 13.1.3 (15609.4.1) it played fluently just 6 times on 10. If I clean the cash then quit then relaunch, it plays the same way.

But those numbers are random. I can’t say it’s always 8/10 on Chrome and 5/10 on Safari.

This is my animation function

/////////////////////////////////////////////////////
function PlayAnimation(objects, absFrame)
/////////////////////////////////////////////////////
{
var i, totObjs = objects.length;

for(i = 0; i < totObjs; i++)
{
	// obj is a json containing a mesh, positions, rotations… if obj is a group (a folder), it contains its children
	var obj = objects[i];
	
	// if the object on the timeline should be rendered
	if(absFrame >= obj.start && absFrame <= obj.end)
	{
		// The obj.mesh is the one we added to the scene with scene.add(obj.mesh);
		// We created with: mesh = new THREE.Mesh(mergedGeometry, materials);
		obj.mesh.visible = true;
		
		// obj.mDynamic is an array containing *only* the parameters we have to animate,
		// so maybe just one parameter as kPosX or e.g. six parameters as kPosX, kPosY, kPosZ, kRotX, kRotY, kRotZ…
		// We previously did set the object static parameters just once, when we loaded the object.				
		var dynParams = obj.mDynamic; 
		
		if(dynParams) // if the object contains dynamic parameters as e.g. positions and rotations, we have to animate it
		{
			// the obj's frame (objFrame) is the absolute current frame (absFrame) - the obj's start time on the timeline
			var objFrame = absFrame - obj.start;
			
			for(const [key, dynParam] of Object.entries(dynParams))
			{
				switch(key)
				{
					case kPosX:
						obj.mesh.position.x = dynParam[objFrame];
					break;
					
					case kPosY:
						obj.mesh.position.y = dynParam[objFrame];
					break;
					
					case kPosZ:
						obj.mesh.position.z = dynParam[objFrame];
					break;
					
					// and so on…	
				}	
			}
		}
		
		if(obj.children)	// in case of group we iterate its children
		{
			PlayAnimation(obj.children, absFrame);
		}		
	}
	// if the object on the timeline should not be rendered
	else{
		obj.mesh.visible = false;
	}
}

}

And this is my animate() function:

const fps = 60.0;
const fpsInterval = 1000 / fps;
var now, elapsed;
var then = Date.now();
var startTime = then;
var mAnimationFrame = 0;
var mObjects = []; // an array containing all the objects (json) loaded.

////////////////////////////////////////////////////////////////
function animate()
////////////////////////////////////////////////////////////////
{
requestAnimationFrame(animate);

now = Date.now();
elapsed = now - then;

// if enough time has elapsed, draw the next frame
if(elapsed > fpsInterval)
{
	then = now - (elapsed % fpsInterval);
	
	PlayAnimation(mObjects, mAnimationFrame);
    mAnimationFrame +=1;
   
	renderer.render(scene, camera);
}

}