初始加载时,在Three.JS Canvas中拟合3D对象(Collada文件)

时间:2015-12-04 22:30:25

标签: canvas three.js

我有一个加载在three.js画布中的框(collada文件)。我可以按预期与它互动。 但是,框大小因用户可以更改而异。

当我将它加载到500px乘500px画布时,如果框很大,用户必须在看到它之前放大,如果它很小,它很小,用户需要放大。尺寸根据不同而变化传递的变量。

如何在加载画布时使对象(collada文件)适合,然后让用户缩放?下面是加载点击以在three.js画布中显示3D对象的代码:

$scope.generate3D = function () {

        // 3D OBJECT - Variables
        var texture0 = baseBlobURL + 'Texture_0.png';
        var boxDAE = baseBlobURL + 'Box.dae';
        var scene;
        var camera;
        var renderer;
        var box;
        var controls;
        var newtexture;

        // Update texture
        newtexture = THREE.ImageUtils.loadTexture(texture0);

        // Initial call to render scene, from this point, Orbit Controls render the scene per the event listener
        THREE.DefaultLoadingManager.onProgress = function (item, loaded, total) {
            // console.log( item, loaded, total ); // debug
            if (loaded === total) render();
        };

        //Instantiate a Collada loader
        var loader = new THREE.ColladaLoader();
        loader.options.convertUpAxis = true;
        loader.load(boxDAE, function (collada) {
            box = collada.scene;
            box.traverse(function (child) {
                if (child instanceof THREE.SkinnedMesh) {
                    var animation = new THREE.Animation(child, child.geometry.animation);
                    animation.play();
                }
            });
            box.scale.x = box.scale.y = box.scale.z = .2;
            box.updateMatrix();
            init();
        });

        function init() {
            scene = new THREE.Scene();
            camera = new THREE.PerspectiveCamera(100, window.innerWidth / window.innerHeight, 0.1, 1000);
            renderer = new THREE.WebGLRenderer();
            renderer.setClearColor(0xdddddd);

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

            // Load the box file
            scene.add(box);

            // Lighting
            var light = new THREE.AmbientLight();
            scene.add(light);

            // Camera
            camera.position.x = 40;
            camera.position.y = 40;
            camera.position.z = 40;

            camera.lookAt(scene.position);

            // Rotation Controls
            controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.addEventListener('change', render);
            controls.rotateSpeed = 5.0;
            controls.zoomSpeed = 5;
            controls.noZoom = false;
            controls.noPan = false;

            // Add 3D rendering to HTML5 DOM element
            var myEl = angular.element(document.querySelector('#webGL-container'));
            myEl.append(renderer.domElement);

        }

        // Render scene
        function render() {
            renderer.render(scene, camera);
            console.log('loaded');
        }
    }

    // Initial 3D Preview Load
    $scope.generate3D();

更新:我已经评估了此处提供的解决方案:How to Fit Camera to Object但我不确定如何定义我的collada文件的距离,因为它可能会有所不同,具体取决于用户输入的尺寸。 collada文件是由用户向第三方供应商发送变量生成的,该第三方供应商返回随后加载到three.js中的collada文件。

更新2:感谢@ Blindman67,我更接近了解这是如何相互作用的。当我手动启动camera.position x,y,z值时,对象在屏幕上。我面临的挑战是如何确定每个盒子动态变化时正确的x,y,z值是多少,而且我确实有超过2.8亿的变化。我知道@ Blindman67已经在逻辑上给了我答案,但我只需要最后一步来发现如何为每次变化的对象找到正确的位置,这样我就可以设置正确的x,y,z。

3 个答案:

答案 0 :(得分:8)

将3D对象适合视图

有几种方法可以将3d对象适合相机视图。

  • 向前或向前移动相机。
  • 增加或减少焦距(这也会影响FOV(场) 观点))
  • 更改世界空间比例或对象的本地空间比例 对象更大或更小。

您可以打折最后一个选项,因为在大多数情况下它是不切实际的。 (虽然我注意到你在代码中这样做,因为在这个答案中有足够的信息可以解决如何缩放对象以适应。但我不建议你这样做)

因此,您可以将摄像机移入或移出,或保持静止并进行缩放。它与真实相机相同,可以缩放或靠近。

这两种方法都有利有弊。

<强>翻译

