Panoramic Virtual Walkthrough Implementation (3.js)

Keywords: network Mobile Google css3

Panoramic virtual walkthrough actually sees many examples, such as panoramas on maps, panoramas on campus, panoramas of companies that were previously popular in the circle of friends. But really think of to study or realize, is a few days ago that there may be such a need for work. Awareness comes too late and curiosity is not enough. Why didn't you think of trying something so interesting and novel at first?

Because the understanding of this aspect is blank, I searched directly in google and found little information. What I said was a little more comprehensive is "Making a 3D Panoramic Walkthrough in H5". Among several schemes, we intend to adopt ThreeJS, because we have seen it a little before, and we intend to learn it, which is just an opportunity. But seeing this tutorial is vague, the key steps should be shown, but without complete code, I still can not have a good concept. Then, I was stunned... Until I asked a colleague of the company, he told me that ThreeJS official website has ready-made examples!!! Suddenly I was shocked by my IQ... Still, I haven't formed the habit of going to the official website to see everything first.

This paper takes the official website as an example to analyze the principle and implementation details of panoramic virtual roaming.

Basic concepts

In my own words, ThreeJS is a 3D JS library encapsulating the functions of WebGL. And what is WebGL (Web Graphic Library)? In short, it is a library or a standard for developing programs related to 3D graphics on the browser side.

The process of developing a 3D program is like taking pictures or recording videos. First, there must be a scene. Then there may be several objects in the scene. Then, the camera is placed somewhere, and the position is aligned somewhere, so that it can be recorded. To display in browsers, you need a renderer. As for what the renderer does or how it works, we may leave it alone. Overall, cameras, scenes, and renderers are the three essential elements to start a 3D program. The relationship between the three is shown in the following figure (although I am not particularly satisfied with this picture, but I have not found any other suitable, it will be used).

Introductory example

Let's go straight to a simple example.

//Create scene
var scene = new THREE.Scene();
//Creating Perspective Camera
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window,innerHeight, 1, 1000);
//Create a renderer to add to the body element
var renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
//Create an object, geometry + material ="three-dimensional object
var geometry = new THREE.CubeGeometry(1,1,1);
var material = new THREE.MeshBasicMaterial({color: 0x00ff00});
var cube = new THREE.Mesh(geometry, material);
//Adding objects to the scene
scene.add(cube);
 //Define rendering function    
function render(){
     requestAnimationFrame(render);
     cube.rotation.x += 0.1;
     cube.rotation.y += 0.1;
     renderer.render(scene, camera);
}
//Real Time Rendering 
render();

This creates a 3D program for rendering cubes. Isn't it simple?

One of the things that needs to be mentioned is the parameters that the camera takes when it is created. Cameras are divided into orthogonal cameras and perspective cameras. Perspective camera under the world is the real effect, has a close relationship between far and small. As shown below, you can easily see the difference between the two.

The parameters of the perspective camera are as follows

PerspectiveCamera( fov, aspect, near, far )
fov — Camera frustum(Cross-sectional vertebrae) vertical field of view.
aspect — Camera frustum aspect ratio.
near — Camera frustum near plane.
far — Camera frustum far plane.

It is shown intuitively by drawing.

Panoramic Virtual Walkthrough Analysis

The human eye is actually the equivalent of a camera. The key point of virtual roaming is how to construct a 720 degree scene without dead angle, and the camera is in the middle, that is, the origin of the coordinate system (not necessarily, but this is more convenient). Since the farthest distance people can see in different directions is the same, the panorama is theoretically a sphere relative to the human eye. But if you map the scene to a sphere, it seems a little complicated. Here we abstract the panorama into a cube, and for us there are six sides up, down, left and right. Principle analysis is clear, the key issues are as follows:

  • How to Stitch Six Pictures into a Cube
  • How does the renderer process the cube's effect to be realistic
  • How to Express and Realize Camera Angle Moving

First of all, we need to be clear that ThreeJS uses right-handed coordinate system, x-axis right, y-axis front, z-axis front. The default position when creating a camera or object is the origin of the coordinate system. So if you want to splice the six pictures into a cube, you just need to do some translation and rotation changes. The main thing to note is that the orientation of the picture should be consistent with the origin, so that the camera can see.

As for how a renderer can turn a cube into a panoramic roaming effect, ThreeJS authors have defined a new renderer called CSS3D Renderer encapsulated rendering process. I probably read a little, but I didn't understand it very well. I felt that the translation 3D of CSS3 was used to transform the picture with scale attributes, in order to achieve the effect of confusing people's eyes.

I searched some data, because I don't know why cubemap can achieve panoramic effect, because it has realized the mapping from sphere to cube. As follows,

As for how to make such pictures, it involves image processing and algorithm, which is not in the scope of this paper for the time being. The following is a complete cubemap.

Finally, the movement of the camera's angle is clearly the movement of the camera's focus. At this point, we can abstract the 720-degree scene into a ball. Since the focal length of the camera is determined, the focus of the camera is determined as long as the angle of the camera is determined. As long as the longitude and latitude are determined, the angle can be determined, and then the final focus position can be calculated by some mathematical calculations. The motion of the scene is relative to the change of the camera's angle of view. So when the mouse moves horizontally, longitude follows; while when the mouse moves vertically, latitude changes. The transformation relationship between rectangular coordinates and spherical coordinates is (taking left-handed coordinates as an example):

  x = r*sin( phi ) *cos( theta );
  z = cos( phi );
  y = sin( phi ) * sin( theta );

Realization of Panoramic Virtual Walkthrough

