我想获取我的webgl画布的快照,并且想要高分辨率捕获,所以我增加了画布的大小。这将自动更改gl.draingBufferWidth
和gl.draingBufferWidth
。然后设置视口,然后渲染场景。
我的代码在低分辨率(4000 * 4000以下)下可以正常工作,但在高分辨率下存在很多问题。
如果分辨率稍高,则快照不会完全显示。查看附件。如果分辨率提高更多,则不会显示任何内容。最后,在某些解决方案下,我的webgl实例被破坏了,我必须重新启动浏览器才能再次运行webgl
有什么方法可以从webgl画布获取高分辨率的快照吗?我可以使用其他解决方案吗?
答案 0 :(得分:1)
4000x4000像素是4000x4000x4或64兆像素的内存。 8000x8000是256兆的内存。浏览器不喜欢分配那么大的内存,并且经常在页面上设置限制。因此,例如,您有一个8000x8000 WebGL画布,它需要2个缓冲区。页面上显示的drawingbuffer和纹理。绘图缓冲区可能是抗锯齿。如果它是MSAA的4倍,那么仅该缓冲区就需要一个内存。然后拍摄一张屏幕截图,以便再增加256兆的内存。因此,是的,浏览器出于某种原因很可能会杀死您的页面。
除了WebGL之外,它还有自己的大小限制。您可以查找实际上是MAX_TEXTURE_SIZE
或MAX_VIEWPORT_DIMS
的限制。从中可以看出,大约40%的机器不能绘制大于4096的图形(尽管如果filter to desktop only it's much better,则可以绘制)。该数字仅表示硬件可以执行的操作。仍然受内存限制。
一种可能解决此问题的方法是将图像分成几部分绘制。如何执行将取决于您的应用程序。如果对所有渲染都使用相当标准的透视矩阵,则可以使用略有不同的数学来渲染视图的任何部分。大多数3d数学库都具有perspective
函数,并且大多数还具有相应的frustum
函数,该函数稍微灵活一些。
这是一个相当标准的WebGL简单样式示例,它使用典型的perspective
函数绘制一个立方体
"use strict";
const vs = `
uniform mat4 u_worldViewProjection;
attribute vec4 position;
attribute vec3 normal;
varying vec3 v_normal;
void main() {
v_normal = normal;
gl_Position = u_worldViewProjection * position;
}
`;
const fs = `
precision mediump float;
varying vec3 v_normal;
void main() {
gl_FragColor = vec4(v_normal * .5 + .5, 1);
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clearColor(0.2, 0.2, 0.2, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 10;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 4, -6];
const target = [0, 0, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
const world = m4.rotationY(Math.PI * .33);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, {
u_worldViewProjection: m4.multiply(viewProjection, world),
});
twgl.drawBufferInfo(gl, bufferInfo);
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
这是使用典型的frustum
函数而不是perspective
"use strict";
const vs = `
uniform mat4 u_worldViewProjection;
attribute vec4 position;
attribute vec3 normal;
varying vec3 v_normal;
void main() {
v_normal = normal;
gl_Position = u_worldViewProjection * position;
}
`;
const fs = `
precision mediump float;
varying vec3 v_normal;
void main() {
gl_FragColor = vec4(v_normal * .5 + .5, 1);
}
`;
const m4 = twgl.m4;
const gl = document.createElement("canvas").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
// size to render
const totalWidth = 400;
const totalHeight = 200;
const partWidth = 100;
const partHeight = 100;
// this fov is for the totalHeight
const fov = 30 * Math.PI / 180;
const aspect = totalWidth / totalHeight;
const zNear = 0.5;
const zFar = 10;
const eye = [1, 4, -6];
const target = [0, 0, 0];
const up = [0, 1, 0];
// since the camera doesn't change let's compute it just once
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const world = m4.rotationY(Math.PI * .33);
const imgRows = []; // this is only to insert in order
for (let y = 0; y < totalHeight; y += partHeight) {
const imgRow = [];
imgRows.push(imgRow)
for (let x = 0; x < totalWidth; x += partWidth) {
renderPortion(totalWidth, totalHeight, x, y, partWidth, partHeight);
const img = new Image();
img.src = gl.canvas.toDataURL();
imgRow.push(img);
}
}
// because webgl goes positive up we're generating the rows
// bottom first
imgRows.reverse().forEach((imgRow) => {
imgRow.forEach(document.body.appendChild.bind(document.body));
document.body.appendChild(document.createElement("br"));
});
function renderPortion(totalWidth, totalHeight, partX, partY, partWidth, partHeight) {
gl.canvas.width = partWidth;
gl.canvas.height = partHeight;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clearColor(0.2, 0.2, 0.2, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// corners at zNear for tital image
const zNearTotalTop = Math.tan(fov) * 0.5 * zNear;
const zNearTotalBottom = -zNearTotalTop;
const zNearTotalLeft = zNearTotalBottom * aspect;
const zNearTotalRight = zNearTotalTop * aspect;
// width, height at zNear for total image
const zNearTotalWidth = zNearTotalRight - zNearTotalLeft;
const zNearTotalHeight = zNearTotalTop - zNearTotalBottom;
const zNearPartLeft = zNearTotalLeft + partX * zNearTotalWidth / totalWidth; const zNearPartRight = zNearTotalLeft + (partX + partWidth) * zNearTotalWidth / totalWidth;
const zNearPartBottom = zNearTotalBottom + partY * zNearTotalHeight / totalHeight;
const zNearPartTop = zNearTotalBottom + (partY + partHeight) * zNearTotalHeight / totalHeight;
const projection = m4.frustum(zNearPartLeft, zNearPartRight, zNearPartBottom, zNearPartTop, zNear, zFar);
const viewProjection = m4.multiply(projection, view);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, {
u_worldViewProjection: m4.multiply(viewProjection, world),
});
twgl.drawBufferInfo(gl, bufferInfo);
}
img { border: 1px solid red; }
body { line-height: 0 }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
如果运行上面的代码段,您会看到它正在生成8张图像
重要的部分是这个
首先,我们需要确定所需的总大小
const totalWidth = 400;
const totalHeight = 200;
然后我们将创建一个函数,该函数将呈现该大小的任何较小部分
function renderPortion(totalWidth, totalHeight, partX, partY, partWidth, partHeight) {
...
我们将画布设置为零件的大小
gl.canvas.width = partWidth;
gl.canvas.height = partHeight;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
然后计算我们需要传递给frustum
函数的内容。首先,我们在zNear处计算一个矩形,该矩形将根据给定的视场,宽高比和zNear值生成透视矩阵。
// corners at zNear for total image
const zNearTotalTop = Math.tan(fov) * 0.5 * zNear;
const zNearTotalBottom = -zNearTotalTop;
const zNearTotalLeft = zNearTotalBottom * aspect;
const zNearTotalRight = zNearTotalTop * aspect;
// width, height at zNear for total image
const zNearTotalWidth = zNearTotalRight - zNearTotalLeft;
const zNearTotalHeight = zNearTotalTop - zNearTotalBottom;
然后,我们在zNear处为要渲染的部分计算相应的面积,并将其传递给frustum
以生成投影矩阵。
const zNearPartLeft = zNearTotalLeft + partX * zNearTotalWidth / totalWidth; const zNearPartRight = zNearTotalLeft + (partX + partWidth) * zNearTotalWidth / totalWidth;
const zNearPartBottom = zNearTotalBottom + partY * zNearTotalHeight / totalHeight;
const zNearPartTop = zNearTotalBottom + (partY + partHeight) * zNearTotalHeight / totalHeight;
const projection = m4.frustum(zNearPartLeft, zNearPartRight, zNearPartBottom, zNearPartTop, zNear, zFar);
然后我们就像正常渲染一样
最后在外面,我们有一个循环,可以使用刚生成的函数以所需的任意分辨率渲染任意数量的零件。
const totalWidth = 400;
const totalHeight = 200;
const partWidth = 100;
const partHeight = 100;
for (let y = 0; y < totalHeight; y += partHeight) {
for (let x = 0; x < totalWidth; x += partWidth) {
renderPortion(totalWidth, totalHeight, x, y, partWidth, partHeight);
const img = new Image();
img.src = gl.canvas.toDataURL();
// do something with image.
}
}
这将使您可以渲染为所需的任何大小,但需要其他方法将图像组合成一个较大的图像。您可能会或可能不会在浏览器中执行此操作。您可以尝试制作一个巨大的2D画布并在其中绘制每个零件(假设2D画布与WebGL的限制不同)。为此,无需制作图像,只需将webgl画布绘制到2d画布中即可。
否则,您可能必须将它们发送到您创建的服务器以组装图像,或者根据用例,让用户保存它们并将它们全部加载到图像编辑程序中。
或者,如果您只想显示它们,则浏览器对16x16 1024x1024图像的效果可能会比一张16kx16k图片更好。在这种情况下,您可能想调用canvas.toBlob
而不是使用dataURL,然后为每个Blob调用URL.createObjectURL
。这样一来,您就不会再出现这些巨大的dataURL字符串了。
示例:
"use strict";
const vs = `
uniform mat4 u_worldViewProjection;
attribute vec4 position;
attribute vec3 normal;
varying vec3 v_normal;
void main() {
v_normal = normal;
gl_Position = u_worldViewProjection * position;
}
`;
const fs = `
precision mediump float;
varying vec3 v_normal;
void main() {
gl_FragColor = vec4(v_normal * .5 + .5, 1);
}
`;
const m4 = twgl.m4;
const gl = document.createElement("canvas").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
// size to render
const totalWidth = 16384;
const totalHeight = 16385;
const partWidth = 1024;
const partHeight = 1024;
// this fov is for the totalHeight
const fov = 30 * Math.PI / 180;
const aspect = totalWidth / totalHeight;
const zNear = 0.5;
const zFar = 10;
const eye = [1, 4, -6];
const target = [0, 0, 0];
const up = [0, 1, 0];
// since the camera doesn't change let's compute it just once
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const world = m4.rotationY(Math.PI * .33);
const imgRows = []; // this is only to insert in order
for (let y = 0; y < totalHeight; y += partHeight) {
const imgRow = [];
imgRows.push(imgRow)
for (let x = 0; x < totalWidth; x += partWidth) {
renderPortion(totalWidth, totalHeight, x, y, partWidth, partHeight);
const img = new Image();
gl.canvas.toBlob((blob) => {
img.src = URL.createObjectURL(blob);
});
imgRow.push(img);
}
}
// because webgl goes positive up we're generating the rows
// bottom first
imgRows.reverse().forEach((imgRow) => {
const div = document.createElement('div');
imgRow.forEach(div.appendChild.bind(div));
document.body.appendChild(div);
});
function renderPortion(totalWidth, totalHeight, partX, partY, partWidth, partHeight) {
gl.canvas.width = partWidth;
gl.canvas.height = partHeight;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clearColor(0.2, 0.2, 0.2, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// corners at zNear for tital image
const zNearTotalTop = Math.tan(fov) * 0.5 * zNear;
const zNearTotalBottom = -zNearTotalTop;
const zNearTotalLeft = zNearTotalBottom * aspect;
const zNearTotalRight = zNearTotalTop * aspect;
// width, height at zNear for total image
const zNearTotalWidth = zNearTotalRight - zNearTotalLeft;
const zNearTotalHeight = zNearTotalTop - zNearTotalBottom;
const zNearPartLeft = zNearTotalLeft + partX * zNearTotalWidth / totalWidth; const zNearPartRight = zNearTotalLeft + (partX + partWidth) * zNearTotalWidth / totalWidth;
const zNearPartBottom = zNearTotalBottom + partY * zNearTotalHeight / totalHeight;
const zNearPartTop = zNearTotalBottom + (partY + partHeight) * zNearTotalHeight / totalHeight;
const projection = m4.frustum(zNearPartLeft, zNearPartRight, zNearPartBottom, zNearPartTop, zNear, zFar);
const viewProjection = m4.multiply(projection, view);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, {
u_worldViewProjection: m4.multiply(viewProjection, world),
});
twgl.drawBufferInfo(gl, bufferInfo);
}
img { border: 1px solid red; }
div { white-space: nowrap; }
body { line-height: 0 }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
如果您希望用户能够下载16386x16386图像而不是256个1024x1024图像,那么另一种解决方案是使用上面的部分渲染代码,并对图像的每一行(或多行)将其数据写入blob手动生成PNG。 This blog post涵盖了根据数据和this answer suggests how to do it for very large data手动生成PNG。
我只是为了好玩而写了library to help generate giant pngs in the browser。
答案 1 :(得分:-1)
使用gl.getParameter(gl.MAX_RENDERBUFFER_SIZE)
检查最大渲染缓冲区大小。您的画布不能大于该值。尝试突破限制可能会使您失去上下文。
如果您想要更大的屏幕截图,可以分多个步骤/平铺显示。调整投影变换以实现此目标应该相当容易。