【问题标题】:Fit 3D Object (Collada File) within Three.JS Canvas on initial load初始加载时在 Three.JS Canvas 中拟合 3D 对象(Collada 文件)
【发布时间】:2016-03-09 23:45:20
【问题描述】:

我有一个在 three.js 画布中加载的框(collada 文件)。我可以按预期与之交互。 但是,盒子的大小会有所不同,因为用户可以更改大小

当我将它加载到一个 500px x 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 文件由用户向第三方供应商发送变量生成,该供应商返回一个 collada 文件,该文件随后被加载到 three.js。

更新 2: 感谢@Blindman67,我更接近于理解它们之间的相互作用。当我手动调高 camera.position x,y,z 值时,对象就在屏幕上。我面临的挑战是如何确定正确的 x、y、z 值,因为每个框都是动态变化的,我实际上有超过 2.8 亿种变化。我知道@Blindman67 已经在逻辑上给了我答案,但我只需要最后一步来发现如何为每次变化的对象获得正确的位置,以便我可以设置正确的 x、y、z。

【问题讨论】:

  • 谢谢。我看了看,但我想我搜索的是错误的命名法。
  • 仍然需要额外的帮助,因为我正在加载 collada 文件而不是创建几何形状,所以我不确定如何定位它所以它被放大了。
  • 没关系,所有加载器都会创建一个Object3D,它会有一个几何属性
  • 由于我只需要允许用户与对象进行交互,我应该使用不同的相机/透视与正交的优势是什么?

标签: canvas three.js


【解决方案1】:

使 3D 对象适合视图

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

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

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

因此,您只能将相机移入或移出或保持静止并进行缩放。和真正的相机一样,你可以放大或拉近。

这两种方法各有利弊。

翻译

在大多数情况下移动相机(小车)是最好的,因为它可以保持视角不变(线条收敛到消失点的速度),因此不会扭曲视野中的对象。在 3D 中,这种方法存在 3 个问题。

  • 视锥体(显示场景的体积)具有 背板(显示对象的最大距离)和字体 平面(可以显示最近的对象)。将相机移至 适合非常大或非常小的物体都会导致物体 在正面或背面之外,并被部分或全部夹住。
  • 移动平面以适应对象也会产生不希望的结果。 移动前后平面以固定物体可能会导致 场景中更近或更远的对象被裁剪掉。
  • 扩大后面板和前面板之间的总距离可以 也会导致 Z 缓冲区混叠伪影。但这些问题只适用 非常大或非常小的物体和场景。

缩放

变焦涉及改变相机的焦距。在您使用的库中,这是通过调整 FOV(视野)来完成的,这是以度数给出的视图左侧和右侧之间的角度。减少 FOV 可以有效地增加焦距并放大(3D 图形没有像相机那样的焦距)。增加 FOV 会缩小。这个方法有问题。

  • 随着 FOV 减小,透视(视差)减小,使 场景出现的3D越来越少。随着 FOV 增加视角 增加扭曲物体并使远处的物体非常 小。
  • 由于摄像头不移动,前后平面保持在原位, 但放大或缩小靠近后平面或前平面的物体可能 仍然会导致 z-buffer 混叠伪影。

使用哪种方法是 ip 给你,你可以使用一种或另一种或两者结合。

如何完成

到手头的问题。我们需要知道物体将在场景中出现多大,并使用此信息将相机设置更改为所需的效果(即使物体适合显示器)。

图 1. 相机、对象和视图。

因此需要一些值。可视化解释见图1。

  • 视场。我会将其转换为弧度
  • 对象与相机的距离
  • 对象的边界球半径。
  • 前面板
  • 背板
  • 屏幕像素大小

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

代码

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 使对象占据所需的像素大小,这将有助于您理解。复制代码并不能教给你太多东西,使用上述信息并应用它会让你牢记在心,并且会让你在未来进行 3D 所需的许多其他过程变得更容易。

也就是说,复制代码是一种快速的解决方案,也是框架和库的全部意义所在。无需知道如何学习更好的东西。

缩放以适应。

这是最简单的,获取对象的角度大小并调整 FOV 以适应。 (注意我使用的是弧度。Three.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 文档。它很简单,适用于透视相机。

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

希望对你有帮助

【讨论】:

  • 这是迄今为止我在 StackOverflow 上看到的最具启发性的帖子。不管需要弄清楚如何在threejs代码中进行这项工作,我只是学到了很多东西,这比一些代码更有价值。你可以说它给了我更大的“视角”……
  • 这确实帮助了我的知识,但我需要知道如何在threejs中实现这一点。有接受者吗?
  • 您需要获取框的大小及其在 3d 空间中的位置。使用box.computeBoundingSphere();var boxRadius = box.boundingSphere; 获取大小,然后box.position 具有框(x,y,z)位置。在答案示​​例中使用这些值,然后使用camera.fov 设置FOV 或camera.position 设置位置。除此之外我不知道你卡在哪里了
  • 我想我面临的挑战是在我的代码上下文中看到这一点。从逻辑上讲,它更有意义,但问题是哪些值和变量去哪里。我尝试添加 box.computeBoundingSphere 但它声明它不是一个函数。我注意到如果我手动更改 camera.position.x、camera.position.y 和 camera.position.z 它会放大更多,但我仍然对如何获得正确的 x、y、z 值感到困惑。
  • Size 是 3D 空间中的大小,实际像素大小将由设备屏幕和所需的像素大小var requieredObjectPixelSize = ?; 在应用转换以适应过程后确定。 3D 对象的坐标系与屏幕分辨率无关。您可以缩放框以适应,但您还必须缩放场景中的所有内容。
【解决方案2】:

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

对于用户“缩放”,还请查看 three.js 示例/js/controls/TrackballControls.js,它会移动相机位置(并更改外观和向上)。如果您选择使用它,我记得您需要在使用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();

}

【讨论】:

    【解决方案3】:

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

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

    由于我知道对象的宽度(动态)和画布的大小(500 像素),因此我能够通过以下方式获得正确的缩放比例:

    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;
    

    【讨论】:

      猜你喜欢
      • 2013-05-08
      • 2016-03-26
      • 2017-02-05
      • 2015-10-12
      • 2015-11-06
      • 2015-02-26
      • 2018-09-02
      • 1970-01-01
      • 2019-09-30
      相关资源
      最近更新 更多