我正在尝试从画布获取像素RGBA数据以进行进一步处理。我认为,如果这有所不同,画布实际上就是Unity游戏。
我正在尝试使用游戏Shakes and Fidget的画布进行此操作。我使用readPixels中的context方法。
这是我尝试过的:
var example = document.getElementById('#canvas');
var context = example.getContext('webgl2'); // Also doesn't work with: ', {preserveDrawingBuffer: true}'
var pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4);
context.readPixels(0, 0, context.drawingBufferWidth, context.drawingBufferHeight, context.RGBA, context.UNSIGNED_BYTE, pixels);
但是所有像素显然都是黑色的(显然不是真的)。
编辑:另外,我想多次读取像素。 谢谢大家的回答。 @Kaiido提供的答案非常适合我:)
答案 0 :(得分:3)
您只能要求一次Canvas上下文。如果您将相同的选项传递给null
,则以下所有请求均将返回getContext()
,或者返回之前创建的上下文。
现在,您链接到的页面在创建上下文时未通过preserveDrawingBuffer
选项,这意味着要能够从此处获取像素信息,您必须将其链接事件循环与游戏循环一样发生。
幸运的是,这个精确的游戏确实使用了一个简单的requestAnimationFrame
循环,因此要挂接到同一个事件循环,我们要做的就是将代码包装在requestAnimationFrame
调用中。
由于回调是堆叠的,并且确实需要此类回调中的下一帧才能创建循环,因此我们可以确保我们的调用将在它们之后堆叠。
我现在意识到这可能并不明显,所以我将尝试进一步解释 requestAnimationFrame 的作用,以及如何确定在Unity的回调之后将调用回调。
requestAnimationFrame(fn)
将fn
回调推送到一堆回调中,所有这些回调将在浏览器执行其 paint之前以先进先出的顺序同时被调用屏幕上的操作。在最接近的事件循环结束时,偶尔会发生这种情况(通常每秒60次)。
可以理解为setTimeout(fn , time_remaining_until_next_paint)
的一种,主要区别是可以保证 requestAnimationFrame 回调执行程序将在事件循环结束时以及在其他js执行之后被调用事件循环的内容。
因此,如果我们在与调用回调的事件循环相同的事件循环中调用requestAnimationFrame(fn)
,则伪造的time_remaining_until_next_paint
将是0
,而fn
将被推送在堆栈的底部(后进后出)。
而且,当从回调执行器本身内部调用requestAnimationFrame(fn)
时,time_remaining_until_next_paint
可能会围绕16
,并且fn
将在下一帧的第一个调用中被调用。>
因此,确保从 requestAnimationFrame 的回调执行程序外部进行的对requestAnimationFrame(fn)
的任何调用都可以在与 requestAnimationFrame支持的循环,然后被调用。
因此,我们要获取这些像素,就是将对 readPixels 的调用包装在 requestAnimationFrame 调用中,并在之后调用它Unity的循环开始了。
var example = document.getElementById('#canvas');
var context = example.getContext('webgl2') || example.getContext('webgl');
var pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4);
requestAnimationFrame(() => {
context.readPixels(0, 0, context.drawingBufferWidth, context.drawingBufferHeight, context.RGBA, context.UNSIGNED_BYTE, pixels);
// here `pixels` has the correct data
});
答案 1 :(得分:1)
可能您需要在渲染像素的同时读取像素,或者需要强制画布使用preserveDrawingBuffer: true
,以便您可以随时读取画布。
要做第二个覆盖getContext
HTMLCanvasElement.prototype.getContext = function(origFn) {
const typesWeCareAbout = {
"webgl": true,
"webgl2": true,
"experimental-webgl": true,
};
return function(type, attributes = {}) {
if (typesWeCareAbout[type]) {
attributes.preserveDrawingBuffer = true;
}
return origFn.call(this, type, attributes);
};
}(HTMLCanvasElement.prototype.getContext);
将其放在Unity游戏之前的文件顶部,或者将其放在单独的脚本文件中,然后将其包含在Unity游戏之前。
您现在应该可以在Unity制作的任何画布上获取上下文,并随时随地调用gl.readPixels
。
对于另一种方法,在同一事件中获取像素,您可以换行requestAnimationFrame
以便在Unity使用gl.readPixels
后插入requestAnimationFrame
window.requestAnimationFrame = function(origFn) {
return function(callback) {
return origFn(this, function(time) {
callback(time);
gl.readPixels(...);
};
};
}(window.requestAnimationFrame);
另一种解决方案是使用虚拟webgl上下文。 This library shows an example of implementing a virtual webgl context并显示an example of post processing the unity output
请注意,在某些时候Unity可能会切换为使用OffscreenCanvas
。届时,它可能需要除上述解决方案以外的其他解决方案。
答案 2 :(得分:0)
或者,您可以将画布的内容流式传输到视频元素,将视频内容绘制到另一个画布并读取那里的像素。
这应该独立于由 requestAnimationFrame
绘制的框架,但是是异步的。
我们需要一个视频、另一个画布和一个流:
var example = document.getElementById('#canvas');
var stream=example.captureStream(0);//0 fps
var vid=document.createElement("video");
vid.width=example.width;
vid.height=example.height;
vid.style="display:none;";
document.body.appendChild(vid);
var canvas2=document.createElement("canvas");
canvas2.width=example.width;
canvas2.height=example.height;
canvas2.style="display:none;";
var width=example.width;
var height=example.height;
body.appendChild(canvas2);
var ctx = canvas2.getContext('2d');
现在您可以通过从流中请求一个帧,将其推入视频并将视频绘制到我们的画布上来读取游戏画布:
stream.requestFrame();
//wait for the game to draw a frame
vid.srcObject=stream;
//wait
ctx.drawImage(vid, 0, 0, width, height, 0, 0, width, height);
var pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4);
ctx.readPixels(0, 0, ctx.drawingBufferWidth, ctx.drawingBufferHeight, ctx.RGBA, ctx.UNSIGNED_BYTE, pixels);