readRenderTargetPixels results all zeroes

2019-08-08 14:48发布

The problem:

I'm trying to read the pixel values from a render target, but the values are all coming back as zeroes. Am I doing something wrong, or is this functionality broken for the version I'm using?

What I have:

An important note is that I'm stuck using r76. I know, I know, it upsets me, too.

The code below is literally all I have. Just click the "GO" button in the upper-right, and it will produce an alert that says whether or not the entire render target buffer was zeroes or not.

What I've tried:

I did try it with the latest three.js (r86), and the exact same code works correctly.

I also tried accounting for any async issues, by arranging my render function to either render to the canvas (works fine), or to the render target (still all zeroes), based on a flag I set outside the render loop.

function render() {
    light.position.copy(camera.position);
    if(doRenderTarget){
        toRenderTarget();
        doRenderTarget = false;
    }
    else{
        renderer.clear();
        renderer.render(scene, camera);
    }
    controls.update();
}

The code:

Using r76:

var hostDiv, scene, renderer, camera, controls, light;

var WIDTH = window.innerWidth,
  HEIGHT = window.innerHeight,
  FOV = 35,
  NEAR = 1,
  FAR = 1000;

var target = new THREE.WebGLRenderTarget();

function toRenderTarget() {
  var size = renderer.getSize();
  var w = WIDTH;
  var h = HEIGHT;
  target.setSize(w, h);
  renderer.clearTarget(target, true, true, true);
  renderer.render(scene, camera, target);
  var renderTargetPixelBuffer = new Uint8Array(w * h * 4);
  renderer.readRenderTargetPixels(target, 0, 0, w, h, renderTargetPixelBuffer);

  var allZeroes = renderTargetPixelBuffer.reduce(function(prev, item) {
    return prev && (item === 0.0);
  }, true);
  if (allZeroes) {
    alert("All zeroes!");
  } else {
    alert("Never mind, the data is okay.");
  }
}
document.getElementById("go").addEventListener("click", toRenderTarget);

function init() {
  hostDiv = document.createElement('div');
  hostDiv.setAttribute('id', 'host');
  document.body.appendChild(hostDiv);

  renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: true
  });
  renderer.setSize(WIDTH, HEIGHT);
  hostDiv.appendChild(renderer.domElement);

  camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
  camera.position.z = 500;

  controls = new THREE.TrackballControls(camera, renderer.domElement);
  //controls.rotateSpeed = 5.0;
  controls.dynamicDampingFactor = 0.5;
  controls.handleResize();

  light = new THREE.PointLight(0xffffff, 1, Infinity);
  light.position.copy(camera.position);

  scene = new THREE.Scene();
  scene.add(camera);
  scene.add(light);

  var cube = new THREE.Mesh(
    new THREE.BoxBufferGeometry(50, 50, 50),
    new THREE.MeshLambertMaterial({
      color: "red"
    })
  );
  scene.add(cube);

  animate();
}

function render() {
  light.position.copy(camera.position);
  renderer.render(scene, camera);
  controls.update();
}

function animate() {
  requestAnimationFrame(animate);
  render();
}

init();

window.onresize = function() {
  WIDTH = window.innerWidth;
  HEIGHT = window.innerHeight;
  if (renderer && camera && controls) {
    renderer.setSize(WIDTH, HEIGHT);
    camera.aspect = WIDTH / HEIGHT;
    camera.updateProjectionMatrix();
    controls.handleResize();
  }
}
body {
  position: relative;
}
<!-- r76 -->
<script src="https://cdn.rawgit.com/mrdoob/three.js/6400f2c9b6ee58e01c005a66f00c7cd1113752aa/build/three.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/6400f2c9b6ee58e01c005a66f00c7cd1113752aa/examples/js/controls/TrackballControls.js"></script>

<input type="button" id="go" value="GO" style="position: absolute; top: 0; right: 0" ; />

Using r86:

var hostDiv, scene, renderer, camera, controls, light;

var WIDTH = window.innerWidth,
  HEIGHT = window.innerHeight,
  FOV = 35,
  NEAR = 1,
  FAR = 1000;

var target = new THREE.WebGLRenderTarget();

function toRenderTarget() {
  var size = renderer.getSize();
  var w = WIDTH;
  var h = HEIGHT;
  target.setSize(w, h);
  renderer.clearTarget(target, true, true, true);
  renderer.render(scene, camera, target);
  var renderTargetPixelBuffer = new Uint8Array(w * h * 4);
  renderer.readRenderTargetPixels(target, 0, 0, w, h, renderTargetPixelBuffer);

  var allZeroes = renderTargetPixelBuffer.reduce(function(prev, item) {
    return prev && (item === 0.0);
  }, true);
  if (allZeroes) {
    alert("All zeroes!");
  } else {
    alert("Never mind, the data is okay.");
  }
}
document.getElementById("go").addEventListener("click", toRenderTarget);

function init() {
  hostDiv = document.createElement('div');
  hostDiv.setAttribute('id', 'host');
  document.body.appendChild(hostDiv);

  renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: true
  });
  renderer.setSize(WIDTH, HEIGHT);
  hostDiv.appendChild(renderer.domElement);

  camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
  camera.position.z = 500;

  controls = new THREE.TrackballControls(camera, renderer.domElement);
  //controls.rotateSpeed = 5.0;
  controls.dynamicDampingFactor = 0.5;
  controls.handleResize();

  light = new THREE.PointLight(0xffffff, 1, Infinity);
  light.position.copy(camera.position);

  scene = new THREE.Scene();
  scene.add(camera);
  scene.add(light);

  var cube = new THREE.Mesh(
    new THREE.BoxBufferGeometry(50, 50, 50),
    new THREE.MeshLambertMaterial({
      color: "red"
    })
  );
  scene.add(cube);

  animate();
}

