WebGL 2D重叠形状动画问题:重绘时旧图形消失无法叠加
解决WebGL 2D图形叠加保留旧图形的问题
嘿,我刚好碰到过类似的坑!核心原因其实是WebGL默认的渲染逻辑——每次你调用gl.clear(gl.COLOR_BUFFER_BIT)的时候,画布的颜色缓冲区会被完全清空,之前画的所有像素都没了,所以重绘后自然只能看到新图形。下面分两种场景给你具体的解决办法:
场景1:点击后叠加静态图形(不需要实时旋转动画)
如果你的需求是用户点击一次,就叠加一个以点击点为中心旋转后的图形,那改起来超简单:
- 初始化时只清一次屏:程序启动的时候调用一次
gl.clear(gl.COLOR_BUFFER_BIT),把画布清空到初始状态就行。 - 移除后续的清屏操作:如果之前有
requestAnimationFrame循环,把循环里的gl.clear()删掉;如果是点击触发绘制,直接在点击事件里画新图形,绝对不要调用清屏。
举个代码例子,假设你之前的点击处理是这样的:
canvas.addEventListener('click', (e) => { gl.clear(gl.COLOR_BUFFER_BIT); // 这行就是清掉旧图形的罪魁祸首! updateRotationCenter(e.clientX, e.clientY); gl.uniformMatrix3fv(matrixLoc, false, rotationMatrix); gl.drawArrays(gl.TRIANGLES, 0, 3); });
现在把gl.clear()那行删掉,点击后新图形就会直接叠在旧图形上面啦。
场景2:实时旋转动画,同时保留所有历史轨迹
如果需要图形一直绕点击点旋转,并且要保留旋转过程中所有位置的图形(比如画轨迹),那得用**离屏帧缓冲(FBO)**来实现累积绘制:
简单来说,就是我们先把所有绘制内容存在一个离屏的纹理里,每次新绘制的图形都叠加到这个纹理上,然后再把整个纹理渲染到画布上。步骤如下:
1. 创建离屏帧缓冲和纹理
const fbo = gl.createFramebuffer(); const accumTexture = gl.createTexture(); // 初始化纹理,尺寸和画布一致 gl.bindTexture(gl.TEXTURE_2D, accumTexture); gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, canvas.width, canvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null ); // 设置纹理过滤,避免拉伸模糊 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 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); // 把纹理绑定到FBO gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, accumTexture, 0 ); // 解绑,避免影响后续操作 gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.bindTexture(gl.TEXTURE_2D, null);
2. 初始化绘制内容
启动时先把初始图形绘制到离屏FBO里:
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); gl.clear(gl.COLOR_BUFFER_BIT); // 绘制初始图形 gl.uniformMatrix3fv(matrixLoc, false, initialMatrix); gl.drawArrays(gl.TRIANGLES, 0, 3); gl.bindFramebuffer(gl.FRAMEBUFFER, null);
3. 修改渲染循环
每次渲染时,先把当前旋转的图形叠加到离屏纹理,再把整个纹理渲染到画布:
// 你需要一个简单的全屏着色器来绘制纹理 // 顶点着色器:attribute vec2 a_position; varying vec2 v_uv; void main() { v_uv = a_position * 0.5 + 0.5; gl_Position = vec4(a_position, 0.0, 1.0); } // 片段着色器:precision mediump float; uniform sampler2D u_texture; varying vec2 v_uv; void main() { gl_FragColor = texture2D(u_texture, v_uv); } function render() { // 第一步:叠加新图形到离屏FBO gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); // 不要清屏!这样新图形会叠在旧内容上 updateRotationMatrix(); // 更新当前的旋转矩阵 gl.uniformMatrix3fv(matrixLoc, false, rotationMatrix); gl.drawArrays(gl.TRIANGLES, 0, 3); // 第二步:把离屏纹理渲染到画布 gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.clear(gl.COLOR_BUFFER_BIT); // 这里清屏没关系,因为我们要绘制整个纹理 // 使用全屏着色器绘制 gl.useProgram(fullscreenProgram); gl.bindTexture(gl.TEXTURE_2D, accumTexture); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); // 全屏四边形用TRIANGLE_STRIP只需要4个顶点 requestAnimationFrame(render); }
额外注意点
- 场景1的方法虽然简单,但如果叠加的图形太多,可能会占用较多内存,不过对于中小规模的需求完全够用。
- 场景2的FBO方法更适合实时动画,性能更可控,但需要额外写一个全屏着色器,代码量不大,网上一搜就能找到现成的模板。
- 如果遇到WebGL上下文丢失的情况(比如浏览器窗口最小化再恢复),需要重新创建FBO和纹理哦。
内容的提问来源于stack exchange,提问作者Legerdemainist
相关产品推荐
相关产品推荐

