11

我在找什么

在填充整个场景的 three.js 场景中显示一个网格。在这种情况下,场景是整个窗口。

这个网格代表一个 3D 表面,可以使用鼠标四处移动。THREE.TrackballControls这个网格面向相机,所以最初它看起来像一个平面 (2D) 表面,直到用鼠标拉动轨迹球。

网格线的宽度应等于渲染器的宽度。

我做了什么

我已经为我到目前为止所做的工作设置了一个工作jsFiddle 。

首先我找到了场景的边界(所有这些都在 jsFiddle 中),

App = function(sceneContainerName) {
   this.sceneContainerName = sceneContainerName;

   this.SCREEN_WIDTH = window.innerWidth;
   this.SCREEN_HEIGHT = window.innerHeight;

   this.MAX_X = this.SCREEN_WIDTH / 2;
   this.MIN_X = 0 - (this.SCREEN_WIDTH / 2);
   this.MAX_Y = this.SCREEN_HEIGHT / 2;
   this.MIN_Y = 0 - (this.SCREEN_HEIGHT / 2);

   this.NUM_HORIZONTAL_LINES = 50;

   this.init();

};

设置三个.js

init: function() {
   // init scene
   this.scene = new THREE.Scene();

   // init camera
   // View Angle, Aspect, Near, Far
   this.camera = new THREE.PerspectiveCamera(45, this.SCREEN_WIDTH / this.SCREEN_HEIGHT, 1, 10000);
   // set camera position
   this.camera.position.z = 1000;
   this.camera.position.y = 0;

   // add the camera to the scene
   this.scene.add(this.camera);

   this.projector = new THREE.Projector();

   // init renderer
   this.renderer = new THREE.CanvasRenderer();
   // start the renderer
   this.renderer.setSize(this.SCREEN_WIDTH, this.SCREEN_HEIGHT);

   this.drawGrid(this.NUM_HORIZONTAL_LINES);

   this.trackball = new THREE.TrackballControls(this.camera, this.renderer.domElement);
   this.trackball.staticMoving = true;

   var me = this;

   this.trackball.addEventListener('change', function() {
       me.render();
   });

   // attach the render-supplied DOM element
   var container = document.getElementById(this.sceneContainerName);
   container.appendChild(this.renderer.domElement);

   this.animate();
},

这些函数为每个屏幕角提供了一个向量,

getNWScreenVector: function() {
    return new THREE.Vector3(this.MIN_X, this.MAX_Y, 0);
},

getNEScreenVector: function() {
    return new THREE.Vector3(this.MAX_X, this.MAX_Y, 0);
},

getSWScreenVector: function() {
    return new THREE.Vector3(this.MIN_X, this.MIN_Y, 0);
},

getSEScreenVector: function() {
    return new THREE.Vector3(this.MAX_X, this.MIN_Y, 0);
},

我创建了一些几何图形来表示屏幕最顶部的一条线,并尝试从顶部开始绘制线条并向下到屏幕底部。

// drawGrid will determine blocksize based on the 
// amount of horizontal gridlines to draw
drawGrid: function(numHorizontalGridLines) {

    // Determine the size of a grid block (square)
    this.gridBlockSize = this.SCREEN_HEIGHT / numHorizontalGridLines;

    var geometry = new THREE.Geometry();
    geometry.vertices.push(this.getNWScreenVector());
    geometry.vertices.push(this.getNEScreenVector());

    var material = new THREE.LineBasicMaterial({
        color: 0x000000,
        opacity: 0.2
    });

    for (var c = 0; c <= numHorizontalGridLines; c++) {
        var line = new THREE.Line(geometry, material);
        line.position.y = this.MAX_Y - (c * this.gridBlockSize);
        this.scene.add(line);
    }
}

问题

此方法不起作用,在 jsFiddle 中,第一行从屏幕开始,并且行的宽度不填充屏幕宽度。

4

4 回答 4

29

从 ThreeJS r57 开始,有一个名为 GridHelper 的助手,您可以使用它轻松绘制漂亮的网格,就像任何其他几何对象一样。

GridHelper 有 2 个参数。第一个是网格的大小,第二个是两行之间的步长

下面是在场景中绘制网格的代码,size = 100,step = 10

var grid = new THREE.GridHelper(100, 10);
scene.add(grid);

