Files
like_pq/test.html
2025-08-29 21:54:28 +08:00

403 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Gallery - Center View & Realistic Materials</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: Arial, sans-serif;
}
canvas {
display: block;
}
#info {
position: absolute;
top: 10px;
width: 100%;
text-align: center;
color: white;
pointer-events: none;
text-shadow: 1px 1px 1px black;
}
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 24px;
text-shadow: 1px 1px 1px black;
}
</style>
</head>
<body>
<div id="info">3D Gallery - 拖动鼠标旋转视图,滚轮缩放</div>
<div id="loading">Loading...</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/TextureLoader.js"></script>
<script>
// 初始化场景、相机和渲染器
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111111);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1.6, 0); // 1.6米高,模拟人眼高度
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// 场景中心点(观察目标)
const centerPoint = new THREE.Vector3(0, 1.6, 0);
// 添加轨道控制器,设置目标为场景中心
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.copy(centerPoint);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 1;
controls.maxDistance = 10;
controls.maxPolarAngle = Math.PI * 0.8; // 限制不能看穿地面
// 添加灯光
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 2, 1);
scene.add(directionalLight);
const pointLight = new THREE.PointLight(0xffffff, 0.5, 10);
pointLight.position.set(0, 3, 0);
scene.add(pointLight);
// 创建画廊展品
const galleryItems = [];
const itemCount = 8;
const radius = 4; // 缩小半径使画作更集中
// 创建纹理加载器
const textureLoader = new THREE.TextureLoader();
// 画框材质
const frameMaterials = [
new THREE.MeshStandardMaterial({ color: 0xc9b38b, metalness: 0.3, roughness: 0.4 }),
new THREE.MeshStandardMaterial({ color: 0x7a6a5f, metalness: 0.4, roughness: 0.6 }),
new THREE.MeshStandardMaterial({ color: 0xe5e5e5, metalness: 0.7, roughness: 0.2 }),
new THREE.MeshStandardMaterial({ color: 0x3a2e1e, metalness: 0.1, roughness: 0.8 })
];
// 创建带画框的照片(自适应图片比例)
function createFramedPhoto(texture, frameMaterial, maxWidth, maxHeight, frameWidth) {
const group = new THREE.Group();
// 计算图片实际比例
const imageRatio = texture.image.width / texture.image.height;
let width, height;
// 根据图片比例调整显示尺寸
if (imageRatio > 1) {
// 横向图片
width = maxWidth;
height = maxWidth / imageRatio;
} else {
// 纵向图片
height = maxHeight;
width = maxHeight * imageRatio;
}
// 照片部分
const photoGeometry = new THREE.PlaneGeometry(width, height);
const photoMaterial = new THREE.MeshStandardMaterial({
map: texture,
side: THREE.DoubleSide,
});
const photo = new THREE.Mesh(photoGeometry, photoMaterial);
group.add(photo);
// 画框部分(根据实际图片尺寸调整)
const frameShape = new THREE.Shape();
const outerWidth = width + frameWidth * 2;
const outerHeight = height + frameWidth * 2;
frameShape.moveTo(-outerWidth / 2, -outerHeight / 2);
frameShape.lineTo(outerWidth / 2, -outerHeight / 2);
frameShape.lineTo(outerWidth / 2, outerHeight / 2);
frameShape.lineTo(-outerWidth / 2, outerHeight / 2);
frameShape.lineTo(-outerWidth / 2, -outerHeight / 2);
const innerPath = new THREE.Path();
innerPath.moveTo(-width / 2, -height / 2);
innerPath.lineTo(width / 2, -height / 2);
innerPath.lineTo(width / 2, height / 2);
innerPath.lineTo(-width / 2, height / 2);
innerPath.lineTo(-width / 2, -height / 2);
frameShape.holes.push(innerPath);
const extrudeSettings = {
steps: 1,
depth: frameWidth / 2,
bevelEnabled: true,
bevelThickness: 0.05,
bevelSize: 0.05,
bevelOffset: 0,
bevelSegments: 8
};
const frameGeometry = new THREE.ExtrudeGeometry(frameShape, extrudeSettings);
const frame = new THREE.Mesh(frameGeometry, frameMaterial);
frame.position.z = -frameWidth / 4;
group.add(frame);
// 添加画框背面
const backGeometry = new THREE.PlaneGeometry(outerWidth, outerHeight);
const backMaterial = new THREE.MeshStandardMaterial({
color: 0x222222,
side: THREE.DoubleSide
});
const back = new THREE.Mesh(backGeometry, backMaterial);
back.position.z = -frameWidth / 2;
group.add(back);
return group;
}
// 加载纹理
const textures = [];
let loadedTextures = 0;
// 加载地面和墙壁纹理
const floorTexture = textureLoader.load('https://threejs.org/examples/textures/hardwood2_diffuse.jpg');
const floorNormalMap = textureLoader.load('https://threejs.org/examples/textures/hardwood2_normal.jpg');
const floorRoughnessMap = textureLoader.load('https://threejs.org/examples/textures/hardwood2_roughness.jpg');
const wallTexture = textureLoader.load('https://threejs.org/examples/textures/brick_diffuse.jpg');
const wallNormalMap = textureLoader.load('https://threejs.org/examples/textures/brick_normal.jpg');
const wallRoughnessMap = textureLoader.load('https://threejs.org/examples/textures/brick_roughness.jpg');
function loadTextures() {
// 加载画作纹理
for (let i = 0; i < itemCount; i++) {
textureLoader.load(
`./${i + 1}.jpg`,
(texture) => {
textures[i] = texture;
loadedTextures++;
if (loadedTextures === itemCount) {
initGallery();
document.getElementById('loading').style.display = 'none';
}
},
undefined,
(err) => {
console.error('Error loading texture', err);
textures[i] = null;
loadedTextures++;
if (loadedTextures === itemCount) {
initGallery();
document.getElementById('loading').style.display = 'none';
}
}
);
}
}
function initGallery() {
// 创建展品
for (let i = 0; i < itemCount; i++) {
const angle = (i / itemCount) * Math.PI * 2;
const x = Math.cos(angle) * radius;
const z = Math.sin(angle) * radius;
const frameMaterial = frameMaterials[Math.floor(Math.random() * frameMaterials.length)];
const framedPhoto = createFramedPhoto(
textures[i], // 加载的纹理
frameMaterial, // 画框材质
2.0, // 最大宽度
1.5, // 最大高度
0.05 // 画框宽度
);
framedPhoto.position.set(x, 1.6, z); // 画作中心与眼睛平齐
framedPhoto.lookAt(centerPoint);
// 为整个group添加用户数据
framedPhoto.userData = {
id: i,
title: `Artwork ${i + 1}`,
description: `This is a description for artwork ${i + 1}`,
originalPosition: new THREE.Vector3(x, 1.6, z),
originalRotation: framedPhoto.rotation.clone(),
originalScale: new THREE.Vector3(1, 1, 1)
};
// 为group中的每个子对象添加引用
framedPhoto.children.forEach(child => {
child.userData.parentGroup = framedPhoto;
});
scene.add(framedPhoto);
galleryItems.push(framedPhoto);
}
// 添加地面 - 使用真实木质纹理
const floorGeometry = new THREE.CircleGeometry(radius + 2, 64);
const floorMaterial = new THREE.MeshStandardMaterial({
map: floorTexture,
normalMap: floorNormalMap,
roughnessMap: floorRoughnessMap,
roughness: 0.8,
metalness: 0.1,
side: THREE.DoubleSide
});
// 调整纹理重复
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
floorTexture.repeat.set(4, 4);
floorNormalMap.wrapS = floorNormalMap.wrapT = THREE.RepeatWrapping;
floorNormalMap.repeat.set(4, 4);
floorRoughnessMap.wrapS = floorRoughnessMap.wrapT = THREE.RepeatWrapping;
floorRoughnessMap.repeat.set(4, 4);
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.position.y = 0;
scene.add(floor);
// 添加墙壁 - 使用真实砖墙纹理
const wallHeight = 3.5;
const wallGeometry = new THREE.CylinderGeometry(radius + 2, radius + 2, wallHeight, 64, 1, true);
const wallMaterial = new THREE.MeshStandardMaterial({
map: wallTexture,
normalMap: wallNormalMap,
roughnessMap: wallRoughnessMap,
roughness: 0.7,
metalness: 0,
side: THREE.DoubleSide
});
// 调整纹理重复
wallTexture.wrapS = wallTexture.wrapT = THREE.RepeatWrapping;
wallTexture.repeat.set(2, 1);
wallNormalMap.wrapS = wallNormalMap.wrapT = THREE.RepeatWrapping;
wallNormalMap.repeat.set(2, 1);
wallRoughnessMap.wrapS = wallRoughnessMap.wrapT = THREE.RepeatWrapping;
wallRoughnessMap.repeat.set(2, 1);
const walls = new THREE.Mesh(wallGeometry, wallMaterial);
walls.position.y = wallHeight / 2;
scene.add(walls);
// 添加聚光灯照射每幅画
for (let i = 0; i < itemCount; i++) {
const item = galleryItems[i];
const spotLight = new THREE.SpotLight(0xffffff, 0.8, 10, Math.PI / 6, 0.5);
spotLight.position.set(
item.userData.originalPosition.x * 1.5,
item.userData.originalPosition.y + 1.5,
item.userData.originalPosition.z * 1.5
);
spotLight.target.position.copy(item.position);
scene.add(spotLight);
scene.add(spotLight.target);
// 添加灯光辅助(调试用)
// const helper = new THREE.SpotLightHelper(spotLight);
// scene.add(helper);
}
}
// 加载纹理
loadTextures();
// 交互系统
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
let selectedItem = null;
// 鼠标移动事件处理
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (selectedItem) {
selectedItem.position.copy(selectedItem.userData.originalPosition);
selectedItem.rotation.copy(selectedItem.userData.originalRotation);
selectedItem.scale.copy(selectedItem.userData.originalScale);
selectedItem = null;
}
for (let i = 0; i < intersects.length; i++) {
const intersected = intersects[i].object;
if (intersected.userData.parentGroup) {
const parentGroup = intersected.userData.parentGroup;
parentGroup.scale.set(1.1, 1.1, 1.1);
const direction = new THREE.Vector3().subVectors(
parentGroup.position,
centerPoint
).normalize();
parentGroup.position.add(direction.multiplyScalar(0.3));
parentGroup.lookAt(centerPoint);
selectedItem = parentGroup;
break;
}
}
}
function onClick() {
if (selectedItem) {
console.log('Selected:', selectedItem.userData);
alert(`Selected: ${selectedItem.userData.title}\n${selectedItem.userData.description}`);
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
window.addEventListener('resize', onWindowResize);
window.addEventListener('mousemove', onMouseMove, false);
// window.addEventListener('click', onClick, false);
function animate() {
requestAnimationFrame(animate);
controls.update();
if (!controls.target.equals(centerPoint)) {
controls.target.copy(centerPoint);
}
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>