Three.js WebGL模糊着色器:如何将生成的圆用于模糊处理?
解决方案:用离屏渲染分两步处理
核心问题本质
你没法在同一个片元着色器里直接采样刚生成的像素结果——GPU是并行计算的,当前像素的输出还没写入帧缓冲,根本没法被读取。必须拆成两步走:先把圆形画到一个离屏纹理上,再用这个纹理作为输入做模糊处理。
修改后的代码实现
JS部分
// 基础场景/相机/渲染器初始化(假设你已具备) const scene = new THREE.Scene(); const camera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0.1, 10); camera.position.z = 1; const renderer = new THREE.WebGLRenderer({ alpha: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 1. 创建离屏渲染目标,专门保存圆形的渲染结果 const rt = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, { format: THREE.RGBAFormat, type: THREE.FloatType, // 避免透明背景下的颜色精度损失 transparent: true }); // 2. 圆形生成专用材质 const circleGeometry = new THREE.PlaneGeometry(1, 1); const circleMaterial = new THREE.ShaderMaterial({ uniforms: { spots: { value: 5.0 }, offset: { value: new THREE.Vector2(0.5, 0.5) }, radius: { value: 0.25 } }, vertexShader: ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` precision mediump float; varying vec2 vUv; uniform float spots; uniform vec2 offset; uniform float radius; float rnd(vec3 scale, float seed) { return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed); } void main() { vec4 color1 = vec4(0.1, 0.5, 0.3, 1.0); vec4 color2 = vec4(0.0, 0.0, 0.0, 0.0); // 完全透明背景 vec4 finalCol = vec4(0.0); vec2 shift = offset; for (float t = 0.0; t <= spots; t++) { // 这里可以加随机偏移,比如:shift += vec2(rnd(vec3(t),0.0)-0.5, rnd(vec3(t+100),0.0)-0.5)*0.2; float p = smoothstep(radius, radius + 0.0001, length(vUv - shift)); vec4 col = mix(color1, color2, p); finalCol += col; shift.x += 0.05; shift.y -= 0.01; } gl_FragColor = finalCol; } `, transparent: true }); const circlePlane = new THREE.Mesh(circleGeometry, circleMaterial); scene.add(circlePlane); // 3. 模糊处理专用材质 const blurGeometry = new THREE.PlaneGeometry(1, 1); const blurMaterial = new THREE.ShaderMaterial({ uniforms: { iP: { value: rt.texture }, // 把离屏纹理作为模糊输入 dl: { value: new THREE.Vector2(0.6 / window.innerWidth, 0) }, // 偏移量要除以分辨率(UV是0-1范围) resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) } }, vertexShader: ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` precision mediump float; varying vec2 vUv; uniform sampler2D iP; uniform vec2 dl; float rnd(vec3 scale, float seed) { return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed); } void main() { vec4 col = vec4(0.0); float tt = 0.0; float off = rnd(vec3(12.9898, 78.233, 151.7182), 0.0); for (float t = -30.0; t <= 30.0; t++) { float pc = (t + off - 0.5) / 30.0; float w = 1.0 - abs(pc); vec4 spl = texture2D(iP, vUv + dl * pc); spl.rgb *= spl.a; // 预乘alpha处理透明背景 col += spl * w; tt += w; } col /= tt; col.rgb /= col.a + 0.00001; // 反预乘还原颜色 gl_FragColor = col; } `, transparent: true }); const blurPlane = new THREE.Mesh(blurGeometry, blurMaterial); blurPlane.visible = false; scene.add(blurPlane); // 渲染循环:分两步渲染 function animate() { requestAnimationFrame(animate); // 第一步:把圆形渲染到离屏目标 circlePlane.visible = true; blurPlane.visible = false; renderer.setRenderTarget(rt); renderer.render(scene, camera); // 第二步:把模糊后的结果渲染到画布 circlePlane.visible = false; blurPlane.visible = true; renderer.setRenderTarget(null); renderer.render(scene, camera); } animate(); // 窗口 resize 时更新渲染目标和参数 window.addEventListener('resize', () => { renderer.setSize(window.innerWidth, window.innerHeight); rt.setSize(window.innerWidth, window.innerHeight); blurMaterial.uniforms.dl.value.set(0.6 / window.innerWidth, 0); });
关键修改说明
- 离屏渲染目标:用
WebGLRenderTarget将圆形的渲染结果保存为可复用的纹理,解决了同一着色器无法读取自身输出的问题 - 拆分着色器:把圆形生成和模糊处理拆成两个独立的着色器,逻辑更清晰,也符合GPU的渲染流程
- UV偏移修正:模糊时的偏移量必须除以屏幕分辨率,因为UV坐标是0-1的相对范围,直接用像素偏移会导致模糊过度
- Alpha通道处理:通过预乘/反预乘alpha,解决透明背景下模糊边缘发黑的问题
随机圆形补充实现
如果要给圆形加随机位置,直接在圆形着色器的循环里修改shift即可:
// 替换原循环里的shift更新逻辑 float randomX = rnd(vec3(t, 123.45, 67.89), 0.0) * 0.4 - 0.2; float randomY = rnd(vec3(t, 987.65, 43.21), 0.0) * 0.4 - 0.2; shift = offset + vec2(randomX, randomY);
内容的提问来源于stack exchange,提问作者Jimmy
相关产品推荐
相关产品推荐

