2
头图

background

You are hey hey hey detective agency internship detective 🕵️, received a higher level assignments, to Zhen Happy Town 🏠 investigation public Zhen not poke 👨 gem 💎 theft, according to the informant tramp old stone 👨🎤 provided clues, the thief hid in the small town, find him quickly, and help Zhenbu not poke and retrieve the stolen gem!

This article uses Three.js SphereGeometry create the 3D panorama preview function, and adds two-dimensional SpriteMaterial , Canvas , three-dimensional GLTF and other interactive points in the panorama to realize a detective game with scene switching and click interaction.

Achieve effect

Swipe the screen left and right to find interaction point in the 3D panorama scene and click to find out where the suspect is actually hiding.

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

💡 online preview: https://dragonir.github.io/3d-panoramic-vision/

Code

Initialize the scene

Create a scene, add cameras, light sources, and rendering.

// 透视摄像机
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1100);
camera.target = new THREE.Vector3(0, 0, 0);
scene = new THREE.Scene();
// 添加环境光
light = new THREE.HemisphereLight(0xffffff);
light.position.set(0, 40, 0);
scene.add(light);
// 添加平行光
light = new THREE.DirectionalLight(0xffffff);
light.position.set(0, 40, -10);
scene.add(light);
// 渲染
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);

Use the sphere to realize the panoramic function

// 创建全景场景
geometry = new THREE.SphereGeometry(500, 60, 60);
// 按z轴翻转
geometry.scale(1, 1, -1);
// 添加室外低画质贴图
outside_low = new THREE.MeshBasicMaterial({
  map: new THREE.TextureLoader().load('./assets/images/outside_low.jpg')
});
// 添加室内低画质贴图
inside_low = new THREE.MeshBasicMaterial({
  map: new THREE.TextureLoader().load('./assets/images/inside_low.jpg')
});
mesh = new THREE.Mesh(geometry, outside_low);
// 异步加载高清纹理图
new THREE.TextureLoader().load('./assets/images/outside.jpg', texture => {
  outside = new THREE.MeshBasicMaterial({
    map: texture
  });
  mesh.material = outside;
});
// 添加到场景中
scene.add(mesh);

📌 panoramic texture is shown in the picture above, which is from Bing .

💡 sphere SphereGeometry

Constructor:

THREE.SphereGeometry(radius, segmentsWidth, segmentsHeight, phiStart, phiLength, thetaStart, thetaLength)
  • radius : radius;
  • segmentsWidth : the number of segments in longitude;
  • segmentsHeight : the number of segments in latitude;
  • phiStart : the radian from which the longitude starts;
  • phiLength : the arc spanned by the longitude;
  • thetaStart : the radian from which the latitude starts;
  • thetaLength : the radian spanned by the latitude.

💡 Basic mesh material MeshBasicMaterial

The material of the sphere is MeshBasicMaterial , which is a simple material that is not affected by the light in the scene. The mesh using this material will be rendered as a simple plane polygon, and it can also display the wireframe of the geometry.

Constructor:

MeshBasicMaterial(parameters: Object)

parameters : (Optional) An object used to define the appearance of a material, with one or more attributes.

Attributes:

  • .alphaMap[Texture] : The alpha map is a grayscale texture used to control the opacity of the entire surface. ( black: completely transparent; white: completely opaque). The default value is null .
  • .aoMap[Texture] : The red channel of this texture is used as an ambient occlusion map. The default value is null .
  • .aoMapIntensity[Float] : The strength of the environmental occlusion effect. The default value is 1 . Zero means no occlusion effect.
  • .color[Color] : The color of the material, the default value is white 0xffffff .
  • .combine[Integer] : How to combine the results of the surface color with the environment map (if any). The options are THREE.Multiply (default), THREE.MixOperation , THREE.AddOperation . If you select more than one, use .reflectivity to blend between the two colors.
  • .envMap[Texture] : Environment map. The default value is null .
  • .lightMap[Texture] : Light map. The default value is null .
  • .lightMapIntensity[Float] : The intensity of the baking light. The default value is 1 .
  • .map[Texture] : texture map. The default is null .
  • .morphTargets[Boolean] : Whether the material uses morphTargets . The default value is false .
  • .reflectivity[Float] : The degree of influence of the environment map on the surface, the default value is 1 , and the effective range is between 0 (no reflection) and 1 (complete reflection).
  • .refractionRatio[Float] : The refractive index should not exceed 1 . The default value is 0.98 .
  • .specularMap[Texture] : The specular map used by the material. The default value is null .
  • .wireframe[Boolean] : Render the geometry as a wireframe. The default value is false (that is, it is rendered as a flat polygon).
  • .wireframeLinecap[String] : Define the appearance of the two ends of the line. Optional values are butt , round and square . The default is round .
  • .wireframeLinejoin[String] : Define the style of the nodes connected by the line. Optional values are round , bevel and miter . The default value is round .
  • .wireframeLinewidth[Float] : Control the width of the wire frame. The default value is 1 .

