Transparency within instanced shapes

2020-05-06 09:04发布

I've been playing with THREE.InstancedBufferGeometry. I finally got an example to work, and have now been playing with the shader. The first thing I tried was setting transparency. My example code is below.

The initial state, and from a host of other camera angles (e.g. drag your mouse to the left), the transparency doesn't seem to have any effect. But then at other camera angles (e.g. reload and drag your mouse to the right), the shapes clearly overlap, which is what I was expecting.

Is depth-sorting handled differently for instanced shapes, or am I doing something wrong, or missing something? Do I somehow need to update the shapes so the camera knows their proper depth in the scene?

var cubeGeo = new THREE.InstancedBufferGeometry().copy(new THREE.BoxBufferGeometry(10, 10, 10));
//cubeGeo.maxInstancedCount = 8;

cubeGeo.addAttribute("cubePos", new THREE.InstancedBufferAttribute(new Float32Array([
  25, 25, 25,
  25, 25, -25, -25, 25, 25, -25, 25, -25,
  25, -25, 25,
  25, -25, -25, -25, -25, 25, -25, -25, -25
]), 3, 1));

var vertexShader = [
  "precision highp float;",
  "",
  "uniform mat4 modelViewMatrix;",
  "uniform mat4 projectionMatrix;",
  "",
  "attribute vec3 position;",
  "attribute vec3 cubePos;",
  "",
  "void main() {",
  "",
  "	gl_Position = projectionMatrix * modelViewMatrix * vec4( cubePos + position, 1.0 );",
  "",
  "}"
].join("\n");
var fragmentShader = [
  "precision highp float;",
  "",
  "void main() {",
  "",
  "	gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5);",
  "",
  "}"
].join("\n");

var mat = new THREE.RawShaderMaterial({
  uniforms: {},
  vertexShader: vertexShader,
  fragmentShader: fragmentShader,
  transparent: true
});

var mesh = new THREE.Mesh(cubeGeo, mat);

scene.add(mesh);
html * {
  padding: 0;
  margin: 0;
  width: 100%;
  overflow: hidden;
}

#host {
  width: 100%;
  height: 100%;
}
<script src="http://threejs.org/build/three.js"></script>
<script src="http://threejs.org/examples/js/controls/TrackballControls.js"></script>
<script src="http://threejs.org/examples/js/libs/stats.min.js"></script>
<div id="host"></div>

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

  var renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setSize(WIDTH, HEIGHT);
  document.getElementById('host').appendChild(renderer.domElement);

  var stats = new Stats();
  stats.domElement.style.position = 'absolute';
  stats.domElement.style.top = '0';
  document.body.appendChild(stats.domElement);


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

  var trackballControl = new THREE.TrackballControls(camera, renderer.domElement);
  trackballControl.rotateSpeed = 2.0; // need to speed it up a little

  var scene = new THREE.Scene();

  var light = new THREE.PointLight(0xffffff, 1, Infinity);
  camera.add(light);

  scene.add(light);

  function render() {
    if (typeof updateVertices !== "undefined") {
      updateVertices();
    }
    renderer.render(scene, camera);
    stats.update();
  }

  function animate() {
    requestAnimationFrame(animate);
    trackballControl.update();
    render();
  }

  animate();
</script>

标签: three.js
2条回答
走好不送
2楼-- · 2020-05-06 09:50

You are using InstancedBufferGeometry with meshes that are translucent.

The instances are rendered in the order they appear in the buffer. The faces of each instance are rendered in the ordered specified by the geometry.

Consequently, if you use instancing with translucency, you will likely have artifacts depending on the viewing angle.

Depending on your use case, you can try setting material.depthWrite = false, but that can lead to other artifacts.

If your mesh textures have areas of complete transparency (rather than partial) you should be able to use material.alphaTest to discard unwanted fragments without artifacts.

three.js r.84

查看更多
萌系小妹纸
3楼-- · 2020-05-06 09:50

After discussion with WestLangley in the comments, I added a sorter for my instances. It sorts the instances positions based on their distance from the camera.

(Side-note: If I had any other THREE.InstancedBufferAttributes I would need to re-order them at the same time.)