移动相机(小车)对于大多数情况来说是最好的,因为它保持透视相同(线条收敛到消失点的速度有多快),因此不会扭曲视野中的物体。在3D中,该方法存在3个问题。

  • 视锥体(显示场景的体积)具有 背板(对象将显示的最大距离)和字体 平面(最近的一个对象可以显示)。将相机移动到 适合非常大或非常小的对象可以导致对象 在前后平面之外,可以部分或完全夹住。
  • 移动平面以适合对象也可能产生不希望的结果。 移动前后平面以保持对象可能会导致 在要剪裁的场景中更近或更远的物体。
  • 扩展背板和前板之间的总距离可以 还会导致Z缓冲区别名伪影。但这些问题只适用 非常大或非常小的物体和场景。

<强>缩放

变焦涉及改变相机的焦距。在库中,您可以通过调整FOV(视野)来完成,这是以度为单位的视图左侧和右侧之间的角度。减小FOV有效地增加了焦距和放大(3D图形不具有像相机那样的焦距)。增加FOV缩小。这种方法存在问题。

  • 随着FOV减小,视角(视差)减小,使得 场景出现的3D越来越少。随着FOV增加视角 增加扭曲的物体并在远处制造物体 小。
  • 由于相机不移动,前后平面保持原位, 但放大或缩小靠近后平面或前平面的物体可能会 仍导致z缓冲区别名伪像。

使用什么方法是ip,你可以使用其中一种或两者结合使用。

如何完成

到手头的问题。我们需要知道对象在场景中的大小,并使用此信息将摄像机设置更改为所需的效果(即使对象适合显示)。

Diagram displaying the camera and the various properties requiered to contruct a view 图1.摄像机,对象和视图。

因此需要一些值。有关视觉说明,请参见图1。

  • FOV。我将此转换为Radians
  • 对象与相机的距离
  • 对象的边界球半径。
  • 前飞机
  • 背板
  • 屏幕像素大小

您需要计算对象的边界球。或者使用近似于边界球的另一个值。我会把它留给你。

代码

var oL,cL; // for the math to make it readable
var FOV = 45 * (Math.PI / 180); // convert to radians
var objectLocation = oL = {x : 0, y : 0, z : 400};
var objectRadius = 50;
var cameraLocation = cL = {x : 0, y : 0, z : 0};
var farPlane = 1000;
var nearPlane = 200;
var displayWidth = 1600;
var displayHeight = 1000;

要确定边界球体在视图上的显示大小是简单的三角形。

// Get the distance from camera to object
var distToObject = Math.sqrt(Math.pow(oL.x - cL.x, 2) + Math.pow(oL.y - cL.y, 2) + Math.pow(oL.z - cL.z, 2));Figure 1

当我们使用直角三角形(见图1)时,我们将结果乘以2得到总角度大小

// trig inverse tan of opposite over adjacent.
var objectAngularSize = Math.atan( (objectRadius) / distToObject ) * 2;

获取对象占据的FOV的分数。

var objectView = objectAngularSize / FOV;

最后你得到了对象的像素大小。

var objectPixelSize = objectView * displayWidth;

这就是你要做的所有事情。这将有助于您了解是否使用上述代码和数学来尝试重新排列计算,以便通过移动相机或FOV使对象占据所需的像素大小。复制代码并不能教会你太多,通过应用它来使用上述信息会将它设置在你的脑海中,并且将来会使你需要的许多其他过程更容易。

那说复制代码是快速解决方案,是框架和库的全部内容。无需知道如何学习更好的东西。

缩放以适应。

这是最简单的并获得对象的角度大小并调整FOV以适应。 (注意我使用的是弧度.Tour.js使用你需要转换的FOV度数)

var requieredObjectPixelSize = 900;
var distToObject = Math.sqrt(Math.pow(oL.x - cL.x, 2) + Math.pow(oL.y - cL.y, 2) + Math.pow(oL.z - cL.z, 2));
var objectAngularSize = Math.atan( (objectRadius) / distToObject ) * 2;
// get the amount the FOV must be expanded by
var scaling = displayWidth / requieredObjectPixelSize;
// change the FOV to set the objects size 
FOV =  objectAngularSize * scaling;

将FOV转换为度数并使用它来创建相机。

翻译以适合

移动相机以适合物体。这是一个更复杂的方法,但是更好的方法。

// Approx size in pixels you want the object to occupy
var requieredObjectPixelSize = 900;

// camera distance to object
var distToObject = Math.sqrt(Math.pow(oL.x - cL.x, 2) + Math.pow(oL.y - cL.y, 2) + Math.pow(oL.z - cL.z, 2));

// get the object's angular size.
var objectAngularSize = Math.atan( (objectRadius) / distToObject ) * 2;

// get the fraction of the FOV the object must occupy to be 900 pixels
var scaling = requieredObjectPixelSize / displayWidth;

// get the angular size the object has to be
var objectAngularSize = FOV * scaling;

