英文:
WebGL - Draw textures to canvas async / in sequence deletes old textures
问题
I understand that you want a translation of the provided code and explanation. Here's the translated code and explanation:
我正在学习 webgl,我试图完成以下任务:获取一系列图像,然后在 fetch 调用完成后将它们绘制到 webgl 画布上。每个图像在画布上都有一个位置,因此每个图像被获取和绘制时,应该“填充”完成图像的一部分。然而,我得到的结果是每次将图像绘制到画布上时,其他图像都会消失。例如,4 张图像:
[![enter image description here][1]][1]
[![enter image description here][2]][2]
[![enter image description here][3]][3]
[![enter image description here][4]][4]
相反,我期望的是在绘制这 4 张图像后,我会看到这样的结果:
[![enter image description here][5]][5]
看一下我的代码,这是我定义图块并迭代它们的主要函数:
```js
function main() {
const tiles: Tile[] = [
{
path: topleft,
position: { x: 0, y: 0 }
},
{
path: topright,
position: { x: 256, y: 0 }
},
{
path: bottomleft,
position: { x: 0, y: 256 }
},
{
path: bottomright,
position: { x: 256, y: 256 }
}
];
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
canvas.height = 256 * 2;
canvas.width = 256 * 2;
tiles.forEach((tile, i) => {
setTimeout(() => {
const image = new Image();
image.crossOrigin = "anonymous";
image.onload = () => render(image, i, tile);
image.src = tile.path;
}, i * 1000);
});
}
在实际情况下,图像源和位置可能会更加动态,但目标是相同的 - 异步加载图像,并在加载完成后渲染它们。我的渲染函数是执行大部分 gl
工作的地方:
// 渲染函数
function render(tileImage: HTMLImageElement, i: number, tile: Tile) {
// 获取顶点数据的位置。
var positionLocation = gl.getAttribLocation(program, 'a_position');
var texcoordLocation = gl.getAttribLocation(program, 'a_texCoord');
// 创建一个用于放置三个 2D 裁剪空间点的缓冲区。
var positionBuffer = gl.createBuffer();
// 将其绑定到 ARRAY_BUFFER(将其视为 ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 设置一个与图像大小相同的矩形。
setRectangle(
gl,
tile.position.x,
tile.position.y,
tileImage.width,
tileImage.height
);
// 为矩形提供纹理坐标。
var texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
// 格式化忽略
new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0,
]),
gl.STATIC_DRAW
);
// 创建纹理并绑定到 gl 上下文。
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// 设置参数,以便渲染任何大小的图像。
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// 将图块图像上传到纹理。
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
tileImage
);
// 查找 uniform 变量
var resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
var textureSizeLocation = gl.getUniformLocation(program, 'u_textureSize');
// 告诉 WebGL 如何从裁剪空间转换为像素。
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// 告诉它使用我们的程序(一对着色器)。
gl.useProgram(program);
// 打开位置属性。
gl.enableVertexAttribArray(positionLocation);
// 绑定位置缓冲区。
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 告诉位置属性如何从 positionBuffer(ARRAY_BUFFER)获取数据。
var size = 2; // 每次迭代的组件数
var type = gl.FLOAT; // 数据是 32 位浮点数
var normalize = false; // 不要规范化数据
var stride = 0; // 0 = 每次迭代前进 size * sizeof(type) 以获取下一个位置
var offset = 0; // 从缓冲区的开头开始
// 格式化忽略
gl.vertexAttribPointer(
positionLocation,
size,
type,
normalize,
stride,
offset
);
// 打开纹理坐标属性。
gl.enableVertexAttribArray(texcoordLocation);
// 绑定纹理坐标缓冲区。
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
// 告诉纹理坐标属性如何从 texcoordBuffer(ARRAY_BUFFER)获取数据。
var size = 2; // 每次迭代的组件数
var type = gl.FLOAT; // 数据是 32 位浮点数
var normalize = false; // 不要规范化数据
var stride = 0; // 0 = 每次迭代前进 size
<details>
<summary>英文:</summary>
I am learning webgl, and I am trying to do the following: Fetch a series of images, and then draw them to the webgl canvas as the fetch calls complete. Each image has a position on the canvas, so as each image is fetched and drawn, it should "fill in" part of a complete image. Instead what I am getting is that each time an image is drawn to the canvas, the others disappear. For example, 4 images:
[![enter image description here][1]][1]
[![enter image description here][2]][2]
[![enter image description here][3]][3]
[![enter image description here][4]][4]
What I would expect instead is that after the 4 images are drawn, I would see this:
[![enter image description here][5]][5]
Taking a look at my code, here is the main function where I define the tiles and iterate over them:
```js
function main() {
const tiles: Tile[] = [
{
path: topleft,
position: { x: 0, y: 0 }
},
{
path: topright,
position: { x: 256, y: 0 }
},
{
path: bottomleft,
position: { x: 0, y: 256 }
},
{
path: bottomright,
position: { x: 256, y: 256 }
}
];
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
canvas.height = 256 * 2;
canvas.width = 256 * 2;
tiles.forEach((tile, i) => {
setTimeout(() => {
const image = new Image();
image.crossOrigin = "anonymous";
image.onload = () => render(image, i, tile);
image.src = tile.path;
}, i * 1000);
});
}
In a real life scenario, the image sources and positions would be a bit more dynamic, but the goal is the same - load images async, and as they load in, render them. My render function is where most of the gl
work happens:
function render(tileImage: HTMLImageElement, i: number, tile: Tile) {
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, 'a_position');
var texcoordLocation = gl.getAttribLocation(program, 'a_texCoord');
// Create a buffer to put three 2d clip space points in
var positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Set a rectangle the same size as the image.
setRectangle(
gl,
tile.position.x,
tile.position.y,
tileImage.width,
tileImage.height
);
// provide texture coordinates for the rectangle.
var texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
// prettier-ignore
new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0,
]),
gl.STATIC_DRAW
);
// Create a texture and bing it to the gl context
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// Upload the tile image to the texture
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
tileImage
);
// lookup uniforms
var resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
var textureSizeLocation = gl.getUniformLocation(program, 'u_textureSize');
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the position attribute
gl.enableVertexAttribArray(positionLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
// prettier-ignore
gl.vertexAttribPointer(
positionLocation, size, type, normalize, stride, offset);
// Turn on the texcoord attribute
gl.enableVertexAttribArray(texcoordLocation);
// bind the texcoord buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
// Tell the texcoord attribute how to get data out of texcoordBuffer (ARRAY_BUFFER)
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
// prettier-ignore
gl.vertexAttribPointer(
texcoordLocation, size, type, normalize, stride, offset);
// set the resolution
gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
// set the size of the image
gl.uniform2f(textureSizeLocation, 256, 256);
// Draw the rectangle.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
gl.drawArrays(primitiveType, offset, count);
}
const gl = getGlContext();
const { program } = setup(gl, vert1, frag1);
// Fills the buffer with the values that define a rectangle.
export function setRectangle(
gl: WebGLRenderingContext,
x: number,
y: number,
width: number,
height: number
) {
const x1 = x,
x2 = x + width,
y1 = y,
y2 = y + height;
gl.bufferData(
gl.ARRAY_BUFFER,
// prettier-ignore
new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2]),
gl.STATIC_DRAW
);
}
Where setup
is a function to create a program
and link the shaders, and getGlContext
just gets and returns the webgl context from the canvas.
I have a feeling I am not understanding something in the procedure of creating and binding the buffer, or creating and binding the texture, so that the old texture data is "forgotten" when the new texture data is drawn.
Here is a codesandbox demonstrating the issue. It uses typescript, and webpack to import the GLSL files, so that they can be written separately and with GLSL syntax highlighting.
How do I draw a new texture to the canvas at a specified location while still maintaining the old textures?
答案1
得分: 1
我认为您的问题在于浏览器会定期读取您的画布内容并将其与页面的其余部分合成。在这样做时,为了不在下一个间隔合成两个帧并为了性能原因,它还会清除画布缓冲区(实际上执行了gl.clear())。
您可以通过在获取WebGL上下文时传递preserveDrawingBuffer: true
选项来强制保留画布:
export const gl = canvas.getContext("webgl", {
preserveDrawingBuffer: true
}) as WebGLRenderingContext;
理想情况下,您希望渲染以大约60fps的速度进行,这样您就不必担心页面合成器清除您的内容,因为您会一遍又一遍地重新绘制它们。
在您的情况下,您可能希望创建多个纹理并在单个渲染通道中渲染所有四个瓷砖,每个象限从不同的纹理统一读取。
或者,您可以将画布内容渲染到输出纹理(查找渲染缓冲区),并且每个瓷砖加载时,将输出纹理与每个新瓷砖组合,直到填满所有空白部分。然后将完整的输出纹理渲染到画布上。
英文:
I think your issue is that the browser reads the contents of your canvas and composites it with the rest of the page at regular intervals. It also clears the canvas buffer (effectively does a gl.clear()) when it does so in order to not composite two frames on the next interval and for performance reasons.
You can force the canvas to persist by passing the preserveDrawingBuffer: true
option when getting the WebGL context:
export const gl = canvas.getContext("webgl", {
preserveDrawingBuffer: true
}) as WebGLRenderingContext;
Ideally, you want your rendering to happen at something like 60fps, so you never have to worry about the page compositor clearing your contents because you're redrawing them over and over and over again.f
In your case, you probably want to create multiple textures and render all four tiles in a single render pass, each quadrant reading from the a different texture uniform.
Or you can render the canvas content to an output texture (look up a renderbuffer) and as each tile loads, combine the output texture and each new tile until all the blank spots are filled in. Then render the complete output texture on the canvas.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论