THREE.js 帧缓冲区对象的闪烁结果
THREE.js flickering result with Frame Buffer Object
我正在处理 THREE.js 中使用帧缓冲区对象的场景。出于某种原因,我看到了疯狂的闪烁/频闪效果。 (如果注释掉以下行,闪光灯就会消失: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>
有谁知道造成这种频闪的原因是什么?两个渲染目标是否不同步?
我正在查看您的 renderTargetA 和 B 的输出,看起来一个在图像的顶行输出数据,而第二个在底行。我最好的猜测是您的“乒乓”效果正在发挥作用,但您看到闪烁,因为您的网格正在从同一行读取数据,而该行有 1/2 的时间是空的。
帧缓冲区 0(数据在顶部):
帧缓冲区 1(底部数据):
我正在处理 THREE.js 中使用帧缓冲区对象的场景。出于某种原因,我看到了疯狂的闪烁/频闪效果。 (如果注释掉以下行,闪光灯就会消失: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>
有谁知道造成这种频闪的原因是什么?两个渲染目标是否不同步?
我正在查看您的 renderTargetA 和 B 的输出,看起来一个在图像的顶行输出数据,而第二个在底行。我最好的猜测是您的“乒乓”效果正在发挥作用,但您看到闪烁,因为您的网格正在从同一行读取数据,而该行有 1/2 的时间是空的。
帧缓冲区 0(数据在顶部):
帧缓冲区 1(底部数据):