// use half the angular size to get the distance the camera must be from the object
distToObject = objectRadius / Math.tan(objectAngularSize / 2);

现在移动相机。它必须沿着物体和相机之间的矢量移动。

// Get the vector from the object to the camera
var toCam = {
    x : cL.x - oL.x,
    y : cL.y - oL.y,
    z : cL.z - oL.z,
}

标准化矢量。这意味着使向量的长度等于1,并通过将每个分量(x,y,z)除以向量的长度来完成。

// First length
var len = Math.sqrt(Math.pow(toCam.x, 2) + Math.pow(toCam.y, 2) + Math.pow(toCam.z, 2));
// Then divide to normalise (you may want to test for divide by zero)
toCam.x /= len;
toCam.y /= len;
toCam.z /= len;

现在,您可以缩放矢量,使其等于摄像机距物体的距离。

toCam.x *= distToObject;
toCam.y *= distToObject;
toCam.z *= distToObject;

然后只需将矢量添加到对象的位置并将其放入相机位置

cL.x = oL.x + toCam.x;
cL.y = oL.y + toCam.y;
cL.z = oL.z + toCam.z;

cl现在拥有相机位置。

最后一件事。您需要检查对象是否在视图内。

if (distToObject - objectRadius < nearPlane) {
    nearPlane = (distToObject - objectRadius) * 0.8; // move the near plane towards the camera 
                                                 // by 20% of the distance between the front of the object and the camera
}

if (distToObject + objectRadius > farPlane) {
    farPlane = distToObject + objectRadius * 1.2; // move the far plane away from the camera 
                                              // by 1.2 time the object radius
}

还有一个问题。如果物体很小,物体可能非常靠近物体的前部位于相机后面。如果发生这种情况,您将需要使用缩放方法并将相机移回。这仅适用于非常精选的情况,大部分都可以忽略。

我没有提供有关如何将其与Three.js集成的信息,但这是一个适用于所有3D软件包的通用答案。您将不得不查阅three.js文档,了解如何更改各种相机设置。它很简单,适用于透视摄像机。

一个很好的答案,我需要忘记它,因为我没有看到错字和错误。我将在当天晚些时候返回并修复它。

希望有所帮助

答案 1 :(得分:2)

此代码将围绕给定对象“自动居中”相机。

对于用户'缩放',还要看一下three.js examples / js / controls / TrackballControls。js,它们可以移动摄像机位置(并更改lookat和up)。如果您选择使用它,我记得您需要在使用centerCam移动摄像机位置之前初始化控件。在这种情况下,centerCam获取整个对象以准备用户交互,TrackballControls侦听器在此之后接管。

<body onload="initPage()">
  <canvas id="cadCanvas" width="400" height="300"></canvas>
  <button onclick="loadFile()">Load Collada File</button>
</body>

function initPage(){
  var domCanvas = document.getElementById('cadCanvas');
  scene = new THREE.Scene();
  var fovy = 75;
  camera = new THREE.PerspectiveCamera( fovy, domCanvas.width/domCanvas.height, 0.1, 1000 );
  renderer = new THREE.WebGLRenderer({canvas:domCanvas});
  renderer.setSize( domCanvas.width, domCanvas.height );
}

function loadFile(){
  new THREE.ColladaLoader().load( url, function(obj3D) {
    scene.add(obj3D);
    centerCam(obj3D);
    renderer.render(scene, camera);
  });
}

function centerCam(aroundObject3D){

  //calc cam pos from Bounding Box
  var BB = new THREE.Box3().setFromObject(aroundObject3D);
  var centerpoint = BB.center();
  var size = BB.size();
  var backup = (size.y / 2) / Math.sin( (camera.fov/2)*(Math.PI/180) );
  var camZpos = BB.max.z + backup + camera.near ;

  //move cam
  camera.position.set(centerpoint.x, centerpoint.y, camZpos);
  camera.far = camera.near + 10*size.z;
  camera.updateProjectionMatrix();

}

答案 2 :(得分:-1)

感谢Blindmann67,我能够更好地理解所有这些复杂性,并在threejs中提出解决方案。有一个特定的行,其中手动设置了刻度尺:

box.scale.x = box.scale.y = box.scale.z = .2;

因为我知道对象的宽度(动态)和画布的大小(500px),所以我能够通过以下方式获得正确的比例:

var initialZoomScale = 500 / canvasWidthProdPixels; 
// canvasWidthProdPixels is my large object (ex. 25,000 px wide) dividing these gives me a small fraction such as .02 which scales the object perfectly)

box.scale.x = box.scale.y = box.scale.z = initialZoomScale;