0

我已经用 three.js 实现了一个简单的 3D 模型查看器。该项目包含一个 3D 模型数据库,分为不同的类别。一类是机型高(宽度相对较小,高度远高于其他机型的高度),另一类机型是小(相对于所有产品,高度和宽度都小于其他机型)在其他类别中,这些是小型模型),在另一个类别中,模型很大(它们的高度和比其他类别的许多模型更宽。

查看器具有固定的画布宽度和高度。为此,在画布中加载模型时,很多模型会立即以小比例加载,这需要后续多次缩放。还有一些模型,其上部不适合画布,而下部则在启动时。这也需要后续的缩放。

查看器首先需要估计模型的尺寸,然后它会自动为模型单独选择比例,然后将模型垂直和水平居中

模型所在的平面(模型有可触摸的宽度和高度,厚度很小,所有这些模型都接近平面)与屏幕平面重合也是必要的。在启动时,许多模型对这些平面都有偏移。如何实现这一点,以便查看器自动展开模型?

下面我附上来自不同类别的模型的屏幕截图 - 它们被清楚地记录在哪里: 高个模型,上半部分不适合画布 小宽高模型 短款,宽度远小于画布宽度 平面偏离屏幕平面的模型

下面是负责初始化场景的查看器代码:

var objectUrl = $('#modelViewerModal').data('object-url');//'/storage/3d/qqq.obj';

var mesh, renderer, scene, camera, controls;

init();
animate();

function init() {
    const screenshotPageWidth = $(document).width();
    const screenshotPageHeight = $(document).height();

    let modalBody = document.querySelector('#scene-container');

    // renderer
    renderer = new THREE.WebGLRenderer({modalBody});

    var height = screenshotPageHeight / 2;
    var width = screenshotPageWidth / 2;
    if (screenshotPageHeight < screenshotPageWidth) { // landscape orientation
        if (width > 3 * height / 2) {
            width = 3 * height / 2;
        } else if (width < 3 * height / 2) {
            height = 2 * width / 3;
        }
    } else if (screenshotPageHeight > screenshotPageWidth) { // portrait orientation
        if (height > 2 * width / 3) {
            height = 2 * width / 3;
        } else if (height < 2 * width / 3) {
            width = 3 * height / 2;
        }
    }

    // let limitHeight = screen.height - 137;

    renderer.setSize(width, height);

    modalBody.appendChild( renderer.domElement );

    // scene
    scene = new THREE.Scene();
    scene.background = new THREE.Color( 0x000000 );

    // camera
    camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
    camera.position.set( 20, 20, 20 );

    // controls
    controls = new OrbitControls( camera, renderer.domElement );

    // ambient
    scene.add( new THREE.AmbientLight( 0x222222 ) );

    // light
    var light = new THREE.DirectionalLight( 0xffffff, 1 );
    light.position.set( 2000, 2000, 2000 );
    scene.add( light );

    var spotLight_01 = getSpotlight('rgb(145, 200, 255)', 1);
    spotLight_01.name = 'spotLight_01';
    var spotLight_02 = getSpotlight('rgb(255, 220, 180)', 1);
    spotLight_02.name = 'spotLight_02';
    scene.add(spotLight_01);
    scene.add(spotLight_02);

    // geometry
    var geometry = new THREE.SphereGeometry( 5, 12, 8 );

    // material
    const material = new THREE.MeshPhongMaterial({
        color: 0xeae8dc,
        side: THREE.DoubleSide,
        transparent: false,
        flatShading: false,
        opacity: 0
    });

    // mesh
    var objLoader = new THREE.OBJLoader();
    objLoader.load(objectUrl,
        function ( obj ) {
            mesh = obj;
            mesh.scale.setScalar( 0.01 );

            obj.traverse( function( child ) {
                if ( child.isMesh ) child.material = material;
            } );

            // center the object
            var aabb = new THREE.Box3().setFromObject( mesh );
            var center = aabb.getCenter( new THREE.Vector3() );

            mesh.position.x += (mesh.position.x - center.x);
            mesh.position.y += (mesh.position.y - center.y);
            mesh.position.z += (mesh.position.z - center.z);

            scene.add( mesh );
            animate();
        } );
}

function animate() {
    requestAnimationFrame( animate );

    controls.update();

    renderer.render( scene, camera );
}

function getSpotlight(color, intensity) {
    var light = new THREE.SpotLight(color, intensity);
    light.castShadow = true;

    light.shadow.mapSize.x = 4096;
    light.shadow.mapSize.y = 4096;

    return light;
}
4

1 回答 1

0

这可以通过两件事来完成 - 相机对象和网格对象。你需要做的是使用相机平截头体(相机可视区域的表示https://en.wikipedia.org/wiki/Viewing_frustum)来计算与它相同高度(或宽度)的距离你的模型。模型的高度可以很容易地从几何图形中获取。

// Get the size of the model
mesh.geometry.computeBoundingBox()
const modelSize = mesh.geometry.boundingBox.getSize()
const width = modelSize.x // the exact axis will depend on which angle you are viewing it from, using x for demonstration here

// Compute necessary camera parameters
const fov = camera.fov
const aspect = camera.aspect // camera aspect ratio (width / height)
// three.js stores camera fov as vertical fov. We need to calculate horizontal fov
const hfov = (2 * Math.atan(Math.tan(MathUtils.degToRad(fov) / 2) * aspect) * 180) / Math.PI;
// calculate the distance from the camera at which the frustum is the same width as your model
const dist = (width * 0.5) / Math.tan(MathUtils.degToRad(hfov * 0.5));
// Position camera exactly based on model. There are more elegant ways to do this, but this is a quick and dirty solution
// Camera looks down its own negative Z axis, adding Z effectively "zooms out"
camera.position.copy(mesh.position).translateZ(dist)

这些 fov 和 frustum 计算可能看起来令人生畏,但这些都是 3D 引擎中很好解决的问题,并且可以通过几次搜索很快找到确切的算法。

这个解决方案显然只适用于宽度,并使用一种刚性的方法来移动相机,但希望它为您提供了以适合您的应用程序的方式应用解决方案的工具。

于 2021-01-12T21:21:56.883 回答