The biggest downside to this is that it becomes more and more expensive as the scene gets bigger, both in instanced and non-instanced shapes.

// Instances Sorter, called each frame
function sortObjectInstances(obj) {
  if (obj.geometry) {
    if (obj.geometry instanceof THREE.InstancedBufferGeometry) {
      var array = obj.geometry.attributes.cubePos.array,
        vecArray = [];
      for (var i = 0, l = array.length / 3; i < l; ++i) {
        vecArray.push(new THREE.Vector3(array[(i * 3)], array[(i * 3) + 1], array[(i * 3) + 2]));
      }
      vecArray.sort(function(a, b) {
        if (a.distanceTo(camera.position) > b.distanceTo(camera.position)) {
          return -1;
        }
        if (a.distanceTo(camera.position) < b.distanceTo(camera.position)) {
          return 1;
        }
        return 0;
      });
      for (var i = 0, l = vecArray.length; i < l; ++i) {
        array[(i * 3)] = vecArray[i].x;
        array[(i * 3) + 1] = vecArray[i].y;
        array[(i * 3) + 2] = vecArray[i].z;
      }
      obj.geometry.attributes.cubePos.needsUpdate = true;
    }
  } else {
    for (var i = 0, l = obj.children.length; i < l; ++i) {
      sortObjectInstances(obj.children[i]);
    }
  }
}

var cubeGeo = new THREE.InstancedBufferGeometry().copy(new THREE.BoxBufferGeometry(10, 10, 10));
//cubeGeo.maxInstancedCount = 8;

cubeGeo.addAttribute("cubePos", new THREE.InstancedBufferAttribute(new Float32Array([
  25, 25, 25,
  25, 25, -25, -25, 25, 25, -25, 25, -25,
  25, -25, 25,
  25, -25, -25, -25, -25, 25, -25, -25, -25
]), 3, 1));

var vertexShader = [
  "precision highp float;",
  "",
  "uniform mat4 modelViewMatrix;",
  "uniform mat4 projectionMatrix;",
  "",
  "attribute vec3 position;",
  "attribute vec3 cubePos;",
  "",
  "void main() {",
  "",
  "	gl_Position = projectionMatrix * modelViewMatrix * vec4( cubePos + position, 1.0 );",
  "",
  "}"
].join("\n");
var fragmentShader = [
  "precision highp float;",
  "",
  "void main() {",
  "",
  "	gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5);",
  "",
  "}"
].join("\n");

var mat = new THREE.RawShaderMaterial({
  uniforms: {},
  vertexShader: vertexShader,
  fragmentShader: fragmentShader,
  transparent: true
});

var mesh = new THREE.Mesh(cubeGeo, mat);

scene.add(mesh);
html * {
  padding: 0;
  margin: 0;
  width: 100%;
  overflow: hidden;
}

#host {
  width: 100%;
  height: 100%;
}
<script src="http://threejs.org/build/three.js"></script>
<script src="http://threejs.org/examples/js/controls/TrackballControls.js"></script>
<script src="http://threejs.org/examples/js/libs/stats.min.js"></script>
<div id="host"></div>

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

  var renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setSize(WIDTH, HEIGHT);
  document.getElementById('host').appendChild(renderer.domElement);

  var stats = new Stats();
  stats.domElement.style.position = 'absolute';
  stats.domElement.style.top = '0';
  document.body.appendChild(stats.domElement);


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

  var trackballControl = new THREE.TrackballControls(camera, renderer.domElement);
  trackballControl.rotateSpeed = 2.0; // need to speed it up a little

  var scene = new THREE.Scene();

  var light = new THREE.PointLight(0xffffff, 1, Infinity);
  camera.add(light);

  scene.add(light);

  function render() {
    if (typeof sortObjectInstances !== "undefined") {
      sortObjectInstances(scene); // Sort the instances
    }
    renderer.render(scene, camera);
    stats.update();
  }

  function animate() {
    requestAnimationFrame(animate);
    trackballControl.update();
    render();
  }

  animate();
</script>

查看更多
登录 后发表回答