💡 TextureLoader

TextureLoader starts loading from the given URL and passes the fully loaded texture to onLoad . The method also returns a new texture object, which can be directly used for material creation, loading a class of the material, and internally using ImageLoader to load the file.

Constructor:

TextureLoader(manager: LoadingManager)
  • manager : 061bb4672cb254 used by the loadingManager , the default value is THREE.DefaultLoadingManager .

method:

.load(url: String, onLoad: Function, onProgress: Function, onError: Function) : Texture
  • url URL or path of the file, Data URI .
  • onLoad : Will be called when loading is complete. The callback parameter is texture to be loaded.
  • onProgress : Will be called during the loading process. The parameter is XMLHttpRequest instance of 061bb4672cb2f8, and the instance contains the parameters total and loaded
  • onError : Called when loading error.

Add interaction points

Create a new interaction point array, including the name, zoom ratio, and space 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 }
];

Add two-dimensional static picture interaction points

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);
});

💡 Wizard material SpriteMaterial

Constructor:

SpriteMaterial(parameters : Object)
  • parameters : Optional, an object used to define the appearance of a material, with one or more attributes. Any properties of the material can be passed in from here (including any properties inherited Material and ShaderMaterial
  • SpriteMaterials will not be cropped Material.clippingPlanes

Attributes:

.alphaMap[Texture] : The alpha map is a grayscale texture used to control 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 will be multiplied by color
.map[Texture] : Color map. The default is null .
.rotation[Radians] : sprite , in radians. The default value is 0 .
.sizeAttenuation[Boolean] : Whether the size of the sprite will be attenuated by the camera's depth. (For perspective cameras only.) The default is true .

Using the same method, load the suspect two-dimensional picture and add it 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);
}

Add 3D dynamic model anchor points

The three-dimensional dynamic anchor point is realized by loading the gltf gltf requires a separate introduction of GLTFLoader.js , and the landmark model is constructed Blender

var loader = new THREE.GLTFLoader();
loader.load('./assets/models/anchor.gltf', object => {
  object.scene.traverse(child => {
    if (child.isMesh) {
      // 修改材质样式
      child.material.metalness = .4;
      child.name.includes('黄') && (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);
  })
});

We need requestAnimationFrame by modifying the model in rotation to achieve autobiography animation.

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

Add a two-dimensional text prompt

You can use Canvas create text prompts and add them 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;
}

Instructions:

outsideTextTip = makeTextSprite('进入室内查找');
outsideTextTip.scale.set(2.2, 2.2, 2)
outsideTextTip.position.set(-0.35, -1, 10);
scene.add(outsideTextTip);
  • 💡 Canvas Canvas can be used as Three.js texture map CanvasTexture . Canvas canvas by 2D API draw geometric shapes, can Canvas After drawing a contour and as Three.js texture map mesh model objects, sprites model.
  • 💡 measureText() method returns an object that contains the specified font width in pixels. If you need to know the width of the text before it is output to the canvas, use this method. measureText Syntax: context.measureText(text).width .

Add 3D text prompt

Due to limited time, 3D text is not used in this example, but using 3D text on the page will achieve better visual effects. If you want to know the specific implementation details, you can read another article of , the follow-up 161bb4672cb7b5 mouse capture and other contents are also explained in detail in this article.

🔗 Portal: uses three.js to realize the cool acidic style 3D page

Mouse capture

Use Raycaster get the click-selected grid object, 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('小偷就在这里', 2000);
      loadMurderer();
    } else {
      Toast(`小偷不在${name.includes('car') ? '车里' : name.includes('people') ? '人群' : name.includes('eating') ? '餐厅' : '这里'}`, 2000);
    }
  }
  onPointerDownPointerX = event.clientX;
  onPointerDownPointerY = event.clientY;
  onPointerDownLon = lon;
  onPointerDownLat = lat;
}

Scene switching

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;
    // 加载新的全景图场景
    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);
}
  • 💡 the properties of the 061bb4672cb8ef perspective camera are created, we can modify them at will according to personal needs, but after the properties of the camera are modified, we need to call the updateProjectionMatrix() method to update.
  • 💡 THREE.Math.degToRad : Convert degrees to radians.

At this point, the 3D is fully realized.

🔗 complete code: https://github.com/dragonir/3d-panoramic-vision

Summarize

The main knowledge points involved in this case include:

  • Sphere SphereGeometry
  • Basic mesh material MeshBasicMaterial
  • Elf material SpriteMaterial
  • Material loading TextureLoader
  • Text texture Canvas
  • Mouse capture Raycaster

Reference


dragonir
1.8k 声望3.9k 粉丝

Accepted ✔