(第三节)全景漫游

时间:2024-03-02 09:49:43

一、实验内容

通过上次实验,了解了Three.js创建场景的基本步骤。这一节,我们将通过Three.js实现全景漫游功能。如下图:
这里写图片描述

全景图是获取一个3D场景中的不同角度的图片,然后通过拼接、融合实现3D的虚拟场景。例如百度地图上全景漫游。

二、探讨

方法1:使用软件(例如:Pano2vr )
前提准备:下载Pano2vr软件;全景图。
全景图:对于现实世界,可以通过鱼眼摄像头拍摄得到全景图;对于虚拟世界,使用3D模型软件,例如3dMaxs,导入场景模型,生成全景图。
有点:可以导出flash、html5等格式的全景漫游。你只需要准备一张全景图就可以。

这里我们使用的是另外一种方法,使用Three.js实现全景漫游的功能,这样有利于我们以后的功能扩展。

三、全景漫游基本知识

全景图通过广角的表现手段以及绘画、相片、视频、三维模型等形式,尽可能多表现出周围的环境。360全景,即通过对专业相机捕捉整个场景的图像信息或者使用建模软件渲染过后的图片,使用软件进行图片拼合,并用专门的播放器进行播放,即将平面照片或者计算机建模图片变为360 度全观,用于虚拟现实浏览,把二维的平面图模拟成真实的三维空间,呈现给观赏者。[1]

实现360度全景漫游的有3种方法,1)球形图、2)圆柱图、3)正方体。
对基本原理比较感兴趣的朋友可以去看看这篇博客,写的很详细。
https://aotu.io/notes/2016/01/02/3D-panorama/?utm_source=tuicool&utm_medium=referral

注:圆柱图实现方法的确定比较明显,所以使用的比较少。上面提到的软件实现全景漫游,使用的是球形的方法。这里我们实现的方法是正方体。

四、实验步骤

1、创建场景:

/**
 * 创建场景
 * @type {THREE.Scene}
 */
scene = new THREE.Scene();

2、添加相机
这里我们使用的是透视相机。

/**
* 添加相机
 * @type {THREE.PerspectiveCamera}
 */
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

3、添加渲染器
这里使用的是CSS3DRenderer渲染器,要在html文件中添加:
这里写图片描述

/**
* 添加一个渲染器
 * @type {THREE.CSS3DRenderer}
 */
renderer = new THREE.CSS3DRenderer();
renderer.setClearColor(0x000000, 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);

4、创建正方体
创建正方体,分别对正方体的六个面进行贴图

/**
 *正方体的6个面的资源及相关(坐标、旋转等)设置
 */
