background

You are the intern detective of Hey Hey Hey detective agency 🕵️, received a task assigned by the superior, to investigate the theft of 👨 gem Gem Of citizens Zhen Buzhan in 🏠 of Zhen Happy town. According to the clue provided by the informant, old stone tetraphim, the thief is hiding in the town, quickly find him out and help Zhen Buzhan to retrieve the stolen gem!

In this paper, Three. Js SphereGeometry is used to create 3D panorama preview function, and 2d SpriteMaterial, Canvas, 3D GLTF and other interaction points are added to the panorama to achieve a detective mini-game with scene switch and click interaction.

Implementation effect

Swipe the screen left and right to find the interaction point in the 3D panoramic scene and click to find the real hiding position of the suspect.

It has been adapted to the mobile terminal and can be accessed on the mobile phone.

💡 Online preview: Dragonir.github. IO / 3D-Panorami…

Code implementation

Initialization Scenario

Create scene, add camera, light, render.

// Perspective camera
camera = new THREE.PerspectiveCamera(75.window.innerWidth / window.innerHeight, 1.1100);
camera.target = new THREE.Vector3(0.0.0);
scene = new THREE.Scene();
// Add ambient light
light = new THREE.HemisphereLight(0xffffff);
light.position.set(0.40.0);
scene.add(light);
// Add parallel light
light = new THREE.DirectionalLight(0xffffff);
light.position.set(0.40, -10);
scene.add(light);
/ / rendering
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
Copy the code

Use sphere to achieve panoramic function

// Create a panoramic scene
geometry = new THREE.SphereGeometry(500.60.60);
// Flip the z axis
geometry.scale(1.1, -1);
// Add an outdoor low-quality map
outside_low = new THREE.MeshBasicMaterial({
  map: new THREE.TextureLoader().load('./assets/images/outside_low.jpg')});// Add an interior low-quality map
inside_low = new THREE.MeshBasicMaterial({
  map: new THREE.TextureLoader().load('./assets/images/inside_low.jpg')}); mesh =new THREE.Mesh(geometry, outside_low);
// Asynchronously load hd texture map
new THREE.TextureLoader().load('./assets/images/outside.jpg'.texture= > {
  outside = new THREE.MeshBasicMaterial({
    map: texture
  });
  mesh.material = outside;
});
// Add to the scene
scene.add(mesh);
Copy the code

📌 Panorama map as shown above, image from Bing.

💡 sphere SphereGeometry

Constructor:

THREE.SphereGeometry(radius, segmentsWidth, segmentsHeight, phiStart, phiLength, thetaStart, thetaLength)
Copy the code
  • radius: radius;
  • segmentsWidth: number of sections in longitude;
  • segmentsHeight: number of segments in latitude;
  • phiStart: The radian at the beginning of longitude;
  • phiLengthThe arc crossed; longitude.
  • thetaStart: Radian at the beginning of latitude;
  • thetaLength: Radian across latitude.

💡 MeshBasicMaterial

The material of the sphere is MeshBasicMaterial, a simple material that is not affected by lighting in the scene. Meshes with this material are rendered as simple flat polygons and can also display geometric wireframes.

Constructor:

MeshBasicMaterial(parameters: Object)
Copy the code

Parameters :(optional) an object used to define the appearance of a material, with one or more properties.

Properties:

  • .alphaMap[Texture]:alphaA map is a grayscale texture that controls the opacity of the entire surface. (Black: completely transparent; White: completely opaque). The default value isnull.
  • .aoMap[Texture]The red channel of this texture is used as an ambient occlusion map. The default value isnull.
  • .aoMapIntensity[Float]: Intensity of ambient occlusion effect. The default value is1. Zero is no occlusion effect.
  • .color[Color]: The color of the material. The default value is white0xffffff.
  • .combine[Integer]: How to combine the surface color results with the environment map (if any). Options forTHREE.Multiply(Default value),THREE.MixOperation.THREE.AddOperation. If multiple values are selected, this parameter is used.reflectivityMix between the two colors.
  • .envMap[Texture]: Environment map. The default value isnull.
  • .lightMap[Texture]: Light map. The default value isnull.
  • .lightMapIntensity[Float]: Intensity of baking light. The default value is1.
  • .map[Texture]: Texture map. The default isnull.
  • .morphTargets[Boolean]: Whether the material is usedmorphTargets. The default value isfalse.
  • .reflectivity[Float]: The degree to which an environment map affects a surface. The default value is1, the effective range is between0(No reflection) and1(Full reflection) between.
  • .refractionRatio[Float]: Refractive index should not exceed1. The default value is0.98.
  • .specularMap[Texture]: Specular map used by the material. The default value isnull.
  • .wireframe[Boolean]: Render geometry as wireframes. The default value isfalse(that is, rendered as flat polygons).
  • .wireframeLinecap[String]: Defines the appearance of both ends of the line. Optional value isbutt.roundsquare. The default isround.
  • .wireframeLinejoin[String]: defines the style of the wire connecting node. Optional value isround.bevelmiter. The default value isround.
  • .wireframeLinewidth[Float]: Controls the width of the wire frame. The default value is1.

