first commit
This commit is contained in:
403
test.html
Normal file
403
test.html
Normal file
@@ -0,0 +1,403 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user