var sides = [{url: \'./images/posx.jpg\', position: [-512, 0, 0], rotation: [0, Math.PI / 2, 0]},
            {url: \'./images/negx.jpg\', position: [512, 0, 0], rotation: [0, -Math.PI / 2, 0]},
            {url: \'./images/posy.jpg\', position: [0, 512, 0], rotation: [Math.PI / 2, 0, Math.PI]},
            {url: \'./images/negy.jpg\', position: [0, -512, 0], rotation: [-Math.PI / 2, 0, Math.PI]},
            {url: \'./images/posz.jpg\', position: [0, 0, 512], rotation: [0, Math.PI, 0]},
            {url: \'./images/negz.jpg\',position: [0, 0, -512], rotation: [0, 0, 0]}];

for (var i = 0; i < sides.length; i++) {
    var side = sides[i];
    var element = document.createElement(\'section\');
    document.body.appendChild(element);
    element.id = \'section_\'+ i;
    var imgElement = document.createElement(\'img\');
    imgElement.width = 1028; // 2 pixels extra to close the gap.
    imgElement.height = 1028;
    imgElement.src = side.url;
    element.appendChild(imgElement);
    var object = new THREE.CSS3DObject(element);
    object.position.set(side.position[0], side.position[1], side.position[2]);
    object.rotation.set(side.rotation[0], side.rotation[1], side.rotation[2]);
    scene.add(object);
}

5、实时渲染函数

/**
 * 实时渲染函数
 */
function render() {
    requestAnimationFrame(render);
    lon = Math.max(-180, Math.min(180, lon));//限制固定角度内旋转
    lat = Math.max(-85, Math.min(85, lat));
    phi = THREE.Math.degToRad(90 - lat);
    theta = THREE.Math.degToRad(lon);
    target.x = Math.sin(phi) * Math.cos(theta);
    target.y = Math.cos(phi);
    target.z = Math.sin(phi) * Math.sin(theta);
    camera.lookAt(target);
    renderer.render(scene, camera);
}

6、窗口改变时对camera焦点的处理

/**
 * 窗体大小改变
 */
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}

7、相机焦点跟着鼠标的操作移动

function onDocumentMouseDown(event) {
    event.preventDefault();
    document.addEventListener(\'mousemove\', onDocumentMouseMove, false);
    document.addEventListener(\'mouseup\', onDocumentMouseUp, false);
}

function onDocumentMouseMove(event) {
    var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
    var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
    lon -= movementX * 0.1;
    lat += movementY * 0.1;
}

function onDocumentMouseUp(event) {
    document.removeEventListener(\'mousemove\', onDocumentMouseMove);
    document.removeEventListener(\'mouseup\', onDocumentMouseUp);
}

/**
 * 鼠标滚轮改变相机焦距
 */
function onDocumentMouseWheel(event) {
    camera.fov -= event.wheelDeltaY * 0.05;
    camera.updateProjectionMatrix();
}

function onDocumentTouchStart(event) {
    event.preventDefault();
    var touch = event.touches[0];
    touchX = touch.screenX;
    touchY = touch.screenY;
}

function onDocumentTouchMove(event) {
    event.preventDefault();
    var touch = event.touches[0];
    lon -= (touch.screenX - touchX) * 0.1;
    lat += (touch.screenY - touchY) * 0.1;
    touchX = touch.screenX;
    touchY = touch.screenY;
}

五、总结

开始导师要实现全景图的功能,开始对这方面的知识不了解,无从下手。后来通过查找资料,看论文等掌握了全景漫游的基本知识点。通过这次式样,掌握了全景漫游功能的基本原理已经实现方法。

六、完整代码

main.js

var scene, renderer, camera;
var target = new THREE.Vector3();   //相机焦点
var lon = 90, lat = 0;
var phi = 0, theta = 0;
var touchX, touchY;

function init() {
    /**
     * 创建场景
     * @type {THREE.Scene}
     */
    scene = new THREE.Scene();

    /**
     * 添加相机
     * @type {THREE.PerspectiveCamera}
     */
    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

    /**
     * 添加一个渲染器
     * @type {THREE.CSS3DRenderer}
     */
    renderer = new THREE.CSS3DRenderer();
    renderer.setClearColor(0x000000, 1.0);
    renderer.setSize(window.innerWidth, window.innerHeight);

    /**
     *正方体的6个面的资源及相关(坐标、旋转等)设置
     */
    var sides = [{url: \'./images/posx.jpg\', position: [-512, 0, 0], rotation: [0, Math.PI / 2, 0]},
                {url: \'./images/negx.jpg\', position: [512, 0, 0], rotation: [0, -Math.PI / 2, 0]},
                {url: \'./images/posy.jpg\', position: [0, 512, 0], rotation: [Math.PI / 2, 0, Math.PI]},
                {url: \'./images/negy.jpg\', position: [0, -512, 0], rotation: [-Math.PI / 2, 0, Math.PI]},
                {url: \'./images/posz.jpg\', position: [0, 0, 512], rotation: [0, Math.PI, 0]},
                {url: \'./images/negz.jpg\',position: [0, 0, -512], rotation: [0, 0, 0]}];

    for (var i = 0; i < sides.length; i++) {
        var side = sides[i];
        var element = document.createElement(\'section\');
        document.body.appendChild(element);
        element.id = \'section_\'+ i;
        var imgElement = document.createElement(\'img\');
        imgElement.width = 1028; // 2 pixels extra to close the gap.
        imgElement.height = 1028;
        imgElement.src = side.url;
        element.appendChild(imgElement);
        var object = new THREE.CSS3DObject(element);
        object.position.set(side.position[0], side.position[1], side.position[2]);
        object.rotation.set(side.rotation[0], side.rotation[1], side.rotation[2]);
        scene.add(object);
    }
    document.body.appendChild(renderer.domElement);

    /**
     * 注册监听
     */
    document.addEventListener(\'mousedown\', onDocumentMouseDown, false);
    document.addEventListener(\'mousewheel\', onDocumentMouseWheel, false);
    document.addEventListener(\'touchstart\', onDocumentTouchStart, false);
    document.addEventListener(\'touchmove\', onDocumentTouchMove, false);
    window.addEventListener(\'resize\', onWindowResize, false);

    render();
}


/**
 * 实时渲染函数
 */
function render() {
    requestAnimationFrame(render);
    lon = Math.max(-180, Math.min(180, lon));//限制固定角度内旋转
    lon += 0.1;//自动旋转
    lat = Math.max(-85, Math.min(85, lat));
    phi = THREE.Math.degToRad(90 - lat);
    theta = THREE.Math.degToRad(lon);
    target.x = Math.sin(phi) * Math.cos(theta);
    target.y = Math.cos(phi);
    target.z = Math.sin(phi) * Math.sin(theta);
    camera.lookAt(target);
    renderer.render(scene, camera);
}

/**
 * 窗体大小改变
 */
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}

function onDocumentMouseDown(event) {
    event.preventDefault();
    document.addEventListener(\'mousemove\', onDocumentMouseMove, false);
    document.addEventListener(\'mouseup\', onDocumentMouseUp, false);
}

function onDocumentMouseMove(event) {
    var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
    var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
    lon -= movementX * 0.1;
    lat += movementY * 0.1;
}

function onDocumentMouseUp(event) {
    document.removeEventListener(\'mousemove\', onDocumentMouseMove);
    document.removeEventListener(\'mouseup\', onDocumentMouseUp);
}

/**
 * 鼠标滚轮改变相机焦距
 */
function onDocumentMouseWheel(event) {
    camera.fov -= event.wheelDeltaY * 0.05;
    camera.updateProjectionMatrix();
}

function onDocumentTouchStart(event) {
    event.preventDefault();
    var touch = event.touches[0];
    touchX = touch.screenX;
    touchY = touch.screenY;
}

function onDocumentTouchMove(event) {
    event.preventDefault();
    var touch = event.touches[0];
    lon -= (touch.screenX - touchX) * 0.1;
    lat += (touch.screenY - touchY) * 0.1;
    touchX = touch.screenX;
    touchY = touch.screenY;
}

/**
 * 脚本入口
 * @type {init}
 */
window.onload = init;

引用
1:百度百科 http://baike.baidu.com/view/2424250.htm