如何为带有父级的缩放对象制作CSS2DRenderer标签,使其表现得像SpriteMaterial一样?

时间:2019-02-26 00:08:08

标签: three.js

我有一个现有的项目,一个太阳系的模型,其中的行星和卫星都标有文字。为了创建标签,我使用了精灵(SpriteMaterial),并且效果很好。 (下面是结果-您可以放大以更仔细地查看标签)

如您所见,标签很好地呈现在其父级旁边,并且缩放比例也与其父级的缩放比例有关。

var camera, scene, renderer;

init();
animate();

function init() {

  camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, .1, 2000);
  camera.position.z = 3;

  scene = new THREE.Scene();

  var object;
  var label;

  var ambientLight = new THREE.AmbientLight(0xcccccc, 0.4);
  scene.add(ambientLight);

  var pointLight = new THREE.PointLight(0xffffff, 0.8);
  camera.add(pointLight);
  scene.add(camera);

  var map = new THREE.TextureLoader().load('https://threejs.org/examples/textures/UV_Grid_Sm.jpg');
  map.wrapS = map.wrapT = THREE.RepeatWrapping;
  map.anisotropy = 16;

  var material = new THREE.MeshPhongMaterial({
    map: map,
    side: THREE.DoubleSide
  });

  //

  sun = new THREE.Mesh(new THREE.SphereBufferGeometry(1, 20, 10), material);
  sun.position.set(0, 0, 0);
  label = makeTextSprite("Sun");
  sun.add(label)
  scene.add(sun);

  ratioOfJupiterToSun = 0.1
  jupiter = new THREE.Mesh(new THREE.SphereBufferGeometry(1, 20, 10), material);
  jupiter.position.set(1.5, 0, 0);
  jupiter.scale.set(ratioOfJupiterToSun, ratioOfJupiterToSun, ratioOfJupiterToSun)
  label = makeTextSprite("Jupiter");
  jupiter.add(label)
  sun.add(jupiter);

  ratioOfGanymedeToJupiter = 0.1
  ganymede = new THREE.Mesh(new THREE.SphereBufferGeometry(1, 20, 10), material);
  ganymede.scale.set(ratioOfGanymedeToJupiter, ratioOfGanymedeToJupiter, ratioOfGanymedeToJupiter)
  ganymede.position.set(4, 0, 0);
  label = makeTextSprite("Ganymede");
  ganymede.add(label)
  jupiter.add(ganymede);



  //

  renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);

  const controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.minDistance = 1.7
  controls.maxDistance = 3

  document.body.appendChild(renderer.domElement);

  window.addEventListener('resize', onWindowResize, false);

}

function onWindowResize() {

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);

}

//

function animate() {

  requestAnimationFrame(animate);

  render();

}

function render() {

  camera.lookAt(scene.position);

  renderer.render(scene, camera);

}

function makeTextSprite(message, centerX = 1.5, centerY = 1.7) {
  var canvas = document.createElement('canvas');
  canvas.width = 256;
  canvas.height = 256;
  var ctx = canvas.getContext("2d");
  ctx.font = "38pt Arial";
  ctx.fillStyle = "white";
  ctx.textAlign = "left";
  ctx.fillText(message, 0, 38);
  var tex = new THREE.Texture(canvas);
  tex.needsUpdate = true;
  var spriteMat = new THREE.SpriteMaterial({
    map: tex
  });
  var sprite = new THREE.Sprite(spriteMat);
  sprite.center.set(centerX, centerY);
  return sprite;
}
body {
  color: #eee;
  font-family: Monospace;
  font-size: 13px;
  text-align: center;
  background-color: #000;
  margin: 0px;
  padding: 0px;
  overflow: hidden;
}

#info {
  position: absolute;
  top: 0px;
  width: 100%;
  padding: 5px;
}