在您的情况下,您可以避免使用名为 drawGrid 的方法并直接将其替换为上述两行代码,或者您可以在 drawgrid 方法中添加上述两行代码。

以下链接中提供了一个实时示例

网格助手示例

于 2014-11-28T12:58:23.373 回答
12

你可以像这样画一个网格。

// each square
var planeW = 50; // pixels
var planeH = 50; // pixels 
var numW = 50; // how many wide (50*50 = 2500 pixels wide)
var numH = 50; // how many tall (50*50 = 2500 pixels tall)
var plane = new THREE.Mesh(
    new THREE.PlaneGeometry( planeW*numW, planeH*numH, planeW, planeH ),
    new THREE.MeshBasicMaterial( {
        color: 0x000000,
        wireframe: true
    } )
);

scene.add(plane);
于 2012-11-23T06:14:19.147 回答
11

因此,您的代码中有两个错误。

首先,您从线开始MAX_Y,然后将每条线放在最后一条线下方的固定距离处。相对较小的错误是 ingetNWScreenVector并且getNEScreenVector您将线的顶点放在高度为MAX_Y, 然后在

line.position.y = this.MAX_Y - (c * this.gridBlockSize);

您正在向 的行添加翻译MAX_Y - (c * this.gridBlockSize),这给出了 的最终 y 位置MAX_Y + MAX_Y - (c * this.gridBlockSize),这是一个MAX_Y太多了。getNWScreenVector如果您的程序以从and开始的行开始有意义getNEScreenVector,那么您应该将该line.position.y行更改为 just

line.position.y = -c * this.gridBlockSize;

您可以看到这些行现在以jsFiddle为中心,但它们的大小仍然不正确。这是因为您没有考虑到您的场景是透视的。您的线条都将其 z 坐标设置为 0,因此当您设置 时this.camera.position.z = 1000,它们距离相机 1000 个单位。没有理由假设宽度与画布像素宽度相同的东西在从 1000 个单位远的角度绘制时将具有相同的宽度。

相反,我们可以计算它们需要多大。我不会在这里详细解释透视图,但我们可以计算出线条需要覆盖多大的区域才能覆盖屏幕。您在相机构造函数中指定了 45 度的垂直 FOV,相机与线之间的距离为 1000。如果您深入了解它如何创建透视矩阵, Three.js 基本上会显示解决方案:makePerspective

首先,我们需要屏幕上半部分的垂直距离,因为 0 位于屏幕的中心。Math.tan(45 / 2 * (Math.PI / 180))(半角的切线,转换为弧度)给出垂直距离除以距相机的距离,因此您可以计算高度

halfDisplayHeight = 1000 * Math.tan(45 / 2 * (Math.PI / 180));

或加倍到

this.DISPLAY_HEIGHT = 2 * 1000 * Math.tan(45 / 2 * (Math.PI / 180));

除非画布是正方形,否则水平FOV是一样的,但是线条区域的宽高比会与屏幕的宽高比成正比。像 three.js 一样,你可以乘以你也提供给相机构造函数的纵横比来计算宽度:

this.DISPLAY_WIDTH = this.DISPLAY_HEIGHT * (this.SCREEN_WIDTH / this.SCREEN_HEIGHT);

这些是您应该用于放置线条的值。全部一起:

this.DISPLAY_HEIGHT = 2 * 1000 * Math.tan(45 / 2 * (Math.PI / 180));
this.DISPLAY_WIDTH = this.DISPLAY_HEIGHT * (this.SCREEN_WIDTH / this.SCREEN_HEIGHT);
this.MAX_X = this.DISPLAY_WIDTH / 2;
this.MIN_X = 0 - (this.DISPLAY_WIDTH / 2);
this.MAX_Y = this.DISPLAY_HEIGHT / 2;
this.MIN_Y = 0 - (this.DISPLAY_HEIGHT / 2);

最后,您需要将线条分布在整个区域,因此您应该设置

this.gridBlockSize = this.DISPLAY_HEIGHT / numHorizontalGridLines;

你可以在这里看到工作:http: //jsfiddle.net/pa46hxwo/

但是,还有另一种方法可以做到这一点,即保持线条相同,但将相机移近线条,使线条的宽度恰好等于该距离处画布的像素宽度。该公式只是对上面的 的修改DISPLAY_HEIGHT,但我们不是求解高度,而是求解高度等于屏幕高度时的距离:

