Parameter for animate function to check whether all objects are rendered in 3d and then take screenshot

Hi all,

I am trying to figure out how would we make it possible to execute some function after renderer.render is called and all objects are rendered in 3d.

i have one event called toggle-3d and i am checking whether that event has some formData, i will take that and then take screenshot from 3d canvas and attach it to formData and send request to sever to save the 3d image of product.

But the issue is that requestAnimationFrame returns the id so i am unable to pass parameter to animate function because i need to ensure that 3d has generated otherwise wrong screenshot is being captured
|

function animate(callback) {
    requestAnimationFrame(animate);
   
    // hre callback refers to the id which was returned by requestAnimationFrame above
    if (callback) {
        callback();
    }
}

and here is event where formData should be captured and processed after 3d has been generated completely. 3d view in ui is only made visible when this event is run

window.addEventListener('toggle-3d',function(event) {
      //some code for scene modifications like removal and adding new objects etc...
     scene.add(cylinder);
      
    animate(() => {
    if(event.detail.formData) {

        const formData = event.detail.formData;

        const canvas = renderer.domElement;

        canvas.toBlob(function(product3dImageBlob) {
            // here code will be to send file from the blob to server
        }
    }
});
   
})

i can not pass callback to animate like above. How we can ensure a whole 3d is rendered and all objects are generated so we can capture screenshot when toggle-3d event is run?

You could try to pass the parameter through a global variable.

put some global variable after the Renderer. to not call the renderer again until you get a shot of the screen, because by default renderer.render self clear buffer at end.

put a shot funcion inside a timetout, 500ms its a good time

after shot sucess, restore global variable to allow render again.

Hi Thank you for the answer but still i am not sure even after taking global variable how would i get the data which i am supposed to get on toggle-3d event’s event.detail.formData because that;s where i need to attach 3d screenshot.

window.addEventListener('toggle-3d',function(event) {
      //  event.detail.formData is available here
      toggled = true;
})

function animate() {
    requestAnimationFrame( animate );
    controls.update();
    renderer.render( scene, camera );
    if(toggled) {
      //  i also need event.detai.formData which was emitted on toggle-3d event to attach 
      //  screenshot 
    }
}

You don’t have to call renderer.render from inside the RAF…

You could do something like:

let glb = await (new THREE.GLTFLoader()).loadAsync( "YourModel.glb" );
scene.add( glb.scene );
renderer.render(scene,camera);
renderer.domElement is now your canvas with the rendered frame.

Side note: sometimes when capturing canvas, you won’t catch it in the right state before it has been cleared again…
you can prevent this issue by passing:

 preserveDrawingBuffer: true

in the renderer constructor options.

@manthrax How does this solution apply to the query here? because i saw you answered about .glb somewhere else in another question.

Here i just need to figure out when canvas is successfully converted to 3d and then capture screenshot.

Its “converted” when you call renderer.render

If you’re not seeing the output in the canvas, then you’re trying to do it before you scene is getting built properly… or there is a problem with preserveDrawingBuffer

I’m pointing out that you can just do all the scene creation + rendering → canvas in one shot. No need to synchronize with the RAF loop or that stuff if you just need a rendered screenshot.

Usually when people have this problem its because they using a loader to load their Meshes…
Then they start the animation loop, and try to take the snapshot, but the loader hasn’t finished yet so the objects to be rendered haven’t yet been added to the scene, and you get an empty snapshot back. Or, they try to drawImage with the canvas at the wrong time and get bitten by the preserveDrawingBuffer issue. (The browser will quickly empty the storage for a canvas after displaying it, to allow it to reuse the framebuffer for other stuff.) If you set preserveDrawingBuffer:true on the renderer constructor, it stops that browser behavior, and ensures that you will always have the complete image in your canvas. ( your canvas gets its own dedicated framebuffer that wont get cleared/re-used).

So…

I’m saying you can skip all that and just load and render your stuff in a linear fashion.

Ah okay now i got what you mean, but if i stop RAF loop would i be able to rotate and do all operation on rendered 3d? because I also need those as well after it has been rendered, not just screenshot. but once it is rendered then only ss should be captured

You don’t have to stop the RAF loop. You can call renderer.render( whenever/whereever you want.
For instance directly in your “take snapshot” button click handler.
onclick = ()=>{
//generate the snapshot
renderer.render(scene,camera);
//now renderer.domElement contains the frame image
}

1 Like

@manthrax Thank you so much, i just tested your solution and it worked :slightly_smiling_face: I am not sure how because even after i get renderer.domElement, there is this toBlob which is async as well and i did not get if this has anything to do with the exact moment when it has exactly present in 3d and then image.

like i put just renderer.render before renderer.domElement.toblob like this

renderer.render(scene,camera);
const canvas = renderer.domElement;

canvas.toBlob(function(product3dImageBlob) {
    formData.append('product3dImage', product3dImageBlob, formData.get('configuratorId') + '-product3dImage.png');

    // and here product3dImage works correctly
})

but i will test and let you know if there is any problem. thanks again :slightly_smiling_face:

1 Like