I am working on a scene in THREE.js that uses a Frame Buffer Object. For some reason, I'm seeing a crazy flickering / strobe effect. (The strobe goes away if one comments out the line: mesh.material.uniforms.fboData.value = renderTargetA.texture
):
THREE.FBO = function(w, m) {
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(-w/2, w/2, w/2, -w/2, -1, 1);
this.scene.add(new THREE.Mesh(new THREE.PlaneGeometry(w, w), m));
};
function resize() {
var w = window.innerWidth,
h = window.innerHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h, false);
}
function getTexture(src) {
var image = document.createElement('img');
var tex = new THREE.Texture(image);
image.addEventListener('load', function(event) {
tex.needsUpdate = true;
});
image.src = src;
return tex;
}
function getDataTexture(fboData, config) {
var dataTexture = new THREE.DataTexture(fboData, config.w, config.w, config.format, config.type);
dataTexture.minFilter = config.minFilter;
dataTexture.magFilter = config.magFilter;
dataTexture.needsUpdate = true;
return dataTexture;
}
/**
* Scene
**/
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.001, 1000);
var renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
camera.position.set(0, 0, 2);
document.body.appendChild(renderer.domElement);
/**
* Geometry
**/
// fbo config
var config = {
w: 512, // number of pixels in each dimension
type: THREE.FloatType,
format: THREE.RGBAFormat,
stride: 4, // number of units per cell in format
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
}
var n = 2**10,
rootN = Math.pow(n, 0.5),
translations = new Float32Array(n * 3),
fboData = new Float32Array(config.w**2 * config.stride),
fboOffsets = new Float32Array(n * 2),
translationIterator = 0,
fboOffsetIterator = 0;
for (var i=0; i<n; i++) {
// set rendered vertex offsets
var x = (((i % rootN) / rootN) * 2) - 1;
var y = ((Math.floor(i / rootN) / rootN) * 2) - 1;
translations[translationIterator++] = x;
translations[translationIterator++] = y;
translations[translationIterator++] = 0;
// set uvs for looking up FBO data that corresponds to this cell
fboOffsets[fboOffsetIterator++] = (i % config.w) / config.w;
fboOffsets[fboOffsetIterator++] = (Math.floor(i / config.w)) / config.w;
// write the x, y components of the fboData array
fboData[(i*config.stride) ] = Math.random(); // make data non-negative
fboData[(i*config.stride) + 1] = Math.random(); // make data non-negative
fboData[(i*config.stride) + 2] = 1;
if (config.stride === 4) fboData[(i*config.stride) + 3] = 1;
}
// create the FBO
var fboTexture = getDataTexture(fboData, config);
fboMaterial = new THREE.RawShaderMaterial({
uniforms: {
fboTexture: {
type: 't',
value: fboTexture,
},
},
vertexShader: document.querySelector('#fbo-vertex').textContent,
fragmentShader: document.querySelector('#fbo-fragment').textContent,
});
fboMaterial.uniforms.fboTexture.needsUpdate = true;
fbo = new THREE.FBO(config.w, fboMaterial);
// create render targets a + b to which the simulation will be rendered
renderTargetA = new THREE.WebGLRenderTarget(config.w, config.w, {
format: config.format,
type: config.type,
minFilter: config.minFilter,
magFilter: config.magFilter,
wrapS: THREE.RepeatWrapping,
wrapT: THREE.RepeatWrapping,
stencilBuffer: false,
});
renderTargetB = renderTargetA.clone();
// render the initial data to target a
renderer.setRenderTarget(renderTargetA);
renderer.render(fbo.scene, fbo.camera);
renderer.setRenderTarget(null);
// render the initial data to target b
renderer.setRenderTarget(renderTargetB);
renderer.render(fbo.scene, fbo.camera);
renderer.setRenderTarget(null);
// create the geometry
var geometry = new THREE.InstancedBufferGeometry();
var position = new THREE.BufferAttribute(new Float32Array([0, 0, 0]), 3);
var translation = new THREE.InstancedBufferAttribute(translations, 3, false, 1);
var fboOffset = new THREE.InstancedBufferAttribute(fboOffsets, 2, false, 1);
geometry.setAttribute('position', position);
geometry.setAttribute('translation', translation);
geometry.setAttribute('fboOffset', fboOffset);
// build the rendered mesh
var material = new THREE.RawShaderMaterial({
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent,
uniforms: {
fboData: {
type: 't',
value: fboTexture,
}
}
})
mesh = new THREE.Points(geometry, material);
mesh.frustumCulled = false;
scene.add(mesh);
// resize everything
resize();
// resize
window.addEventListener('resize', resize);
/**
* Main
**/
function updateFBO() {
// at the start of the render block, A is one frame behind B
var oldA = renderTargetA; // store A, the penultimate state
renderTargetA = renderTargetB; // advance A to the updated state
renderTargetB = oldA; // set B to the penultimate state
// pass the updated values to the fbo
fboMaterial.uniforms.fboTexture.value = renderTargetA.texture;
// run a frame and store the new positional values in renderTargetB
renderer.setRenderTarget(renderTargetB);
renderer.render(fbo.scene, fbo.camera);
renderer.setRenderTarget(null);
// pass the new positional values to the scene users see
if (mesh) mesh.material.uniforms.fboData.value = renderTargetA.texture;
}
function animate() {
requestAnimationFrame(animate);
updateFBO();
renderer.render(scene, camera);
}
animate();
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<style>* {margin: 0; height: 100%; width: 100%;}</style>
</head>
<body>
<!-- rendered vert shader -->
<script type='x-shader/x-vertex' id='vertex-shader'>
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform sampler2D fboData;
attribute vec3 position;
attribute vec3 translation;
attribute vec2 fboOffset;
varying vec4 vPixel;
void main() {
gl_PointSize = 7.0;
vec3 pos = position + translation;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
vPixel = texture2D(fboData, fboOffset);
}
</script>
<!-- rendered frag shader -->
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
varying vec4 vPixel;
void main() {
gl_FragColor = vPixel;
}
</script>
<!-- FBO vert -->
<script type='x-shader/x-vertex' id='fbo-vertex'>
precision lowp float;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
attribute vec2 uv; // x,y offsets of each point in FBO texture
attribute vec3 position;
varying vec2 vUv;
void main() {
// vUv is the position of the current observation in the texture
vUv = vec2(uv.x, 1.0 - uv.y);
// the position of the cell in the texture
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<!-- FBO frag -->
<script type='x-shader/x-fragment' id='fbo-fragment'>
precision lowp float;
uniform sampler2D fboTexture;
varying vec2 vUv;
void main() {
vec4 pixel = texture2D(fboTexture, vUv);
// write the updated value to the screen so it can be read
gl_FragColor = vec4(pixel);
}
</script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js'></script>
</body>
</html>
Does anyone know what is causing this strobing? Are the two render targets somehow out of sync?
I'm looking at the output of both your renderTargetA and B, and it looks like one is outputting data at the top row of the image, while the second one is at the bottom row. My best guess is that your "ping-pong" effect is working, but you see a flicker because your mesh is reading data from the same row, which is empty 1/2 of the time.
Frame buffer 0 (Data at top):
Frame buffer 1 (Data at bottom):