【问题标题】:Fill three.js scene with a grid用网格填充 three.js 场景
【发布时间】:2012-06-21 00:38:56
【问题描述】:

我在寻找什么

在填充整个场景的 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 中,第一行从屏幕开始,并且行的宽度不填充屏幕宽度。

【问题讨论】:

    标签: javascript 3d three.js


    【解决方案1】:

    所以你的代码中有两个错误。

    首先,您从MAX_Y 处的行开始,然后将每一行放置在最后一行下方的固定距离处。相对较小的错误是在getNWScreenVectorgetNEScreenVector 中,您将线的顶点放在MAX_Y 的高度,然后在

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

    您正在向MAX_Y - (c * this.gridBlockSize) 行添加一个翻译,这给出了MAX_Y + MAX_Y - (c * this.gridBlockSize) 的最终y 位置,这是一个MAX_Y 太多了。如果您的程序从getNWScreenVectorgetNEScreenVector 降序的行开始是有意义的,那么您应该将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/

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

    【讨论】:

    • 布伦丹,绝妙的答案!我很欣赏数学,这个答案是一个很好的参考。在我看到你的答案之前,我想出了一个基于一些代码的解决方案,用于使用projector.unprojectVector 来检测 3D 世界中的鼠标位置以获取屏幕的角落。这是jsFiddle。网格完美匹配,但我在场景中绘制的与该网格相关的所有内容似乎都卡在轨迹球的外边缘,使得场景导航不那么直观。您的解决方案会有所帮助。
    • 很好的解释!谢谢老哥!
    【解决方案2】:

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

    // 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);
    

    【讨论】:

    • 注意这里现在画的是对角线
    • 我认为这不再按预期工作了。
    【解决方案3】:

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

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

    下面是在场景中绘制网格的代码,大小=100,步长=10

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

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

    以下链接中提供了一个现场示例

    Grid Helper Example

    【讨论】:

      【解决方案4】:

      此线程有助于对应用于 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>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-08-18
        • 1970-01-01
        • 1970-01-01
        • 2015-03-20
        • 1970-01-01
        • 1970-01-01
        • 2019-03-27
        • 1970-01-01
        相关资源
        最近更新 更多