See the accepted answer. How could I call renderer.render( scene, camera ); in the javascript side to force rendering to the drawing buffer?
Basically, any way to call this function from the javascript side?
Jukka has been on leave from Unity since early summer. Better tag someone active in Unity.
@brendanduncan_u3d Can you help? Please, I need to know how to force a draw call in js, similar to how it’s done with threejs. What would be the equivalent for Unity WebGL on the javascript side?
I’m not entirely clear on what you’re asking for. You can call Unity code from JS, Unity - Manual: Interaction with browser scripting (Calling Unity scripts functions from Javascript).
@brendanduncan_u3d Sorry if I wasn’t clear. I want to be able to force a draw call on a jslib or jspre file. Please see this link. It’s the first solution. I don’t know how to do it. What is the command I can use to have the effect of drawing to the buffer?
@brendanduncan_u3d Perphaps one of th webgl devs knows how to do it? I’m sure it’s possible. I just need to know in the webgl.framework.js, what function or whatever is used to draw to the buffer?
Ok, so my take on what you’re asking for is to be able to grab render from the canvas during a time when Unity isn’t in the middle of a render so the canvas hasn’t been cleared, and you do not want to include any C# code in the project itself, you want to do it entirely from JS.
In that case, you could just wait for the beginning of the canvas’s refresh cycle using JS’s requestAnimationFrame. If the project is using default framerate, it would also be using requestAnimationFrame to trigger the frame updates and render.
Something like:
canvas.addEventListener("click", function() {
const saveBlob = (function() {
const a = document.createElement('a');
document.body.appendChild(a);
a.style.display = 'none';
return function saveData(blob, fileName) {
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
};
}());
requestAnimationFrame(function() {
canvas.toBlob((blob) => {
saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
});
})
});
@brendanduncan_u3d Thanks, but I tried requestAnimationFrame, and it doesn’t work 100% of the time(although it works better than without, but it needs to be 100%). I really need to force a canvas draw like the examples I showed.
There’s really not anything in framework.js you could use from pure JS, there’s no “present buffer” command and there’s no force draw call.
You could go real hacky and start hijacking the canvas functions. The WebGL context is gotten from context.getContext("webgl2")
. One function that happens at the very beginning of a frame is context.isContextLost. So you could hijack context.isContextLost, it will likely be at the beginning of the frame prior to it having been cleared by Unity. However, preserveDrawingBuffer is false so the browser will have cleared the canvas, so you need to hijack canvas.getContext too, and force the preserveDrawingBuffer to be true. Of course, if you start poking around at this level, you’ll be breaking your warranty
var saveBlob = (function() {
var a = document.createElement('a');
document.body.appendChild(a);
a.style.display = 'none';
return function saveData(blob, fileName) {
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
};
}());
// Hijack canvas getContext to force preserveDrawingBuffer to be true so the
// browser doesn't clear.
var origGetContext = canvas.getContext;
canvas.getContext = function(name, options) {
options = options || {};
options.preserveDrawingBuffer = true;
return origGetContext.call(canvas, name, options);
};
// Hijack isContextLost since it will likely be the first command in the Unity rendering process.
var ctx = canvas.getContext("webgl2");
// Use doScreenShot as a flag to indicate we want to do a canvas grab
var doScreenShot = false;
var origIsContextLost = ctx.isContextLost;
ctx.isContextLost = function() {
if (doScreenShot) {
doScreenShot = false;
canvas.toBlob((blob) => {
saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
});
}
origIsContextLost.call(ctx, ...arguments);
};
canvas.addEventListener("click", function() {
doScreenShot = true;
});
@brendanduncan_u3d I see, thank you. That’s very helpful. You are right, this seems too much. It would probably break something. But I will try. I have 2 questions
- How can I restore everything after the screenshot?
- Is it canvas.getContext(“webgl2”); even if the browser is under webgl1? How do I get the correct context if it’s webgl1 and not 2? How can I know?
- You can just put the origIsContextLost and origGetContext back.
- You can put the hijacking of isContextLost into getContext, to make it work for whatever context is requested.
Here’s an updated (though now I haven’t tested it):
var saveBlob = (function() {
var a = document.createElement('a');
document.body.appendChild(a);
a.style.display = 'none';
return function saveData(blob, fileName) {
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
};
}());
// Use doScreenShot as a flag to indicate we want to do a canvas grab
var doScreenShot = false;
var origIsContextLost = null;
// Hijack canvas getContext to force preserveDrawingBuffer to be true so the
// browser doesn't clear.
var origGetContext = canvas.getContext;
canvas.getContext = function(name, options) {
// If it's a webgl1 or webgl2 context...
if (!origIsContextLost && name.includes("webgl")) {
options = options || {};
options.preserveDrawingBuffer = true;
var ctx = origGetContext.call(canvas, name, options);;
// Hijack isContextLost since it will likely be the first command in the Unity rendering process.
origIsContextLost = ctx.isContextLost;
ctx.isContextLost = function() {
if (doScreenShot) {
doScreenShot = false;
canvas.toBlob((blob) => {
saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
});
// Restore everything to it's original state
ctx.isContextLost = origIsContextLost;
canvas.getContext = origGetContext;
}
return origIsContextLost.call(ctx, ...arguments);
};
return ctx;
}
// Not a webgl context, or isContextLost has already been wrapped
return origGetContext.call(canvas, name, options);
};
canvas.addEventListener("click", function() {
doScreenShot = true;
});
@brendanduncan_u3d How about this method? This wait for end of frame code in C#, there must be something going on in the javascript side right? How can the C# know to wait for the end of frame? Maybe the answer is on the javascript bindings for that function!
No, sorry, there isn’t anything on the javascript side. Besides requestAnimationFrame, there is nothing in the JS side that is frame specific, it all happens in the C++ engine, which C# is a layer on top of.