💡 TextureLoader

TextureLoader loads from the given URL and passes the fully loaded texture to onLoad. This method also returns a new texture object that can be used directly for material creation, a class that loads the material, and internally uses ImageLoader to load the file.

Constructor:

TextureLoader(manager: LoadingManager)
Copy the code
  • manager: used by the loaderloadingManager, the default value isTHREE.DefaultLoadingManager.

Methods:

.load(url: String.onLoad: Function.onProgress: Function.onError: Function) : Texture
Copy the code
  • url: the fileURLOr the path, or it could beData URI.
  • onLoad: will be called when the load is complete. The callback parameter is the one to be loadedtexture.
  • onProgress: will be called during loading. Parameters forXMLHttpRequestInstance, which containstotalloadedParameters.
  • onError: is called when loading errors occur.

Adding interaction points

Create a new interaction point group containing the name, scale, and spatial coordinates of each interaction point.

var interactPoints = [
  { name: 'point_0_outside_house'.scale: 2.x: 0.y: 1.5.z: 24 },
  { name: 'point_1_outside_car'.scale: 3.x: 40.y: 1.z: -20 },
  { name: 'point_2_outside_people'.scale: 3.x: -20.y: 1.z: -30 },
  { name: 'point_3_inside_eating_room'.scale: 2.x: -30.y: 1.z: 20 },
  { name: 'point_4_inside_bed_room'.scale: 3.x: 48.y: 0.z: -20}];Copy the code

Add a 2d static image interaction point

let pointMaterial = new THREE.SpriteMaterial({
  map: new THREE.TextureLoader().load('./assets/images/point.png')}); interactPoints.map(item= > {
  let point = new THREE.Sprite(pointMaterial);
  point.name = item.name;
  point.scale.set(item.scale * 1.2, item.scale * 1.2, item.scale * 1.2);
  point.position.set(item.x, item.y, item.z);
  scene.add(point);
});
Copy the code

💡 SpriteMaterial SpriteMaterial

Constructor:

SpriteMaterial(parameters : Object)
Copy the code
  • parameters: Optional, an object used to define the appearance of a material, with one or more properties. Any properties of a material can be passed in from here (including fromMaterialShaderMaterialAny inherited properties).
  • SpriteMaterialsWill not beMaterial.clippingPlanesCut out.

Properties:

AlphaMap [Texture] : The Alpha map is a grayscale Texture that controls the opacity of the entire surface. The default value is null. Color [color] : The color of the material. The default value is white 0xffFFFF. .map is multiplied by color. .map[Texture] : color map. The default is null. Rotation [Radians] : Sprite rotation, in Radians. The default value is 0. .size” [Boolean] : Will the Sprite’s size be attenuated by the camera depth. (Perspective cameras only.) The default is true.

Using the same method, load a 2d image of the suspect to add to the scene.

function loadMurderer() {
  let material = new THREE.SpriteMaterial({
    map: new THREE.TextureLoader().load('./assets/models/murderer.png')}); murderer =new THREE.Sprite(material);
  murderer.name = 'murderer';
  murderer.scale.set(12.12.12);
  murderer.position.set(43, -3, -20);
  scene.add(murderer);
}
Copy the code

Add 3d dynamic model anchors

The 3D dynamic anchor point is realized by loading GLTF model of landmark anchor point shape. Loading GLTF requires the introduction of GLtFloader.js separately, and the landmark model is constructed using Blender.

var loader = new THREE.GLTFLoader();
loader.load('./assets/models/anchor.gltf'.object= > {
  object.scene.traverse(child= > {
    if (child.isMesh) {
      // Modify the material style
      child.material.metalness = 4.;
      child.name.includes('yellow') && (child.material.color = new THREE.Color(0xfffc00))}}); object.scene.rotation.y =Math.PI / 2;
  interactPoints.map(item= > {
    let anchor = object.scene.clone();
    anchor.position.set(item.x, item.y + 3, item.z);
    anchor.name = item.name;
    anchor.scale.set(item.scale * 3, item.scale * 3, item.scale * 3); scene.add(anchor); })});Copy the code

You need to implement the autobiography animation by modifying the model’s rotation in the requestAnimationFrame.

function animate() {
  requestAnimationFrame(animate);
  anchorMeshes.map(item= > {
    item.rotation.y += 0.02;
  });
}
Copy the code

Add a 2d text prompt

You can use Canvas to create text hints to add to the scene.