this.camera.position.z = this.SCREEN_HEIGHT / (2 * Math.tan(45 / 2 * (Math.PI / 180)));

你可以在这里看到:http: //jsfiddle.net/0jtykgrL/

这是对您的代码的一个小得多的更改,但这意味着相机位置将根据画布的大小而改变,这可能会影响您需要精确放置的其他内容,因此您可以选择。

于 2012-06-21T00:13:30.890 回答
0

该线程有助于对应用于 A-Frame 的技术进行变体,并稍作改动;根据场景子项的边界框动态创建和调整网格大小。

注意:必须调用以下方法来获取最初为空的边界框坐标,这与为您预先计算的边界球体不同:

' boundingBox.geometry.computeBoundingBox(); '

可以在此处找到此“场景 ario”(双关语)的 Codepen:https ://codepen.io/ubermario/pen/JvZMPg

    <!DOCTYPE html>
    <html lang="en" >

    <head>

      <meta charset="UTF-8">
      <link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-8ea04875e70c4b0bb41da869e81236e54394d63638a1ef12fa558a4a835f1164.ico" />
      <link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-f2d2b6d2c61838f7e76325261b7195c27224080bc099486ddd6dccb469b8e8e6.svg" color="#111" />
      <title>CodePen -  A-Frame Dynamically Sized GridHelper</title>     
    </head>

    <body translate="no" >

      <script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>
      <a-scene>
        <a-entity>
          <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
          <a-box position="-1 0.5 -3" rotation="0 45 0" width="1" height="1" depth="1" color="#4CC3D9"></a-box>
          <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
          <!-- replaced with gridhelper>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane> -->
          <!-- Remove sky; it distorts the dimensions of just the sphere, box, cylinder objects
          <a-sky color="#ECECEC"></a-sky>
          -->
          <a-entity>
      </a-scene>




        <script >
          AFRAME.registerComponent("cmpGridHelper", {
      schema: {
        size: { default: 5, type: "int" },
        divisions: { default: 10, type: "int" },
        colorCenterLine: { default: "red", type: "color" },
        colorGrid: { default: "black", type: "color" },
        x: { default: 0, type: "number" },
        y: { default: 0, type: "number" },
        z: { default: 0, type: "number" }
      },
      init: function() {
        var entity = this.el.object3D,
          schema = this.data,
          grid = new THREE.GridHelper(
            schema.size,
            schema.divisions,
            schema.colorCenterLine,
            schema.colorGrid
          );
        grid.name = "gridHelper";
        entity.add(grid);
      },
      update: function(oldData) {
        var entity = this.el.object3D,
          grid = entity.getObjectByName("gridHelper"),
          schema = this.data;
        grid.position.set(schema.x, schema.y, schema.z);
      },
      remove: function() {
        var entity = this.el.object3D;
        entity.remove(entity.getObjectByName("gridHelper"));
      }
    });

    var sceneObj3d = document.querySelector("a-scene");

    if (sceneObj3d.hasLoaded) {
      fnAddDyanmicGridHelper();
    } else {
      sceneObj3d.addEventListener("loaded", fnAddDyanmicGridHelper);
    }

function fnAddDyanmicGridHelper() {
  //var helper = new THREE.BoundingBoxHelper(sceneObj3d.querySelector('a-entity').object3D, 0xff0000);
  var childObjects = sceneObj3d.querySelector('a-entity').object3D;
  var boundingBox = new THREE.BoxHelper(childObjects, 0xff0000);
  //Uncomment the next line to display the bounding box
  //childObjects.add(boundingBox);
  //Need the following method call to get the geometry.boundingBox coordinates in addition to the sphere coordinates
  boundingBox.geometry.computeBoundingBox();                            

  var x = boundingBox.geometry.boundingSphere.center.x;
  var z = boundingBox.geometry.boundingSphere.center.z;
  var size = boundingBox.geometry.boundingSphere.radius * 2;
  sceneObj3d.setAttribute("cmpGridHelper", {
    x: x,
    z: z,
    y: boundingBox.geometry.boundingBox.min.y,
    size: size,
    divisions: size
  });
}
        </script>
    </body>

    </html>
于 2018-05-13T20:04:19.100 回答