WebGL技术问题:如何为片元着色器输出添加后期处理滤镜?
WebGL实现片元着色器输出的Bloom/发光效果步骤
1. 准备帧缓冲和纹理
WebGL的帧缓冲(FBO)完全可以捕获片元着色器的输出,不需要依赖外部Image。你需要创建关联纹理的帧缓冲,用来存储渲染结果:
function createFramebufferTexture(gl, width, height) { const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, 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); const fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) { console.error("Framebuffer incomplete"); } gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.bindTexture(gl.TEXTURE_2D, null); return { fbo, texture }; }
调用这个函数创建两个帧缓冲:一个存原始场景,一个存模糊后的光晕。
2. 将片元着色器输出渲染到帧缓冲
替代直接渲染到画布,切换渲染目标到帧缓冲,执行你的原始渲染逻辑:
// 绑定帧缓冲,渲染结果存入纹理 gl.bindFramebuffer(gl.FRAMEBUFFER, sceneFbo.fbo); gl.viewport(0, 0, canvas.width, canvas.height); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 执行你的原始场景渲染(用你自己的顶点/片元着色器) drawYourOriginalScene(); // 解绑帧缓冲,回到默认画布 gl.bindFramebuffer(gl.FRAMEBUFFER, null);
此时sceneFbo.texture里就存储了你的片元着色器输出内容。
3. 提取高亮区域(可选优化)
如果只需要让场景中高亮部分发光,先通过着色器提取高亮像素:
precision mediump float; uniform sampler2D u_sceneTexture; varying vec2 v_uv; void main() { vec4 color = texture2D(u_sceneTexture, v_uv); float brightness = dot(color.rgb, vec3(0.299, 0.587, 0.114)); // 阈值可调整,只保留亮度过高的像素 gl_FragColor = brightness > 0.8 ? color : vec4(0.0); }
把这个着色器的输出渲染到highlightFbo,后续只模糊高亮区域能节省性能。
4. 高斯模糊处理
对高亮纹理做水平+垂直两次模糊,得到柔和光晕:
- 水平模糊着色器示例:
precision mediump float; uniform sampler2D u_inputTexture; uniform vec2 u_textureSize; varying vec2 v_uv; void main() { vec2 step = 1.0 / u_textureSize; vec4 color = vec4(0.0); float weights[9] = float[](0.05, 0.1, 0.05, 0.1, 0.4, 0.1, 0.05, 0.1, 0.05); vec2 offsets[9] = vec2[]( vec2(-step.x, step.y), vec2(0.0, step.y), vec2(step.x, step.y), vec2(-step.x, 0.0), vec2(0.0, 0.0), vec2(step.x, 0.0), vec2(-step.x, -step.y), vec2(0.0, -step.y), vec2(step.x, -step.y) ); for (int i = 0; i < 9; i++) { color += texture2D(u_inputTexture, v_uv + offsets[i]) * weights[i]; } gl_FragColor = color; }
先渲染水平模糊到临时帧缓冲,再用临时纹理作为输入渲染垂直模糊,最终得到模糊后的光晕纹理。
5. 叠加最终效果
把原始场景纹理和模糊光晕叠加到默认画布:
precision mediump float; uniform sampler2D u_sceneTexture; uniform sampler2D u_blurTexture; varying vec2 v_uv; void main() { vec4 sceneColor = texture2D(u_sceneTexture, v_uv); vec4 blurColor = texture2D(u_blurTexture, v_uv); // 调整权重控制发光强度 gl_FragColor = sceneColor + blurColor * 1.5; }
绑定默认帧缓冲(传null),用这个着色器渲染一个全屏矩形,传入两个纹理即可完成最终效果。
关键注意事项
- 帧缓冲尺寸要和画布一致(或按比例缩小提升性能),避免拉伸变形。
- 切换渲染目标和着色器时,确保纹理和uniform绑定正确。
- 如果不需要局部发光,直接模糊整个场景纹理再叠加,也能实现全局发光效果。
内容的提问来源于stack exchange,提问作者Joe
相关产品推荐
相关产品推荐