function makeTextSprite(message, parameters) {
  if (parameters === undefined) parameters = {};
  var fontface = parameters.hasOwnProperty("fontface")? parameters["fontface"] : "Arial";
  var fontsize = parameters.hasOwnProperty("fontsize")? parameters["fontsize"] : 32;
  var borderThickness = parameters.hasOwnProperty("borderThickness")? parameters["borderThickness"] : 4;
  var borderColor = parameters.hasOwnProperty("borderColor")? parameters["borderColor"] : { r: 0.g: 0.b: 0.a: 1.0 };
  var canvas = document.createElement('canvas');
  var context = canvas.getContext('2d');
  context.font = fontsize + "px " + fontface;
  var metrics = context.measureText(message);
  var textWidth = metrics.width;
  context.strokeStyle = "rgba(" + borderColor.r + "," + borderColor.g + "," + borderColor.b + "," + borderColor.a + ")";
  context.lineWidth = borderThickness;
  context.fillStyle = "#fffc00";
  context.fillText(message, borderThickness, fontsize + borderThickness);
  context.font = 48 + "px " + fontface;
  var texture = new THREE.Texture(canvas);
  texture.needsUpdate = true;
  var spriteMaterial = new THREE.SpriteMaterial({ map: texture });
  var sprite = new THREE.Sprite(spriteMaterial);
  return sprite;
}
Copy the code

Usage:

outsideTextTip = makeTextSprite('Look inside');
outsideTextTip.scale.set(2.2.2.2.2)
outsideTextTip.position.set(-0.35, -1.10);
scene.add(outsideTextTip);
Copy the code
  • 💡 CanvasThe canvas can be used asThree.jsTexture mapCanvasTexture.CanvasThe canvas can be accessed through2D APIDraw a variety of geometric shapes that can be passedCanvasDraw an outline and then act asThree.jsTexture mapping of mesh model, Sprite model and other model objects.
  • 💡 measureText()Method returns an object containing the specified font width in pixels. Use this method if you need to know the width of the text before it is output to the canvas.measureTextGrammar:context.measureText(text).width.

Add 3d text prompt

Due to the limited time, 3D text is not used in this example, but using 3D text in the page will achieve better visual effect. For details of the implementation, you can read my other article, and the following content such as mouse capture is also explained in detail in this article.

🔗 Portal: Use three.js for cool, acid-styled 3D pages

The mouse capture

Use Raycaster to get click selected mesh objects and add click interaction.

function onDocumentMouseDown(event) {
  raycaster.setFromCamera(mouse, camera);
  var intersects = raycaster.intersectObjects(interactMeshes);
  if (intersects.length > 0) {
    let name = intersects[0].object.name;
    if (name === 'point_0_outside_house') {
      camera_time = 1;
    } else if (name === 'point_4_inside_bed_room') {
      Toast('The thief is here'.2000);
      loadMurderer();
    } else {
      Toast('The thief is not here${name.includes('car')?'car' : name.includes('people')?'people' : name.includes('eating')?'restaurant' : 'here'}`.2000);
    }
  }
  onPointerDownPointerX = event.clientX;
  onPointerDownPointerY = event.clientY;
  onPointerDownLon = lon;
  onPointerDownLat = lat;
}
Copy the code

Scene: the switch

function update() {
  lat = Math.max(-85.Math.min(85, lat));
  phi = THREE.Math.degToRad(90 - lat);
  theta = THREE.Math.degToRad(lon);
  camera.target.x = 500 * Math.sin(phi) * Math.cos(theta);
  camera.target.y = 500 * Math.cos(phi);
  camera.target.z = 500 * Math.sin(phi) * Math.sin(theta);
  camera.lookAt(camera.target);
  if (camera_time > 0 && camera_time < 50) {
    camera.target.x = 0;
    camera.target.y = 1;
    camera.target.z = 24;
    camera.lookAt(camera.target);
    camera.fov -= 1;
    camera.updateProjectionMatrix();
    camera_time++;
    outsideTextTip.visible = false;
  } else if (camera_time === 50) {
    lat = -2;
    lon = 182;
    camera_time = 0;
    camera.fov = 75;
    camera.updateProjectionMatrix();
    mesh.material = inside_low;
    // Load a new panorama scene
    new THREE.TextureLoader().load('./assets/images/inside.jpg'.function (texture) {
      inside = new THREE.MeshBasicMaterial({
        map: texture
      });
      mesh.material = inside;
    });
    loadMarker('inside');
  }
  renderer.render(scene, camera);
}
Copy the code
  • 💡We can modify the attributes of the perspective camera according to personal needs after creation, but the camera attributes need to be called after modificationupdateProjectionMatrix()Method to update.
  • 💡 THREE.Math.degToRad: Convert degrees to radians.

Here, the 3D panoramic function is fully realized.

🔗 Full code: github.com/dragonir/3d…

conclusion

This case mainly involves knowledge points including:

  • A sphereSphereGeometry
  • Base mesh materialMeshBasicMaterial
  • The elves materialSpriteMaterial
  • The material is loadedTextureLoader
  • Text textureCanvas
  • The mouse captureRaycaster

The resources

  • [1]. Use three.js in React to realize Web VR panoramic house viewing
  • [2]. Use three.js to create cool acid style 3D pages