a {
  color: #0080ff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/CSS2DRenderer.js"></script>

精灵的问题在于它们使用栅格图形进行标注。有时质量不够。

这就是为什么我想用THREE.CSS2DRenderer重现同样的效果。不幸的是,我获得的最佳近似值是

var camera, scene, renderer, labelRenderer;

init();
animate();

function init() {

  camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, .1, 2000);
  camera.position.z = 3;

  scene = new THREE.Scene();

  var object;
  var label;

  var ambientLight = new THREE.AmbientLight(0xcccccc, 0.4);
  scene.add(ambientLight);

  var pointLight = new THREE.PointLight(0xffffff, 0.8);
  camera.add(pointLight);
  scene.add(camera);

  var map = new THREE.TextureLoader().load('https://threejs.org/examples/textures/UV_Grid_Sm.jpg');
  map.wrapS = map.wrapT = THREE.RepeatWrapping;
  map.anisotropy = 16;

  var material = new THREE.MeshPhongMaterial({
    map: map,
    side: THREE.DoubleSide
  });

  //

  sun = new THREE.Mesh(new THREE.SphereBufferGeometry(1, 20, 10), material);
  sun.position.set(0, 0, 0);
  label = makeTextLabel("Sun");
  sun.add(label)
  scene.add(sun);

  ratioOfJupiterToSun = 0.1
  jupiter = new THREE.Mesh(new THREE.SphereBufferGeometry(1, 20, 10), material);
  jupiter.position.set(1.5, 0, 0);
  jupiter.scale.set(ratioOfJupiterToSun, ratioOfJupiterToSun, ratioOfJupiterToSun)
  label = makeTextLabel("Jupiter");
  jupiter.add(label)
  sun.add(jupiter);

  ratioOfGanymedeToJupiter = 0.1
  ganymede = new THREE.Mesh(new THREE.SphereBufferGeometry(1, 20, 10), material);
  ganymede.scale.set(ratioOfGanymedeToJupiter, ratioOfGanymedeToJupiter, ratioOfGanymedeToJupiter)
  ganymede.position.set(4, 0, 0);
  label = makeTextLabel("Ganymede");
  ganymede.add(label)
  jupiter.add(ganymede);

  renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);

  const controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.minDistance = 2
  controls.maxDistance = 3

  document.body.appendChild(renderer.domElement);

  labelRenderer = new THREE.CSS2DRenderer();
  labelRenderer.setSize(window.innerWidth, window.innerHeight);
  labelRenderer.domElement.style.position = 'absolute';
  labelRenderer.domElement.style.top = '0';
  labelRenderer.domElement.style.pointerEvents = 'none';
  document.body.appendChild(labelRenderer.domElement);

  window.addEventListener('resize', onWindowResize, false);

}

function onWindowResize() {

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);
  labelRenderer.setSize(window.innerWidth, window.innerHeight);
}

//

function animate() {

  requestAnimationFrame(animate);

  render();

}

function render() {

  var timer = Date.now() * 0.0005;

  // camera.position.x = Math.sin(timer) * 400;
  // camera.position.z = Math.cos(timer) * 400;

  camera.lookAt(scene.position);

  scene.traverse(function(object) {

    if (object.isMesh === true) {

      //object.rotation.x = timer;
      //object.rotation.z = timer;

    }

  });

  renderer.render(scene, camera);
  labelRenderer.render(scene, camera);
}

function makeTextLabel(label) {
  var text = document.createElement('div');
  text.style.color = 'rgb(255, 255, 255)';
  text.style.marginTop = '1.7em';
  text.style.marginLeft = '-4em';
  text.className = 'label';
  text.textContent = label;
  return new THREE.CSS2DObject(text);
}
body {
  color: #eee;
  font-family: Monospace;
  font-size: 13px;
  text-align: center;
  background-color: #000;
  margin: 0px;
  padding: 0px;
  overflow: hidden;
}

a {
  color: #0080ff;
}

.label {
  color: #FFF;
  font-family: sans-serif;
  font-size: 44px;
  padding: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/CSS2DRenderer.js"></script>

如您所见,它部分起作用:标签尊重父母的坐标系(世界矩阵)。这就是为什么他们的立场是相对的。但是规模不是。所有标签的尺寸都相同。

我还希望标签稍微移到左下角,就像我用Sprite一样。但是我能找到的最好方法是:

text.style.marginTop = '3em';
text.style.marginLeft = '-4em';

这显然也没有考虑到父母的规模。

您对如何使其与原版相似有任何想法吗?

jsfiddle中两个示例的代码:

r。 98

编辑:隐藏摘要以使帖子更具可读性

1 个答案:

答案 0 :(得分:0)

报告不是错误。问题是您正在使用CSS2DRenderer,但听起来您正在寻找CSS3DRenderer提供的行为。您可以在动作in the examples section中看到它,以及放大/缩小时的行为。

您可以详细了解in the docs。请记住,您还必须创建CSS3DObject而不是2D。然后,您可以将它们放置在所需的任何位置,就像其他3dObject:.position.set(x, y, z);scale.set(x, y, z);

更新:

如果必须使用CSS2D,则可以通过计算每个标签到相机的距离来执行自己的缩放。

// Create a Vec3 that holds the label's position
var start = new Vector3();
start.copy(label.position);

// Create a var that holds the distance to the camera
var dist = start.distanceTo(camera.position);

// Apply falloff, then turn that into the label's scale
var size = 1 / dist;

现在,您可以使用size通过CSS transform: scale(x);或其他方式缩放标签。