复用WebGLRenderTarget作为材质时出现全黑问题求助
Three.js FBO作为Uniform传入后材质全黑问题
我是Shader新手,用Three.js结合ShaderMaterial开发时遇到一个问题:用FBO创建材质并作为uniform传入片元着色器,原本应该得到随机分布的颜色材质,但把this.fbo.texture设为uniform参数值后,材质变成全黑。
相关代码
JavaScript部分
setupFBO(){ this.size = 128; this.fbo = this.getRenderTarget(); this.fboScene = new THREE.Scene(); this.fboCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, -1, 1); this.fboCamera.position.set(0, 0, 0.5); let geometry = new THREE.PlaneGeometry(2, 2); this.data = new Float32Array(this.size * this.size * 4); for(let i = 0; i < this.size; i++){ for(let j = 0; j < this.size; j++){ let index = (i + j * this.size) * 4; let theta = Math.random() * Math.PI * 2; let r = 0.5 + 0.5 * Math.random(); this.data[index + 0] = r * Math.cos(theta); this.data[index + 1] = r * Math.sin(theta); this.data[index + 2] = 1.0; this.data[index + 3] = 1.0; } } this.fboTexture = new THREE.DataTexture(this.data, this.size, this.size, THREE.RGBAFormat, THREE.FloatType); this.fboTexture.magFilter = THREE.NearestFilter; this.fboTexture.minFilter = THREE.NearestFilter; this.fboTexture.needsUpdate = true; this.fboMaterial = new THREE.ShaderMaterial({ uniforms: { uPositions: {value: this.fboTexture}, time: {value: 0} }, vertexShader: simVertex, fragmentShader: simFragment }) let FBOmesh = new THREE.Mesh(geometry, this.fboMaterial); this.fboScene.add(FBOmesh); this.renderer.setRenderTarget(null); this.renderer.setRenderTarget(this.fbo); this.renderer.render(this.fboScene, this.fboCamera); this.renderer.setRenderTarget(null); ////here is the problem //this.fboMaterial.uniforms.uPositions.value = this.fbo.texture; //here is the problem //this.renderer.setRenderTarget(null); //this.renderer.setRenderTarget(this.fbo); //this.renderer.render(this.fboScene, this.fboCamera); //this.renderer.setRenderTarget(null); //this.renderer.render(this.fboScene, this.fboCamera); } getRenderTarget(){ const renderTarget = new THREE.WebGLRenderTarget(this.canvas.width, this.canvas.height, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat, type: THREE.FloatType, stencilBuffer: false }); return renderTarget; }
顶点着色器(simVertex.glsl)
uniform float time; varying vec2 vUv; varying vec3 vPosition; float PI = 3.141592653589793238; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
片元着色器(simFragment.glsl)
uniform float time; uniform float progress; uniform sampler2D uPositions; varying vec2 vUv; varying vec3 vPosition; void main(){ vec4 pos = texture2D(uPositions, vUv); gl_FragColor = vec4(pos.xyz, 1); }
奇怪的现象
如果先修改uniform值再执行渲染操作,结果是正常的:
this.fboMaterial.uniforms.uPositions.value = this.fbo.texture; this.renderer.setRenderTarget(null); this.renderer.setRenderTarget(this.fbo); this.renderer.render(this.fboScene, this.fboCamera); this.renderer.setRenderTarget(null); this.renderer.render(this.fboScene, this.fboCamera);
但我觉得两种操作顺序应该得到相同结果,毕竟先把渲染结果存在this.fbo里再重置了RenderTarget。而且换用新的FBO(比如fbo1)就不会有问题,怀疑是render操作修改了相关值,这是为什么?
解答
这是因为WebGL不允许在同一个渲染周期内,同时将一个帧缓冲作为渲染目标和采样纹理使用。
第一种操作顺序的问题在于:
- 先把场景渲染到
this.fbo,此时this.fbo是当前的渲染目标 - 紧接着把
this.fbo.texture设为uniform,然后再次渲染到this.fbo——这时候你试图读取正在被写入的帧缓冲,WebGL会触发"帧缓冲读写冲突",导致采样结果异常(全黑是常见表现)
而第二种顺序是先把uniform设为this.fbo.texture,再执行渲染:
- 第一次渲染到
this.fbo时,虽然uniform指向它,但此时this.fbo还没有有效数据,不过因为是首次写入,WebGL会正常执行写入操作;第二次渲染时,this.fbo已经有了上一次的渲染数据,此时读取是合法的(因为当前渲染目标是屏幕,读取this.fbo.texture没有冲突)
换用新FBO没问题的原因也很简单:两个不同的帧缓冲,一个作为写入目标,另一个作为采样源,完全不存在读写冲突。
解决方法
- 使用双缓冲FBO:准备两个FBO,交替作为渲染目标和采样纹理——这是GPGPU(通用GPU计算)里的标准做法,彻底避免读写冲突
- 确保在读取FBO纹理时,它不是当前的渲染目标:比如在渲染到FBO后,先切换回默认帧缓冲(
setRenderTarget(null)),再进行读取操作;如果需要循环迭代计算,必须用两个FBO交替
内容的提问来源于stack exchange,提问作者bents
相关产品推荐
相关产品推荐

