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 fromBing
.
💡
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]
: Thealpha
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 isnull
..aoMapIntensity[Float]
: The strength of the environmental occlusion effect. The default value is1
. Zero means no occlusion effect..color[Color]
: The color of the material, the default value is white0xffffff
..combine[Integer]
: How to combine the results of the surface color with the environment map (if any). The options areTHREE.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 isnull
..lightMap[Texture]
: Light map. The default value isnull
..lightMapIntensity[Float]
: The intensity of the baking light. The default value is1
..map[Texture]
: texture map. The default isnull
..morphTargets[Boolean]
: Whether the material usesmorphTargets
. The default value isfalse
..reflectivity[Float]
: The degree of influence of the environment map on the surface, the default value is1
, and the effective range is between0
(no reflection) and1
(complete reflection)..refractionRatio[Float]
: The refractive index should not exceed1
. The default value is0.98
..specularMap[Texture]
: The specular map used by the material. The default value isnull
..wireframe[Boolean]
: Render the geometry as a wireframe. The default value isfalse
(that is, it is rendered as a flat polygon)..wireframeLinecap[String]
: Define the appearance of the two ends of the line. Optional values arebutt
,round
andsquare
. The default isround
..wireframeLinejoin[String]
: Define the style of the nodes connected by the line. Optional values areround
,bevel
andmiter
. The default value isround
..wireframeLinewidth[Float]
: Control the width of the wire frame. The default value is1
.
💡
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 theloadingManager
, the default value isTHREE.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 istexture
to be loaded.onProgress
: Will be called during the loading process. The parameter isXMLHttpRequest
instance of 061bb4672cb2f8, and the instance contains the parameterstotal
andloaded
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 inheritedMaterial
andShaderMaterial
SpriteMaterials
will not be croppedMaterial.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 asThree.js
texture mapCanvasTexture
.Canvas
canvas by2D API
draw geometric shapes, canCanvas
After drawing a contour and asThree.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 theupdateProjectionMatrix()
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。