Combined with the above analysis, the following code is the official network code, with comments.

 <script src="js/three.js"></script>
 <script src="js/renderers/CSS3DRenderer.js"></script>

 <script>
 //Defining cameras, scenes, and renderers are three key elements in the formation of 3D scenes
 var camera, scene, renderer;
 //Define the geometry, material, and mesh formed by adding material to the geometry
 var geometry, material, mesh;
 //Generate three-dimensional vector (0, 0, 0), camera target point
 var target = new THREE.Vector3();
 //Longitude is vertical with east longitude and West longitude; lat dimension is horizontal with south latitude and North latitude.
 //The latitude and longitude represent the focus of the camera, and the initial state is in front of it.
 var lon = 90, lat = 0;
 //It's also the focus of the camera. It's the angle above. It's converted into radian.
 var phi = 0, theta = 0;
 //x,y Input by Mobile User 
 var touchX, touchY;

 init();
 animate();

 function init() {
     //The default position of the camera is at the origin of the coordinate system.
     camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 );

     scene = new THREE.Scene();
     //In the right-hand coordinate system, z faces the observer, the camera. Below are six faces spliced into cubes, corresponding to each other.
     var sides = [
         {
             url: 'res/Bridge2/posx.jpg', //Left
             position: [ -512, 0, 0 ],
             rotation: [ 0, Math.PI / 2, 0 ]
         },
         {
             url: 'res/Bridge2/negx.jpg', //Right
             position: [ 512, 0, 0 ],
             rotation: [ 0, -Math.PI / 2, 0 ]
         },
         {
             url: 'res/Bridge2/posy.jpg', //Upper side
             position: [ 0,  512, 0 ],
             rotation: [ Math.PI / 2, 0, Math.PI  ]
         },
         {
             url: 'res/Bridge2/negy.jpg', //Underside
             position: [ 0, -512, 0 ],
             rotation: [ -Math.PI / 2, 0, Math.PI ]
         },
         {
             url: 'res/Bridge2/posz.jpg', //Front
             position: [ 0, 0,  512 ],
             rotation: [ 0, Math.PI, 0 ]
         },
         {
             url: 'res/Bridge2/negz.jpg', //after
             position: [ 0, 0, -512 ],
             rotation: [ 0, 0, 0 ]
         }
     ];
    //Add six pictures to the scene
     for ( var i = 0; i < sides.length; i ++ ) {

         var side = sides[ i ];

         var element = document.createElement( 'img' );
         element.width = 1026; // 2 pixels extra to close the gap.
         element.src = side.url;
         //CSS 3D Object is a way to expand. The prototype is object 3D. See CSS 3D Renderer. js.
         var object = new THREE.CSS3DObject( element );
         object.position.fromArray( side.position );
         object.rotation.fromArray( side.rotation );
         scene.add( object );

     }
     //Renderer is also an extended method, see CSS3D Renderer. JS
     renderer = new THREE.CSS3DRenderer();
     renderer.setSize( window.innerWidth, window.innerHeight );
     document.body.appendChild( renderer.domElement );

     //Add mouse, gesture, window events
     document.addEventListener( 'mousedown', onDocumentMouseDown, false );
     document.addEventListener( 'wheel', onDocumentMouseWheel, false );

     document.addEventListener( 'touchstart', onDocumentTouchStart, false );
     document.addEventListener( 'touchmove', onDocumentTouchMove, false );

     window.addEventListener( 'resize', onWindowResize, false );

 }

 function onWindowResize() {
     //When the window is zoomed in, make sure that the scene is zoomed in with it.
     camera.aspect = window.innerWidth / window.innerHeight;
     camera.updateProjectionMatrix();

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

 }

 function onDocumentMouseDown( event ) {
     event.preventDefault();
     //Ensure that drag events are monitored
     document.addEventListener( 'mousemove', onDocumentMouseMove, false );
     document.addEventListener( 'mouseup', onDocumentMouseUp, false );

 }

 function onDocumentMouseMove( event ) {
     //Mouse movement distance currentEvent.movementX = currentEvent.screenX - previousEvent.screenX
     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 ) {
     //Ensure that drag events are monitored
     document.removeEventListener( 'mousemove', onDocumentMouseMove );
     document.removeEventListener( 'mouseup', onDocumentMouseUp );

 }

 function onDocumentMouseWheel( event ) {
     //The camera's vision pulls in or away as the mouse scrolls.
     camera.fov += event.deltaY * 0.05;
     camera.updateProjectionMatrix();

 }

 function onDocumentTouchStart( event ) {

     event.preventDefault();
     //There is no movement at the mobile end, so we use touch X touch Y directly to calculate the distance of movement.
     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;

 }
 //Open animation
 function animate() {

     requestAnimationFrame( animate );

     lon +=  0.1;
     lat = Math.max( - 85, Math.min( 85, lat ) );
     phi = THREE.Math.degToRad( 90 - lat ); //Angle to radian
     theta = THREE.Math.degToRad( lon );
     //Calculating the coordinates of the focus of the camera in spherical coordinate system
     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 );

 }

Reference:
https://threejs.org/ ThreeJS official website
https://isux.tencent.com/3d.html Creating the Secret Books of "3D Panoramic Roaming" in H5
http://www.hewebgl.com/article/getarticle/27 ThreeJS Course for WebGL Chinese Network
https://zh.wikipedia.org/wiki/%E7%90%83%E5%BA%A7%E6%A8%99%E7%B3%BB Spherical coordinate system
http://stackoverflow.com/questions/29678510/convert-21-equirectangular-panorama-to-cube-map

Posted by Azala on Tue, 02 Apr 2019 00:00:29 -0700