function render() {
  light.position.copy(camera.position);
  renderer.render(scene, camera);
  controls.update();
}

function animate() {
  requestAnimationFrame(animate);
  render();
}

init();

window.onresize = function() {
  WIDTH = window.innerWidth;
  HEIGHT = window.innerHeight;
  if (renderer && camera && controls) {
    renderer.setSize(WIDTH, HEIGHT);
    camera.aspect = WIDTH / HEIGHT;
    camera.updateProjectionMatrix();
    controls.handleResize();
  }
}
body {
  position: relative;
}
<!-- r86 -->
<script src="https://threejs.org/build/three.js"></script>
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>

<input type="button" id="go" value="GO" style="position: absolute; top: 0; right: 0" ; />

Using r76 and deferring render until the next animation loop:

var hostDiv, scene, renderer, camera, controls, light;

var WIDTH = window.innerWidth,
  HEIGHT = window.innerHeight,
  FOV = 35,
  NEAR = 1,
  FAR = 1000;

var target = new THREE.WebGLRenderTarget();

var doRenderTarget = false;
function go(){
  doRenderTarget = true;
}

function toRenderTarget() {
  var size = renderer.getSize();
  var w = WIDTH;
  var h = HEIGHT;
  target.setSize(w, h);
  renderer.clearTarget(target, true, true, true);
  renderer.render(scene, camera, target);
  var renderTargetPixelBuffer = new Uint8Array(w * h * 4);
  renderer.readRenderTargetPixels(target, 0, 0, w, h, renderTargetPixelBuffer);

  var allZeroes = renderTargetPixelBuffer.reduce(function(prev, item) {
    return prev && (item === 0.0);
  }, true);
  if (allZeroes) {
    alert("All zeroes!");
  } else {
    alert("Never mind, the data is okay.");
  }
}
document.getElementById("go").addEventListener("click", toRenderTarget);

function init() {
  hostDiv = document.createElement('div');
  hostDiv.setAttribute('id', 'host');
  document.body.appendChild(hostDiv);

  renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: true
  });
  renderer.setSize(WIDTH, HEIGHT);
  hostDiv.appendChild(renderer.domElement);

  camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
  camera.position.z = 500;

  controls = new THREE.TrackballControls(camera, renderer.domElement);
  //controls.rotateSpeed = 5.0;
  controls.dynamicDampingFactor = 0.5;
  controls.handleResize();

  light = new THREE.PointLight(0xffffff, 1, Infinity);
  light.position.copy(camera.position);

  scene = new THREE.Scene();
  scene.add(camera);
  scene.add(light);

  var cube = new THREE.Mesh(
    new THREE.BoxBufferGeometry(50, 50, 50),
    new THREE.MeshLambertMaterial({
      color: "red"
    })
  );
  scene.add(cube);

  animate();
}

function render() {
  light.position.copy(camera.position);
  if(doRenderTarget){
    toRenderTarget();
  }
  else{
    renderer.render(scene, camera);
  }
  controls.update();
}

function animate() {
  requestAnimationFrame(animate);
  render();
}

init();

window.onresize = function() {
  WIDTH = window.innerWidth;
  HEIGHT = window.innerHeight;
  if (renderer && camera && controls) {
    renderer.setSize(WIDTH, HEIGHT);
    camera.aspect = WIDTH / HEIGHT;
    camera.updateProjectionMatrix();
    controls.handleResize();
  }
}
body {
  position: relative;
}
<!-- r76 -->
<script src="https://cdn.rawgit.com/mrdoob/three.js/6400f2c9b6ee58e01c005a66f00c7cd1113752aa/build/three.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/6400f2c9b6ee58e01c005a66f00c7cd1113752aa/examples/js/controls/TrackballControls.js"></script>

<input type="button" id="go" value="GO" style="position: absolute; top: 0; right: 0" ; />

Follow-up:

The key here appears to be reading directly from the GL context, but the root cause of my problem is much, much simpler. I wondered what was wrong with how readRenderTargetPixels was doing its read, because it literally calls the same GL method. It was right there in front of my face the whole time. Blocking the _gl.readPixels call:

r76:

if ( ( x > 0 && x <= ( renderTarget.width - width ) ) && ( y > 0 && y <= ( renderTarget.height - height ) ) ) {

r86:

if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {

The function in r76 wasn't allowing x or y to be 0, and I was setting both to zero. The r86 function allows both to be 0, which is why it worked.

Since r76 is super-old, I'm not going to bother submitting a bug report or a fix. Thanks, @evil professeur, for helping highlight the issue!

标签: three.js
1条回答
Bombasti
2楼-- · 2019-08-08 15:20

The problem is that by the time you call the function, the buffer will be cleared. What you have to do is to call it just after render. The easiest way is to set a flag on click that informing the rendering function that you want to retrieve the pixels and have it call toRenderTarget immediately after render. the changes would look like that:

var getRenderedPixels = false;
function setAwaitFlag() { 
  getRenderedPixels = true;
}

document.getElementById("go").addEventListener("click", setAwaitFlag);

and then in the render function

function render() {
  light.position.copy(camera.position);
  renderer.render(scene, camera);
  if (getRenderedPixels) {
    toRenderTarget();
    getRenderedPixels = false;
  }
  controls.update();
}

Aside from that I used gl.ReadPixels instead of readRenderTargetPixels and it clicked. here's the change:

var gl = renderer.getContext(),
pixels = new Uint8Array(w * h * 4);
gl.readPixels(0,0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, renderTargetPixelBuffer);

and here's the fiddle https://jsfiddle.net/c06b5p8u/1/

you should narrow the number of pixels read to improve performance, though. I was stuck on this one a while back too.

查看更多
登录 后发表回答