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;phiLength
The 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]
:alpha
A 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.reflectivity
Mix 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
.round
和square
. The default isround
..wireframeLinejoin[String]
: defines the style of the wire connecting node. Optional value isround
.bevel
和miter
. 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 fileURL
Or 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 forXMLHttpRequest
Instance, which containstotal
和loaded
Parameters.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 fromMaterial
和ShaderMaterial
Any inherited properties).SpriteMaterials
Will not beMaterial.clippingPlanes
Cut 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
💡
Canvas
The canvas can be used asThree.js
Texture mapCanvasTexture
.Canvas
The canvas can be accessed through2D API
Draw a variety of geometric shapes that can be passedCanvas
Draw an outline and then act asThree.js
Texture 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.measureText
Grammar: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 sphere
SphereGeometry
- Base mesh material
MeshBasicMaterial
- The elves material
SpriteMaterial
- The material is loaded
TextureLoader
- Text texture
Canvas
- The mouse capture
Raycaster
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