Javascript 用网格填充three.js场景
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11093084/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Fill three.js scene with a grid
提问by cs_brandt
What I am looking for
我在寻找什么
Display a grid within a three.js scene that fills the entire scene. In this case, the scene is the whole window.
在一个 Three.js 场景中显示一个填充整个场景的网格。在这种情况下,场景是整个窗口。
This grid represents a surface in 3D and can be moved around with the mouse using THREE.TrackballControls
This grid is facing the camera so initially it looks like a flat (2D) surface until the trackball is pulled around with the mouse.
这个网格代表一个 3D 表面,可以使用鼠标移动THREE.TrackballControls
这个网格面向相机,所以最初它看起来像一个平面 (2D) 表面,直到用鼠标拉动轨迹球。
The width of the grid lines should equal to the width of the renderer.
网格线的宽度应等于渲染器的宽度。
What I did
我做了什么
I have setup a working jsFiddlefor what I have done so far.
我已经为到目前为止所做的工作设置了一个有效的jsFiddle。
First I find the bounds of the scene (all of this is in the 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();
};
};
Setup three.js
设置三.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();
},
These functions provide a vector for each screen corner,
这些函数为每个屏幕角落提供一个向量,
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);
},
I create some geometry that will represent a line at the very top of the screen, and try to draw lines starting from the top and going down to the bottom of the screen.
我创建了一些几何图形来表示屏幕最顶部的一条线,并尝试从屏幕顶部开始画线,一直到屏幕底部。
// 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);
}
}
The problem
问题
This method does not work, in the jsFiddle the first line starts off the screen and the width of the lines do not fill the screen width.
此方法不起作用,在 jsFiddle 中,第一行从屏幕开始,行的宽度未填充屏幕宽度。
回答by aravind.udayashankara
Since ThreeJS r57 onwards, there is a helper called GridHelper using which you can easily draw a nice grid, just like any other geometric object.
从 ThreeJS r57 开始,有一个名为 GridHelper 的助手,您可以使用它轻松绘制漂亮的网格,就像任何其他几何对象一样。
GridHelper takes 2 parameters. First one is the size of the grid and the 2nd one is the size of the step between 2 lines
GridHelper 需要 2 个参数。第一个是网格的大小,第二个是两行之间的步长
Below is the code to draw the grid on the scene, with the size = 100 and step = 10
下面是在场景上绘制网格的代码,大小=100,步长=10
var grid = new THREE.GridHelper(100, 10);
scene.add(grid);
In your case, you can avoid having a method called drawGrid and directly replace that with the above two lines code, or you can add these above two lines of code with in the drawgrid method.
在你的情况下,你可以避免有一个叫做 drawGrid 的方法,直接用上面两行代码替换它,或者你可以在 drawgrid 方法中添加上面这两行代码。
A live example is available here in the following link
以下链接中提供了一个实时示例
回答by Brendan Kenny
So there are two bugs in your code.
所以你的代码中有两个错误。
First, you're starting with the line at MAX_Y
and then putting each line a fixed distance below the last. The relatively minor bug is that in getNWScreenVector
and getNEScreenVector
you're putting the line's vertices at a height of MAX_Y
, then in
首先,您从 at 线开始,MAX_Y
然后将每条线放在最后一条线下方固定距离处。相对较小的错误是 ingetNWScreenVector
并且getNEScreenVector
您将线的顶点放在 的高度MAX_Y
,然后在
line.position.y = this.MAX_Y - (c * this.gridBlockSize);
you're adding a translation to the line of MAX_Y - (c * this.gridBlockSize)
, which gives a final y position of MAX_Y + MAX_Y - (c * this.gridBlockSize)
, which is one MAX_Y
too many. If it makes sense to your program to start with lines descending from getNWScreenVector
and getNEScreenVector
, then you should change the line.position.y
line to just
您正在向 的行添加翻译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;
You can see that the lines are now centered on jsFiddle, but they're still sized incorrectly. This is because you aren't accounting for the fact that your scene is in perspective. Your lines all have their z coordinates set to 0, so when you set this.camera.position.z = 1000
, they are 1000 units away from the camera. There is no reason to assume that something with the same width as the pixel width of the canvas will have the same width when drawn in perspective from 1000 units away.
您可以看到线条现在以jsFiddle为中心,但它们的大小仍然不正确。这是因为您没有考虑到场景是透视的这一事实。您的线条都将其 z 坐标设置为 0,因此当您设置 时this.camera.position.z = 1000
,它们距离相机 1000 个单位。没有理由假设宽度与画布像素宽度相同的东西在从 1000 个单位远的透视图中绘制时将具有相同的宽度。
Instead, we can calculate how big they need to be. I won't go in to a full explanation of perspective here, but we can figure out how big an area the lines need to cover to cover the screen. You've specified a vertical FOV of 45 degrees in your camera constructor and a distance of 1000 between the camera and your lines. Three.js basically shows the solution if you peak into how it creates the perspective matrix: makePerspective
相反,我们可以计算它们需要多大。我不会在这里详细解释透视图,但我们可以计算出线条需要覆盖多大的区域才能覆盖屏幕。您已在相机构造函数中指定了 45 度的垂直 FOV,相机与线条之间的距离为 1000。如果您深入了解它如何创建透视矩阵,three.js 基本上会显示解决方案:makePerspective
First, we need the vertical distance of the upper half of the screen, since 0 is at the center of the screen. Math.tan(45 / 2 * (Math.PI / 180))
(tangent of half the angle, converted to radians) gives the vertical distance divided by the distance away from the camera, so you can calculate the height with
首先,我们需要屏幕上半部分的垂直距离,因为 0 位于屏幕的中心。Math.tan(45 / 2 * (Math.PI / 180))
(角度一半的切线,转换为弧度)给出垂直距离除以距相机的距离,因此您可以使用以下方法计算高度
halfDisplayHeight = 1000 * Math.tan(45 / 2 * (Math.PI / 180));
or double it to
或加倍
this.DISPLAY_HEIGHT = 2 * 1000 * Math.tan(45 / 2 * (Math.PI / 180));
The horizontal FOV is notthe same unless the canvas is square, but the width and height ratio of the line area will be proportional to the width and height ratio of the screen. Like three.js does, you can just multiply by the aspect ratio you also provided to the camera constructor to figure out the width:
除非画布是方形的,否则水平FOV是不一样的,但是线条区域的宽高比会与屏幕的宽高比成正比。像three.js一样,你可以乘以你也提供给相机构造函数的纵横比来计算宽度:
this.DISPLAY_WIDTH = this.DISPLAY_HEIGHT * (this.SCREEN_WIDTH / this.SCREEN_HEIGHT);
These are the values that you should use for placing your lines. All together:
这些是您应该用于放置线条的值。全部一起:
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);
Finally, you'll want to spread the lines over the full area, so you should set
最后,您需要将线条分布在整个区域,因此您应该设置
this.gridBlockSize = this.DISPLAY_HEIGHT / numHorizontalGridLines;
You can see that working here: http://jsfiddle.net/pa46hxwo/
你可以在这里看到工作:http: //jsfiddle.net/pa46hxwo/
There is another way to do this, though, which is to keep the lines the same, but move the camera closer to your lines so that the width of the lines just happens to equal the pixel width of the canvas at that distance. The formula is just a reworking of the above for DISPLAY_HEIGHT
, but instead of solving for height, we solve for a distance when the height equals the screen height:
不过,还有另一种方法可以做到这一点,即保持线条不变,但将相机移近线条,以便线条的宽度恰好等于该距离处画布的像素宽度。该公式只是对上述 for 的修改DISPLAY_HEIGHT
,但我们不是求解高度,而是求解高度等于屏幕高度时的距离:
this.camera.position.z = this.SCREEN_HEIGHT / (2 * Math.tan(45 / 2 * (Math.PI / 180)));
You can see that here: http://jsfiddle.net/0jtykgrL/
你可以在这里看到:http: //jsfiddle.net/0jtykgrL/
It's a much smaller change to your code, but it means that the camera position will change depending on how big the canvas is, which could affect other content which you need to place precisely, so the choice is yours.
这是对您的代码的一个小得多的更改,但这意味着相机位置将根据画布的大小而变化,这可能会影响您需要精确放置的其他内容,因此选择权在您手中。
回答by geedew
You can draw a grid like this.
您可以像这样绘制网格。
// 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);
回答by UberMario
This thread helped with a variation of the technique applied to A-Frame and with a slight twist; dynamically creating and sizing the grid as a function of the bounding box for the scene's children.
这条线有助于改变应用于 A-Frame 的技术,并略有扭曲;根据场景子项的边界框动态创建和调整网格大小。
Note: The following method must be invoked to get the the bounding box coordinates that are initially null, unlike the bounding sphere which is pre-calculated for you:
注意:必须调用以下方法来获取初始为空的边界框坐标,这与预先为您计算的边界球体不同:
'boundingBox.geometry.computeBoundingBox();'
' boundingBox.geometry.computeBoundingBox(); '
Codepen for this 'scene-ario' (pun intended) can be found here: https://codepen.io/ubermario/pen/JvZMPg
可在此处找到此“场景-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>