目錄
版本1.0:簡易版本
版本2.0:建筑渲染
版本3.0:優化建筑群
版本4.0:增加公路和車流
版本5.0:去除壓在公路上的建筑
版本6.0:優化車流群
版本7.0:添加煙花效果
版本8.0:添加樹木
版本9.0:美化建筑群
版本10.0:添加云朵
版本11.0:添加動態熱氣球
版本1.0:簡易版本
<!DOCTYPE html>
<html>
<head><title>3D凱旋門與擴展建筑群(斜角俯視)</title><style>body { margin: 0; }canvas { display: block; }</style>
</head>
<body><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 設置場景const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0xffffff, 1); // 白色背景document.body.appendChild(renderer.domElement);// 添加光源const ambientLight = new THREE.AmbientLight(0x404040);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);directionalLight.position.set(0, 1, 1);scene.add(directionalLight);// 創建凱旋門主要結構const archMaterial = new THREE.MeshPhongMaterial({ color: 0xD2B48C });// 底部基座const baseGeometry = new THREE.BoxGeometry(8, 2, 4);const base = new THREE.Mesh(baseGeometry, archMaterial);base.position.y = 1;scene.add(base);// 左右立柱const pillarGeometry = new THREE.BoxGeometry(2, 6, 4);const leftPillar = new THREE.Mesh(pillarGeometry, archMaterial);leftPillar.position.set(-3, 5, 0);scene.add(leftPillar);const rightPillar = new THREE.Mesh(pillarGeometry, archMaterial);rightPillar.position.set(3, 5, 0);scene.add(rightPillar);// 頂部橫梁const topGeometry = new THREE.BoxGeometry(8, 2, 4);const topBeam = new THREE.Mesh(topGeometry, archMaterial);topBeam.position.y = 8;scene.add(topBeam);// 添加簡單的裝飾細節const detailGeometry = new THREE.BoxGeometry(7, 0.5, 0.5);const detail = new THREE.Mesh(detailGeometry, archMaterial);detail.position.set(0, 7, 2);scene.add(detail);// 添加擴展建筑群(5環布局,不遮擋凱旋門)const buildingMaterial = new THREE.MeshPhongMaterial({ color: 0x808080 }); // 灰色高樓材質const buildingGeometry = new THREE.BoxGeometry(3, 10, 3); // 高樓基本形狀// 定義5環,調整半徑和高度const rings = 5;const baseRadius = 20; // 起始環半徑const ringSpacing = 10; // 每環間距for (let ring = 1; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4; // 每環建筑數量遞增for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;const height = 6 + Math.random() * 6 * ring; // 高度6-12到6-36const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height / 2, z);buildingMesh.scale.y = height / 10; // 調整高度縮放scene.add(buildingMesh);}}// 設置相機位置(斜角俯視)camera.position.set(30, 30, 30); // 斜上方位置camera.lookAt(0, 5, 0); // 聚焦于凱旋門中心// 動畫循環function animate() {requestAnimationFrame(animate);// 添加旋轉動畫scene.rotation.y += 0.002; // 減慢旋轉速度renderer.render(scene, camera);}animate();// 處理窗口大小變化window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>
效果圖:
版本2.0:建筑渲染
<!DOCTYPE html>
<html>
<head><title>3D凱旋門與多彩建筑群(斜角俯視)</title><style>body { margin: 0; }canvas { display: block; }</style>
</head>
<body><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 設置場景const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0xffffff, 1); // 白色背景document.body.appendChild(renderer.domElement);// 添加光源const ambientLight = new THREE.AmbientLight(0x404040);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);directionalLight.position.set(0, 1, 1);scene.add(directionalLight);// 創建凱旋門主要結構const archMaterial = new THREE.MeshPhongMaterial({ color: 0xD2B48C });// 底部基座const baseGeometry = new THREE.BoxGeometry(8, 2, 4);const base = new THREE.Mesh(baseGeometry, archMaterial);base.position.y = 1;scene.add(base);// 左右立柱const pillarGeometry = new THREE.BoxGeometry(2, 6, 4);const leftPillar = new THREE.Mesh(pillarGeometry, archMaterial);leftPillar.position.set(-3, 5, 0); // 修正:移除 .demoscene.add(leftPillar);const rightPillar = new THREE.Mesh(pillarGeometry, archMaterial);rightPillar.position.set(3, 5, 0);scene.add(rightPillar);// 頂部橫梁const topGeometry = new THREE.BoxGeometry(8, 2, 4);const topBeam = new THREE.Mesh(topGeometry, archMaterial);topBeam.position.y = 8;scene.add(topBeam);// 添加簡單的裝飾細節const detailGeometry = new THREE.BoxGeometry(7, 0.5, 0.5);const detail = new THREE.Mesh(detailGeometry, archMaterial);detail.position.set(0, 7, 2);scene.add(detail);// 添加擴展建筑群(5環布局,豐富顏色和窗戶)const buildingGeometry = new THREE.BoxGeometry(3, 10, 3); // 高樓基本形狀const windowMaterial = new THREE.MeshPhongMaterial({ color: 0xFFFFFF }); // 白色窗戶材質const windowGeometry = new THREE.BoxGeometry(0.4, 0.4, 0.1); // 窗戶形狀// 定義顏色調色板const buildingColors = [0xFF6347, // 番茄紅0x4682B4, // 鋼藍0x32CD32, // 檸檬綠0xFFD700, // 金黃0x9932CC, // 深紫0xFF4500, // 橙紅0x00CED1 // 深青];// 定義5環const rings = 5;const baseRadius = 20; // 起始環半徑const ringSpacing = 10; // 每環間距for (let ring = 1; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4; // 每環建筑數量遞增for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;const height = 6 + Math.random() * 6 * ring; // 高度6-12到6-36// 創建建筑const buildingMaterial = new THREE.MeshPhongMaterial({ color: buildingColors[Math.floor(Math.random() * buildingColors.length)] // 隨機顏色});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height / 2, z);buildingMesh.scale.y = height / 10; // 調整高度縮放scene.add(buildingMesh);// 添加窗戶(在建筑正面和側面)const numWindows = Math.floor(height / 2); // 根據高度確定窗戶數量for (let w = 0; w < numWindows; w++) {// 正面窗戶const windowMesh1 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh1.position.set(x + 1.2, (w * 1.5) + 1, z + 1.51); // 建筑正面windowMesh1.scale.y = height / 10;scene.add(windowMesh1);const windowMesh2 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh2.position.set(x - 1.2, (w * 1.5) + 1, z + 1.51); // 建筑正面另一側windowMesh2.scale.y = height / 10;scene.add(windowMesh2);// 側面窗戶const windowMesh3 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh3.position.set(x + 1.51, (w * 1.5) + 1, z + 1.2); // 建筑側面windowMesh3.scale.y = height / 10;scene.add(windowMesh3);const windowMesh4 = new THREE.Mesh(windowGeometry, windowMaterial); // 修正:outraTHREE 改為 THREEwindowMesh4.position.set(x + 1.51, (w * 1.5) + 1, z - 1.2); // 建筑側面另一側windowMesh4.scale.y = height / 10;scene.add(windowMesh4);}}}// 設置相機位置(斜角俯視)camera.position.set(30, 30, 30); // 斜上方位置camera.lookAt(0, 5, 0); // 聚焦于凱旋門中心// 動畫循環function animate() {requestAnimationFrame(animate);// 添加旋轉動畫scene.rotation.y += 0.002; // 減慢旋轉速度renderer.render(scene, camera);}animate();// 處理窗口大小變化window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>
效果圖
版本3.0:優化建筑群
<!DOCTYPE html>
<html>
<head><title>3D超大精細凱旋門與多彩建筑群(斜角俯視)</title><style>body { margin: 0; }canvas { display: block; }</style>
</head>
<body><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 設置場景const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0xffffff, 1); // 白色背景document.body.appendChild(renderer.domElement);// 添加光源(增強細節)const ambientLight = new THREE.AmbientLight(0x404040, 1.2);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 凱旋門材質(增加石材質感)const archMaterial = new THREE.MeshStandardMaterial({ color: 0xD2B48C, roughness: 0.8, metalness: 0.2 });// 精細凱旋門結構(寬13.5、高15、深6.6,放大1.5倍)// 基座const baseGeometry = new THREE.BoxGeometry(13.5, 1.5, 6.6);const base = new THREE.Mesh(baseGeometry, archMaterial);base.position.y = 0.75;scene.add(base);// 四個支撐柱子(更寬大,模擬拱門)const pillarWidth = 2.2;const pillarHeight = 12;const pillarGeometry = new THREE.BoxGeometry(pillarWidth, pillarHeight, pillarWidth);const frontLeftPillar = new THREE.Mesh(pillarGeometry, archMaterial);frontLeftPillar.position.set(-5.65, 7.5, -2.2);scene.add(frontLeftPillar);const frontRightPillar = new THREE.Mesh(pillarGeometry, archMaterial);frontRightPillar.position.set(5.65, 7.5, -2.2);scene.add(frontRightPillar);const backLeftPillar = new THREE.Mesh(pillarGeometry, archMaterial);backLeftPillar.position.set(-5.65, 7.5, 2.2);scene.add(backLeftPillar);const backRightPillar = new THREE.Mesh(pillarGeometry, archMaterial);backRightPillar.position.set(5.65, 7.5, 2.2);scene.add(backRightPillar);// 中間橫梁(中央拱門頂部)const midBeamGeometry = new THREE.BoxGeometry(13.5, 1, 6.6);const midBeam = new THREE.Mesh(midBeamGeometry, archMaterial);midBeam.position.y = 10;scene.add(midBeam);// 頂部atticconst atticGeometry = new THREE.BoxGeometry(13.5, 1.5, 6.6);const attic = new THREE.Mesh(atticGeometry, archMaterial);attic.position.y = 13.5;scene.add(attic);// 頂部雕塑(簡化的Quadriga)const quadrigaGeometry = new THREE.BoxGeometry(2, 1, 2);const quadrigaMaterial = new THREE.MeshPhongMaterial({ color: 0xB8860B });const quadriga = new THREE.Mesh(quadrigaGeometry, quadrigaMaterial);quadriga.position.set(0, 14.5, 0);scene.add(quadriga);// Frieze(帶狀裝飾,增加細節)const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1);scene.add(frieze);const friezeBack = frieze.clone();friezeBack.position.z = -3.1;scene.add(friezeBack);// Frieze上的小裝飾(模擬勝利盾牌)const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2);scene.add(detail);const detailBack = detail.clone();detailBack.position.z = -3.2;scene.add(detailBack);}// 基座雕塑(四個雕塑組)const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({ color: 0xB8860B });const sculpture1 = new THREE.Mesh(sculptureGeometry, sculptureMaterial);sculpture1.position.set(-5.65, 3, -3.2);scene.add(sculpture1);const sculpture2 = sculpture1.clone();sculpture2.position.set(5.65, 3, -3.2);scene.add(sculpture2);const sculpture3 = sculpture1.clone();sculpture3.position.set(-5.65, 3, 3.2);scene.add(sculpture3);const sculpture4 = sculpture1.clone();sculpture4.position.set(5.65, 3, 3.2);scene.add(sculpture4);// 擴展建筑群(高度4-8單位)const buildingGeometry = new THREE.BoxGeometry(3, 10, 3);const windowMaterial = new THREE.MeshPhongMaterial({ color: 0xFFFFFF });const windowGeometry = new THREE.BoxGeometry(0.4, 0.4, 0.1);const buildingColors = [0xFF6347, 0x4682B4, 0x32CD32, 0xFFD700, 0x9932CC, 0xFF4500, 0x00CED1];const rings = 5;const baseRadius = 25; // 增大半徑以避免遮擋const ringSpacing = 10;for (let ring = 1; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;const height = 4 + Math.random() * 4; // 高度4-8單位const buildingMaterial = new THREE.MeshPhongMaterial({ color: buildingColors[Math.floor(Math.random() * buildingColors.length)]});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height / 2, z);buildingMesh.scale.y = height / 10;scene.add(buildingMesh);const numWindows = Math.floor(height / 2);for (let w = 0; w < numWindows; w++) {const windowMesh1 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh1.position.set(x + 1.2, (w * 1.5) + 1, z + 1.51);windowMesh1.scale.y = height / 10;scene.add(windowMesh1);const windowMesh2 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh2.position.set(x - 1.2, (w * 1.5) + 1, z + 1.51);windowMesh2.scale.y = height / 10;scene.add(windowMesh2);const windowMesh3 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh3.position.set(x + 1.51, (w * 1.5) + 1, z + 1.2);windowMesh3.scale.y = height / 10;scene.add(windowMesh3);const windowMesh4 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh4.position.set(x + 1.51, (w * 1.5) + 1, z - 1.2);windowMesh4.scale.y = height / 10;scene.add(windowMesh4);}}}// 設置相機位置(斜角俯視,適應更大凱旋門)camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 動畫循環function animate() {requestAnimationFrame(animate);scene.rotation.y += 0.002;renderer.render(scene, camera);}animate();// 處理窗口大小變化window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>
圖示效果
版本4.0:增加公路和車流
- 在原本凱旋門和建筑群的基礎上,加上 環形公路 + 放射狀公路 + 科技感燈光 + 內外環對向車流。
<!DOCTYPE html>
<html>
<head><title>3D超大精細凱旋門與多彩建筑群(斜角俯視)</title><style>body { margin: 0; }canvas { display: block; }</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<script>// 場景 & 相機 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0xffffff, 1);document.body.appendChild(renderer.domElement);// 燈光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 凱旋門材質const archMaterial = new THREE.MeshStandardMaterial({color: 0xD2B48C, roughness: 0.8, metalness: 0.2});// 凱旋門主體const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5, 2.2], [5.65, 7.5, 2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3, 3.2], [5.65, 3, 3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// 建筑群const buildingGeometry = new THREE.BoxGeometry(3, 10, 3);const windowMaterial = new THREE.MeshPhongMaterial({color: 0xFFFFFF});const windowGeometry = new THREE.BoxGeometry(0.4, 0.4, 0.1);const buildingColors = [0xFF6347,0x4682B4,0x32CD32,0xFFD700,0x9932CC,0xFF4500,0x00CED1];const rings = 5, baseRadius = 25, ringSpacing = 10;for (let ring = 1; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;const height = 4 + Math.random() * 4;const buildingMaterial = new THREE.MeshPhongMaterial({color: buildingColors[Math.floor(Math.random()*buildingColors.length)]});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height/2, z);buildingMesh.scale.y = height / 10;scene.add(buildingMesh);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 4;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);// 放射狀公路const radialRoadLength = 60;const radialRoadWidth = 3;for (let i = 0; i < 8; i++) {const angle = (i / 8) * Math.PI * 2;const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeo = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glow = new THREE.Mesh(glowGeo, glowMaterial);glow.position.copy(road.position);glow.rotation.y = road.rotation.y;glow.position.y = 0.06;scene.add(glow);}// ===== 動態車群(內外環對向行駛) =====const cars = [];const numCars = 30;const innerRadius = roadRadius - 1;const outerRadius = roadRadius + 1;for (let i = 0; i < numCars; i++) {const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const isOuter = i % 2 === 0; // 偶數外環,奇數內環car.userData = {angle: Math.random() * Math.PI * 2,speed: (0.002 + Math.random() * 0.002) * (isOuter ? 1 : -1),radius: isOuter ? outerRadius : innerRadius};scene.add(car);cars.push(car);}// 相機位置camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 動畫function animate() {requestAnimationFrame(animate);cars.forEach(car => {car.userData.angle += car.userData.speed;car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;});scene.rotation.y += 0.002;renderer.render(scene, camera);}animate();window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});
</script>
</body>
</html>
效果圖
版本5.0:去除壓在公路上的建筑
優化點
公路(環形和放射狀)完全沒有建筑壓住
所有道路都暢通可見
建筑分布更合理
<!DOCTYPE html>
<html>
<head><title>3D凱旋門與暢通道路的建筑群</title><style>body { margin: 0; }canvas { display: block; }</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<script>// 場景 & 相機 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0xffffff, 1);document.body.appendChild(renderer.domElement);// 燈光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 凱旋門材質const archMaterial = new THREE.MeshStandardMaterial({color: 0xD2B48C, roughness: 0.8, metalness: 0.2});// 凱旋門主體const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5, 2.2], [5.65, 7.5, 2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3, 3.2], [5.65, 3, 3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 建筑群(避開環形和放射狀道路) =====const buildingGeometry = new THREE.BoxGeometry(3, 10, 3);const buildingColors = [0xFF6347,0x4682B4,0x32CD32,0xFFD700,0x9932CC,0xFF4500,0x00CED1];const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = []; // 放射狀道路角度const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) { // 從第2圈開始const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;// 檢測是否在放射狀道路范圍內let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) { // 道路兩側 6 米范圍內不建樓onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 4 + Math.random() * 4;const buildingMaterial = new THREE.MeshPhongMaterial({color: buildingColors[Math.floor(Math.random()*buildingColors.length)]});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height/2, z);buildingMesh.scale.y = height / 10;scene.add(buildingMesh);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 4;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);// 放射狀公路const radialRoadLength = 60;const radialRoadWidth = 3;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeo = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glow = new THREE.Mesh(glowGeo, glowMaterial);glow.position.copy(road.position);glow.rotation.y = road.rotation.y;glow.position.y = 0.06;scene.add(glow);}// ===== 動態車群 =====const cars = [];const numCars = 30;const innerRadius = roadRadius - 1;const outerRadius = roadRadius + 1;for (let i = 0; i < numCars; i++) {const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const isOuter = i % 2 === 0;car.userData = {angle: Math.random() * Math.PI * 2,speed: (0.002 + Math.random() * 0.002) * (isOuter ? 1 : -1),radius: isOuter ? outerRadius : innerRadius};scene.add(car);cars.push(car);}// 相機camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 動畫function animate() {requestAnimationFrame(animate);cars.forEach(car => {car.userData.angle += car.userData.speed;car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;});scene.rotation.y += 0.002;renderer.render(scene, camera);}animate();window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});
</script>
</body>
</html>
效果圖
版本6.0:優化車流群
環形公路:雙向環形車流(內圈逆時針,外圈順時針)
放射狀公路:每條都有雙向車流,到端點自動掉頭
建筑:完全避開公路,不會擋路
統一的交通邏輯:汽車不再有兩個完全獨立的交通環路,而是具有可以是 或 的屬性。這個單一的動畫循環可以處理所有汽車,并允許動態路徑切換。
userData.path
'ring'
'radial'
無縫路徑轉換:
徑向到環形:當徑向道路上的汽車到達終點 () 或中心 () 時,它會自動切換到 。然后,它計算出一個新的角度和速度,以繼續沿環形道路行駛,從而創建平滑過渡。
car.userData.position > radialRoadLength
car.userData.position < 0
path
'ring'
環形到徑向:當環路上的汽車經過徑向道路交叉口時,它有很小的機會切換到徑向道路。這是通過防止所有汽車同時改變路徑并確保動態、逼真的流動進行控制的。
Math.random() < 0.01
改進的代碼結構:汽車創建循環更加集中。創建汽車并給出初始路徑和位置。然后,單個動畫循環管理所有汽車的狀態和運動,無論其當前路徑如何。
動態攝像機:場景現在圍繞原點旋轉 ()。這讓用戶更好地感受到一個充滿活力、不斷運動的充滿活力的城市。
scene.rotation.y += 0.002
<!DOCTYPE html>
<html>
<head><title>3D凱旋門與暢通道路的建筑群 - 優化版</title><style>body { margin: 0; overflow: hidden; }canvas { display: block; }</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<script>// 場景 & 相機 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0xffffff, 1);document.body.appendChild(renderer.domElement);// 燈光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 凱旋門材質const archMaterial = new THREE.MeshStandardMaterial({color: 0xD2B48C, roughness: 0.8, metalness: 0.2});// 凱旋門主體const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5,? 2.2], [5.65, 7.5,? 2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3,? 3.2], [5.65, 3,? 3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 建筑群 =====const buildingGeometry = new THREE.BoxGeometry(3, 10, 3);const buildingColors = [0xFF6347,0x4682B4,0x32CD32,0xFFD700,0x9932CC,0xFF4500,0x00CED1];const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = [];const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) {onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 4 + Math.random() * 4;const buildingMaterial = new THREE.MeshPhongMaterial({color: buildingColors[Math.floor(Math.random()*buildingColors.length)]});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height/2, z);buildingMesh.scale.y = height / 10;scene.add(buildingMesh);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 6;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);const radialRoadLength = 60;const radialRoadWidth = 5;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeoLeft = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowLeft = new THREE.Mesh(glowGeoLeft, glowMaterial);glowLeft.position.set(road.position.x + Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z - Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowLeft.rotation.y = road.rotation.y;scene.add(glowLeft);const glowGeoRight = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowRight = new THREE.Mesh(glowGeoRight, glowMaterial);glowRight.position.set(road.position.x - Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z + Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowRight.rotation.y = road.rotation.y;scene.add(glowRight);}// ===== 優化車流 =====const cars = [];const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const numCars = 100;const laneOffset = radialRoadWidth / 4;const innerRadius = roadRadius - roadWidth / 4;const outerRadius = roadRadius + roadWidth / 4;for (let i = 0; i < numCars; i++) {const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const initialPath = Math.random() < 0.5 ? 'ring' : 'radial';let initialAngle, initialDirection, initialRadius, initialPosition, initialLane;if (initialPath === 'ring') {initialAngle = Math.random() * Math.PI * 2;initialDirection = Math.random() < 0.5 ? 1 : -1;initialRadius = initialDirection === 1 ? outerRadius : innerRadius;car.userData = {path: 'ring',angle: initialAngle,speed: (0.0015 + Math.random() * 0.0025) * initialDirection,radius: initialRadius,lane: 0 // Not used for ring, but for consistency};} else {initialAngle = radialAngles[Math.floor(Math.random() * numRadials)];initialDirection = Math.random() < 0.5 ? 1 : -1;initialPosition = Math.random() * radialRoadLength;initialLane = initialDirection === 1 ? laneOffset : -laneOffset;car.userData = {path: 'radial',angle: initialAngle,position: initialPosition,speed: (0.05 + Math.random() * 0.03) * initialDirection,lane: initialLane};}scene.add(car);cars.push(car);}// 相機camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 動畫function animate() {requestAnimationFrame(animate);cars.forEach(car => {if (car.userData.path === 'ring') {car.userData.angle += car.userData.speed;// 檢查是否接近放射狀道路的交叉口const currentAngle = car.userData.angle % (Math.PI * 2);for (const radialAngle of radialAngles) {if (Math.abs(currentAngle - radialAngle) < 0.05 && Math.random() < 0.01) { // 隨機決定是否轉向const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'radial';car.userData.angle = radialAngle;car.userData.position = newDirection > 0 ? 0 : radialRoadLength;car.userData.speed = (0.05 + Math.random() * 0.03) * newDirection;car.userData.lane = car.userData.speed > 0 ? laneOffset : -laneOffset;break;}}car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;} else if (car.userData.path === 'radial') {car.userData.position += car.userData.speed;// 檢查是否到達道路盡頭或中心if ((car.userData.speed > 0 && car.userData.position > radialRoadLength) || (car.userData.speed < 0 && car.userData.position < 0)) {// 切換到環形道路const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'ring';car.userData.angle = car.userData.angle + (Math.PI / 2); // 從徑向到環形,角度需要偏移car.userData.speed = (0.0015 + Math.random() * 0.0025) * newDirection;car.userData.radius = newDirection > 0 ? outerRadius : innerRadius;}const baseX = Math.cos(car.userData.angle) * (car.userData.position + roadRadius);const baseZ = Math.sin(car.userData.angle) * (car.userData.position + roadRadius);const offsetX = Math.sin(car.userData.angle) * car.userData.lane;const offsetZ = -Math.cos(car.userData.angle) * car.userData.lane;car.position.set(baseX + offsetX, 0.4, baseZ + offsetZ);car.rotation.y = -car.userData.angle + (car.userData.speed > 0 ? 0 : Math.PI);}});scene.rotation.y += 0.002;renderer.render(scene, camera);}animate();window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});
</script>
</body>
</html>
效果圖
版本7.0:添加煙花效果
<!DOCTYPE html>
<html>
<head><title>3D凱旋門與暢通道路的建筑群 - 天藍色背景版</title><style>body { margin: 0; overflow: hidden; font-family: sans-serif; }canvas { display: block; }#controls {position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);padding: 10px 20px;font-size: 16px;cursor: pointer;border: none;background-color: rgba(0, 0, 0, 0.2);color: white;border-radius: 5px;}</style>
</head>
<body><button id="controls">啟動階梯式煙花</button><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 場景 & 相機 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0x87CEEB, 1); // 天藍色背景document.body.appendChild(renderer.domElement);// 燈光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 地面const groundGeometry = new THREE.PlaneGeometry(2000, 2000);const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 }); // 接近公路的顏色const ground = new THREE.Mesh(groundGeometry, groundMaterial);ground.rotation.x = -Math.PI / 2;scene.add(ground);// 凱旋門材質const archMaterial = new THREE.MeshStandardMaterial({color: 0xD2B48C, roughness: 0.8, metalness: 0.2}); // 黃褐色凱旋門// 凱旋門主體const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5, 2.2], [5.65, 7.5, 2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3, 3.2], [5.65, 3, 3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 建筑群 =====const buildingGeometry = new THREE.BoxGeometry(3, 10, 3);const buildingColors = [0xFF6347,0x4682B4,0x32CD32,0xFFD700,0x9932CC,0xFF4500,0x00CED1];const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = [];const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) {onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 4 + Math.random() * 4;const buildingMaterial = new THREE.MeshPhongMaterial({color: buildingColors[Math.floor(Math.random()*buildingColors.length)]});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height/2, z);buildingMesh.scale.y = height / 10;scene.add(buildingMesh);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 6;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);// 移除閃爍效果,直接添加不閃爍的發光環const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);const radialRoadLength = 60;const radialRoadWidth = 5;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeoLeft = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowLeft = new THREE.Mesh(glowGeoLeft, glowMaterial);glowLeft.position.set(road.position.x + Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z - Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowLeft.rotation.y = road.rotation.y;scene.add(glowLeft);const glowGeoRight = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowRight = new THREE.Mesh(glowGeoRight, glowMaterial);glowRight.position.set(road.position.x - Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z + Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowRight.rotation.y = road.rotation.y;scene.add(glowRight);}// ===== 優化車流 =====const cars = [];const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const numCars = 100;const laneOffset = radialRoadWidth / 4;const innerRadius = roadRadius - roadWidth / 4;const outerRadius = roadRadius + roadWidth / 4;for (let i = 0; i < numCars; i++) {const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const initialPath = Math.random() < 0.5 ? 'ring' : 'radial';let initialAngle, initialDirection, initialRadius, initialPosition, initialLane;if (initialPath === 'ring') {initialAngle = Math.random() * Math.PI * 2;initialDirection = Math.random() < 0.5 ? 1 : -1;initialRadius = initialDirection === 1 ? outerRadius : innerRadius;car.userData = {path: 'ring',angle: initialAngle,speed: (0.0015 + Math.random() * 0.0025) * initialDirection,radius: initialRadius,lane: 0};} else {initialAngle = radialAngles[Math.floor(Math.random() * numRadials)];initialDirection = Math.random() < 0.5 ? 1 : -1;initialPosition = Math.random() * radialRoadLength;initialLane = initialDirection === 1 ? laneOffset : -laneOffset;car.userData = {path: 'radial',angle: initialAngle,position: initialPosition,speed: (0.05 + Math.random() * 0.03) * initialDirection,lane: initialLane};}scene.add(car);cars.push(car);}// ===== 煙花代碼開始 =====const fireworks = [];const particleGeometry = new THREE.SphereGeometry(0.08, 8, 8);const innerCircleRadius = roadRadius - roadWidth / 2;// 煙花爆炸函數function createExplosion(originPosition, color) {const numParticles = 60 + Math.floor(Math.random() * 30);const explosionRadius = 0.5 + Math.random();for (let i = 0; i < numParticles; i++) {const particleMaterial = new THREE.MeshBasicMaterial({ color: color });const particle = new THREE.Mesh(particleGeometry, particleMaterial);particle.position.copy(originPosition);const velocity = new THREE.Vector3((Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius);particle.userData = {velocity: velocity,life: 1.5 + Math.random() * 1,state: 'exploded'};fireworks.push(particle);scene.add(particle);}}// 發射煙花(升空)function launchFirework(startPosition) {const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(startPosition);// 爆炸高度比凱旋門低一些,約12-18const targetHeight = 12 + Math.random() * 6;const color = new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex();rocket.userData = {velocity: new THREE.Vector3(0, 0.35 + Math.random() * 0.25, 0),state: 'rising',targetY: targetHeight,color: color};fireworks.push(rocket);scene.add(rocket);}// 場景初始化時的所有煙花一起升空function launchInitialFireworks() {for (let i = 0; i < 15; i++) {const angle = (i / 15) * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}}// 間歇性發射單個煙花function launchIntermittentFirework() {const angle = Math.random() * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}// 階梯式環形發射煙花function launchTieredFireworks() {const numTiers = 6;const delayPerTier = 400; // 毫秒const baseHeight = 12;const heightStep = 3; // 階梯高度for (let i = 0; i < numTiers; i++) {setTimeout(() => {const angle = (i / numTiers) * Math.PI * 2;const radius = innerCircleRadius * 0.8;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(position);rocket.userData = {velocity: new THREE.Vector3(0, 0.35, 0),state: 'rising',targetY: baseHeight + i * heightStep,color: new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex()};fireworks.push(rocket);scene.add(rocket);}, i * delayPerTier);}}function updateFireworks() {for (let i = fireworks.length - 1; i >= 0; i--) {const p = fireworks [i];if (p.userData.state === 'rising') {p.position.add(p.userData.velocity);if (p.position.y >= p.userData.targetY) {createExplosion(p.position, p.userData.color);scene.remove(p);fireworks.splice(i, 1);}} else if (p.userData.state === 'exploded') {p.userData.life -= 0.015;p.position.add(p.userData.velocity);p.material.opacity = Math.max(0, p.userData.life / 2);p.material.transparent = true;p.userData.velocity.y -= 0.002;if (p.userData.life <= 0) {scene.remove(p);fireworks.splice(i, 1);}}}}// ===== 煙花代碼結束 =====// 相機camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 動畫function animate() {requestAnimationFrame(animate);cars.forEach(car => {if (car.userData.path === 'ring') {car.userData.angle += car.userData.speed;const currentAngle = car.userData.angle % (Math.PI * 2);for (const radialAngle of radialAngles) {if (Math.abs(currentAngle - radialAngle) < 0.05 && Math.random() < 0.01) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'radial';car.userData.angle = radialAngle;car.userData.position = newDirection > 0 ? 0 : radialRoadLength;car.userData.speed = (0.05 + Math.random() * 0.03) * newDirection;car.userData.lane = car.userData.speed > 0 ? laneOffset : -laneOffset;break;}}car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;} else if (car.userData.path === 'radial') {car.userData.position += car.userData.speed;if ((car.userData.speed > 0 && car.userData.position > radialRoadLength) || (car.userData.speed < 0 && car.userData.position < 0)) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'ring';car.userData.angle = car.userData.angle + (Math.PI / 2);car.userData.speed = (0.0015 + Math.random() * 0.0025) * newDirection;car.userData.radius = newDirection > 0 ? outerRadius : innerRadius;}const baseX = Math.cos(car.userData.angle) * (car.userData.position + roadRadius);const baseZ = Math.sin(car.userData.angle) * (car.userData.position + roadRadius);const offsetX = Math.sin(car.userData.angle) * car.userData.lane;const offsetZ = -Math.cos(car.userData.angle) * car.userData.lane;car.position.set(baseX + offsetX, 0.4, baseZ + offsetZ);car.rotation.y = -car.userData.angle + (car.userData.speed > 0 ? 0 : Math.PI);}});// 間歇性發射新煙花if (Math.random() < 0.005) {launchIntermittentFirework();}// 更新煙花狀態updateFireworks();scene.rotation.y += 0.002;renderer.render(scene, camera);}// 初始煙花發射launchInitialFireworks();animate();// 按鈕點擊事件document.getElementById('controls').addEventListener('click', launchTieredFireworks);window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>
顯示效果
版本8.0:添加樹木
背景顏色: 天藍色
地面顏色: 略淺于公路的深灰色
凱旋門顏色: 黃褐色
樹木: 在環形道路內側和放射狀公路兩側都增加了樹木
放射狀樹木: 去除了最靠近環形公路的那一圈樹木
<!DOCTYPE html>
<html>
<head><title>3D凱旋門與暢通道路的建筑群 - 最終版</title><style>body { margin: 0; overflow: hidden; font-family: sans-serif; }canvas { display: block; }#controls {position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);padding: 10px 20px;font-size: 16px;cursor: pointer;border: none;background-color: rgba(0, 0, 0, 0.2);color: white;border-radius: 5px;}</style>
</head>
<body><button id="controls">啟動階梯式煙花</button><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 場景 & 相機 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0x87CEEB, 1); // 天藍色背景document.body.appendChild(renderer.domElement);// 燈光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 地面const groundGeometry = new THREE.PlaneGeometry(2000, 2000);const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 }); // 接近公路的顏色const ground = new THREE.Mesh(groundGeometry, groundMaterial);ground.rotation.x = -Math.PI / 2;scene.add(ground);// 凱旋門材質const archMaterial = new THREE.MeshStandardMaterial({color: 0xD2B48C, roughness: 0.8, metalness: 0.2}); // 黃褐色凱旋門// 凱旋門主體const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5, 2.2], [5.65, 7.5, 2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3, 3.2], [5.65, 3, 3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 建筑群 =====const buildingGeometry = new THREE.BoxGeometry(3, 10, 3);const buildingColors = [0xFF6347,0x4682B4,0x32CD32,0xFFD700,0x9932CC,0xFF4500,0x00CED1];const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = [];const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) {onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 4 + Math.random() * 4;const buildingMaterial = new THREE.MeshPhongMaterial({color: buildingColors[Math.floor(Math.random()*buildingColors.length)]});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height/2, z);buildingMesh.scale.y = height / 10;scene.add(buildingMesh);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 6;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);// 移除閃爍效果,直接添加不閃爍的發光環const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);const radialRoadLength = 60;const radialRoadWidth = 5;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeoLeft = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowLeft = new THREE.Mesh(glowGeoLeft, glowMaterial);glowLeft.position.set(road.position.x + Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z - Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowLeft.rotation.y = road.rotation.y;scene.add(glowLeft);const glowGeoRight = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowRight = new THREE.Mesh(glowGeoRight, glowMaterial);glowRight.position.set(road.position.x - Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z + Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowRight.rotation.y = road.rotation.y;scene.add(glowRight);}// ===== 樹木 =====const treeTrunkGeometry = new THREE.CylinderGeometry(0.3, 0.3, 2, 8);const treeLeavesGeometry = new THREE.ConeGeometry(1.5, 3, 8);const treeTrunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });const treeLeavesMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });function createTree(x, z) {const trunk = new THREE.Mesh(treeTrunkGeometry, treeTrunkMaterial);trunk.position.set(x, 1, z);scene.add(trunk);const leaves = new THREE.Mesh(treeLeavesGeometry, treeLeavesMaterial);leaves.position.set(x, 3.5, z);scene.add(leaves);}// 在凱旋門和道路之間生成樹林const innerForestRadius = 15;const outerForestRadius = roadRadius - roadWidth / 2 - 2;const numForestTrees = 150;for (let i = 0; i < numForestTrees; i++) {const angle = Math.random() * Math.PI * 2;const radius = innerForestRadius + Math.random() * (outerForestRadius - innerForestRadius);const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;createTree(x, z);}// 沿徑向公路兩側生成樹木,跳過最內側一圈const treeSpacing = 6;const radialOffset = radialRoadWidth / 2 + 1;for (let angle of radialAngles) {for (let i = 1; i < radialRoadLength / treeSpacing; i++) { // i從1開始,跳過最里面一圈const dist = i * treeSpacing;// 左側樹木const xLeft = Math.cos(angle) * (dist + roadRadius) + Math.sin(angle) * radialOffset;const zLeft = Math.sin(angle) * (dist + roadRadius) - Math.cos(angle) * radialOffset;createTree(xLeft, zLeft);// 右側樹木const xRight = Math.cos(angle) * (dist + roadRadius) - Math.sin(angle) * radialOffset;const zRight = Math.sin(angle) * (dist + roadRadius) + Math.cos(angle) * radialOffset;createTree(xRight, zRight);}}// ===== 優化車流 =====const cars = [];const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const numCars = 100;const laneOffset = radialRoadWidth / 4;const innerRadius = roadRadius - roadWidth / 4;const outerRadius = roadRadius + roadWidth / 4;for (let i = 0; i < numCars; i++) {const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const initialPath = Math.random() < 0.5 ? 'ring' : 'radial';let initialAngle, initialDirection, initialRadius, initialPosition, initialLane;if (initialPath === 'ring') {initialAngle = Math.random() * Math.PI * 2;initialDirection = Math.random() < 0.5 ? 1 : -1;initialRadius = initialDirection === 1 ? outerRadius : innerRadius;car.userData = {path: 'ring',angle: initialAngle,speed: (0.0015 + Math.random() * 0.0025) * initialDirection,radius: initialRadius,lane: 0};} else {initialAngle = radialAngles[Math.floor(Math.random() * numRadials)];initialDirection = Math.random() < 0.5 ? 1 : -1;initialPosition = Math.random() * radialRoadLength;initialLane = initialDirection === 1 ? laneOffset : -laneOffset;car.userData = {path: 'radial',angle: initialAngle,position: initialPosition,speed: (0.05 + Math.random() * 0.03) * initialDirection,lane: initialLane};}scene.add(car);cars.push(car);}// ===== 煙花代碼開始 =====const fireworks = [];const particleGeometry = new THREE.SphereGeometry(0.08, 8, 8);const innerCircleRadius = roadRadius - roadWidth / 2;// 煙花爆炸函數function createExplosion(originPosition, color) {const numParticles = 60 + Math.floor(Math.random() * 30);const explosionRadius = 0.5 + Math.random();for (let i = 0; i < numParticles; i++) {const particleMaterial = new THREE.MeshBasicMaterial({ color: color });const particle = new THREE.Mesh(particleGeometry, particleMaterial);particle.position.copy(originPosition);const velocity = new THREE.Vector3((Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius);particle.userData = {velocity: velocity,life: 1.5 + Math.random() * 1,state: 'exploded'};fireworks.push(particle);scene.add(particle);}}// 發射煙花(升空)function launchFirework(startPosition) {const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(startPosition);// 爆炸高度比凱旋門低一些,約12-18const targetHeight = 12 + Math.random() * 6;const color = new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex();rocket.userData = {velocity: new THREE.Vector3(0, 0.35 + Math.random() * 0.25, 0),state: 'rising',targetY: targetHeight,color: color};fireworks.push(rocket);scene.add(rocket);}// 場景初始化時的所有煙花一起升空function launchInitialFireworks() {for (let i = 0; i < 15; i++) {const angle = (i / 15) * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}}// 間歇性發射單個煙花function launchIntermittentFirework() {const angle = Math.random() * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}// 階梯式環形發射煙花function launchTieredFireworks() {const numTiers = 6;const delayPerTier = 400; // 毫秒const baseHeight = 12;const heightStep = 3; // 階梯高度for (let i = 0; i < numTiers; i++) {setTimeout(() => {const angle = (i / numTiers) * Math.PI * 2;const radius = innerCircleRadius * 0.8;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(position);rocket.userData = {velocity: new THREE.Vector3(0, 0.35, 0),state: 'rising',targetY: baseHeight + i * heightStep,color: new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex()};fireworks.push(rocket);scene.add(rocket);}, i * delayPerTier);}}function updateFireworks() {for (let i = fireworks.length - 1; i >= 0; i--) {const p = fireworks [i];if (p.userData.state === 'rising') {p.position.add(p.userData.velocity);if (p.position.y >= p.userData.targetY) {createExplosion(p.position, p.userData.color);scene.remove(p);fireworks.splice(i, 1);}} else if (p.userData.state === 'exploded') {p.userData.life -= 0.015;p.position.add(p.userData.velocity);p.material.opacity = Math.max(0, p.userData.life / 2);p.material.transparent = true;p.userData.velocity.y -= 0.002;if (p.userData.life <= 0) {scene.remove(p);fireworks.splice(i, 1);}}}}// ===== 煙花代碼結束 =====// 相機camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 動畫function animate() {requestAnimationFrame(animate);cars.forEach(car => {if (car.userData.path === 'ring') {car.userData.angle += car.userData.speed;const currentAngle = car.userData.angle % (Math.PI * 2);for (const radialAngle of radialAngles) {if (Math.abs(currentAngle - radialAngle) < 0.05 && Math.random() < 0.01) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'radial';car.userData.angle = radialAngle;car.userData.position = newDirection > 0 ? 0 : radialRoadLength;car.userData.speed = (0.05 + Math.random() * 0.03) * newDirection;car.userData.lane = car.userData.speed > 0 ? laneOffset : -laneOffset;break;}}car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;} else if (car.userData.path === 'radial') {car.userData.position += car.userData.speed;if ((car.userData.speed > 0 && car.userData.position > radialRoadLength) || (car.userData.speed < 0 && car.userData.position < 0)) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'ring';car.userData.angle = car.userData.angle + (Math.PI / 2);car.userData.speed = (0.0015 + Math.random() * 0.0025) * newDirection;car.userData.radius = newDirection > 0 ? outerRadius : innerRadius;}const baseX = Math.cos(car.userData.angle) * (car.userData.position + roadRadius);const baseZ = Math.sin(car.userData.angle) * (car.userData.position + roadRadius);const offsetX = Math.sin(car.userData.angle) * car.userData.lane;const offsetZ = -Math.cos(car.userData.angle) * car.userData.lane;car.position.set(baseX + offsetX, 0.4, baseZ + offsetZ);car.rotation.y = -car.userData.angle + (car.userData.speed > 0 ? 0 : Math.PI);}});// 間歇性發射新煙花if (Math.random() < 0.005) {launchIntermittentFirework();}// 更新煙花狀態updateFireworks();scene.rotation.y += 0.002;renderer.render(scene, camera);}// 初始煙花發射launchInitialFireworks();animate();// 按鈕點擊事件document.getElementById('controls').addEventListener('click', launchTieredFireworks);window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>
顯示效果
版本9.0:美化建筑群
<!DOCTYPE html>
<html>
<head><title>3D凱旋門與暢通道路的建筑群 - 細節版</title><style>body { margin: 0; overflow: hidden; font-family: sans-serif; }canvas { display: block; }#controls {position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);padding: 10px 20px;font-size: 16px;cursor: pointer;border: none;background-color: rgba(0, 0, 0, 0.2);color: white;border-radius: 5px;}</style>
</head>
<body><button id="controls">啟動階梯式煙花</button><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 場景 & 相機 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0x87CEEB, 1); // 天藍色背景document.body.appendChild(renderer.domElement);// 燈光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 地面const groundGeometry = new THREE.PlaneGeometry(2000, 2000);const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 }); // 接近公路的顏色const ground = new THREE.Mesh(groundGeometry, groundMaterial);ground.rotation.x = -Math.PI / 2;scene.add(ground);// 凱旋門材質const archMaterial = new THREE.MeshStandardMaterial({color: 0xFFFFFF, roughness: 0.8, metalness: 0.2}); // 白色凱旋門// 凱旋門主體const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5, 2.2], [5.65, 7.5, 2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3, 3.2], [5.65, 3, 3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 建筑群 =====const frenchRoofMaterial = new THREE.MeshPhongMaterial({color: 0x708090, shininess: 10}); // Slate gray for mansard roofsconst frenchBodyMaterial = new THREE.MeshPhongMaterial({color: 0xE0CDA9, roughness: 0.6}); // Beige stoneconst frenchDetailMaterial = new THREE.MeshPhongMaterial({color: 0xD4AF37, metalness: 0.5}); // Gold accentsconst frenchWindowMaterial = new THREE.MeshBasicMaterial({color: 0xADD8E6}); // Light blue glassfunction createFrenchBuilding(x, z, baseHeight) {const building = new THREE.Group();// Bottom base (rectangular foundation with stone texture effect)const baseGeo = new THREE.BoxGeometry(5, 0.8, 5);const base = new THREE.Mesh(baseGeo, frenchBodyMaterial);base.position.y = 0.4;building.add(base);// Main body (multi-story with classical symmetry)const bodyHeight = baseHeight * 0.7;const bodyGeo = new THREE.BoxGeometry(4.5, bodyHeight, 4.5);const body = new THREE.Mesh(bodyGeo, frenchBodyMaterial);body.position.y = 0.8 + bodyHeight / 2;building.add(body);// Columns (Corinthian-style, simplified with capitals)const columnHeight = bodyHeight;const columnGeo = new THREE.CylinderGeometry(0.2, 0.2, columnHeight, 12);const capitalGeo = new THREE.BoxGeometry(0.4, 0.3, 0.4);const positions = [[-2, -2], [2, -2],[-2, 2], [2, 2]];positions.forEach(pos => {const column = new THREE.Mesh(columnGeo, frenchDetailMaterial);column.position.set(pos[0], 0.8 + columnHeight / 2, pos[1]);building.add(column);const capital = new THREE.Mesh(capitalGeo, frenchDetailMaterial);capital.position.set(pos[0], 0.8 + columnHeight + 0.15, pos[1]);building.add(capital);});// Windows (detailed arched windows on each face, multi per story)const windowGeo = new THREE.BoxGeometry(0.8, 1.5, 0.1);const archGeo = new THREE.TorusGeometry(0.4, 0.1, 8, 12, Math.PI);const frameGeo = new THREE.BoxGeometry(0.9, 1.6, 0.12);const numStories = 3; // Fine detail with multiple storiesfor (let story = 0; story < numStories; story++) {const storyY = 0.8 + (bodyHeight / numStories) * (story + 0.5);for (let side = 0; side < 4; side++) {const angle = side * Math.PI / 2;const windowMesh = new THREE.Mesh(windowGeo, frenchWindowMaterial);windowMesh.position.set(Math.cos(angle) * 2.3, storyY, Math.sin(angle) * 2.3);windowMesh.rotation.y = -angle;building.add(windowMesh);// Arched topconst arch = new THREE.Mesh(archGeo, frenchDetailMaterial);arch.position.set(0, 0.8, 0);arch.rotation.x = Math.PI / 2;windowMesh.add(arch);// Frameconst frame = new THREE.Mesh(frameGeo, frenchDetailMaterial);frame.position.set(0, 0, -0.01);windowMesh.add(frame);// Pane divisions (vertical and horizontal for fine detail)const paneGeo = new THREE.BoxGeometry(0.05, 1.5, 0.05);for (let p = -0.3; p <= 0.3; p += 0.3) {const vPane = new THREE.Mesh(paneGeo, frenchDetailMaterial);vPane.position.set(p, 0, 0.05);windowMesh.add(vPane);}for (let q = -0.6; q <= 0.6; q += 0.6) {const hPane = new THREE.Mesh(paneGeo, frenchDetailMaterial);hPane.rotation.z = Math.PI / 2;hPane.position.set(0, q, 0.05);windowMesh.add(hPane);}}}// Mansard roof (sloped with dormers for French style)const roofHeight = baseHeight * 0.3;const roofGeo = new THREE.BoxGeometry(4.5, roofHeight, 4.5);const roof = new THREE.Mesh(roofGeo, frenchRoofMaterial);roof.position.y = 0.8 + bodyHeight + roofHeight / 2;building.add(roof);// Dormer windows on roof (fine detail)const dormerGeo = new THREE.BoxGeometry(1, 1, 0.5);const dormerRoofGeo = new THREE.ConeGeometry(0.6, 0.8, 4);for (let side = 0; side < 4; side++) {const angle = side * Math.PI / 2;const dormer = new THREE.Mesh(dormerGeo, frenchBodyMaterial);dormer.position.set(Math.cos(angle) * 1.8, 0.8 + bodyHeight + roofHeight / 2, Math.sin(angle) * 1.8);dormer.rotation.y = -angle;building.add(dormer);const dormerRoof = new THREE.Mesh(dormerRoofGeo, frenchRoofMaterial);dormerRoof.position.set(0, 0.9, 0);dormer.add(dormerRoof);const dormerWindow = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.8, 0.1), frenchWindowMaterial);dormerWindow.position.set(0, 0, 0.26);dormer.add(dormerWindow);}// Ornate cornices and friezes (fine decorative bands)const corniceGeo = new THREE.BoxGeometry(5, 0.3, 5);const cornice = new THREE.Mesh(corniceGeo, frenchDetailMaterial);cornice.position.y = 0.8 + bodyHeight;building.add(cornice);building.position.set(x, 0, z);scene.add(building);}const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = [];const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) {onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 6 + Math.random() * 8;createFrenchBuilding(x, z, height);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 6;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);// 移除閃爍效果,直接添加不閃爍的發光環const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);const radialRoadLength = 60;const radialRoadWidth = 5;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeoLeft = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowLeft = new THREE.Mesh(glowGeoLeft, glowMaterial);glowLeft.position.set(road.position.x + Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z - Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowLeft.rotation.y = road.rotation.y;scene.add(glowLeft);const glowGeoRight = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowRight = new THREE.Mesh(glowGeoRight, glowMaterial);glowRight.position.set(road.position.x - Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z + Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowRight.rotation.y = road.rotation.y;scene.add(glowRight);}// ===== 樹木 =====const treeTrunkGeometry = new THREE.CylinderGeometry(0.3, 0.3, 2, 8);const treeLeavesGeometry = new THREE.ConeGeometry(1.5, 3, 8);const treeTrunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });const treeLeavesMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });function createTree(x, z) {const trunk = new THREE.Mesh(treeTrunkGeometry, treeTrunkMaterial);trunk.position.set(x, 1, z);scene.add(trunk);const leaves = new THREE.Mesh(treeLeavesGeometry, treeLeavesMaterial);leaves.position.set(x, 3.5, z);scene.add(leaves);}// 在凱旋門和道路之間生成樹林const innerForestRadius = 15;const outerForestRadius = roadRadius - roadWidth / 2 - 2;const numForestTrees = 150;for (let i = 0; i < numForestTrees; i++) {const angle = Math.random() * Math.PI * 2;const radius = innerForestRadius + Math.random() * (outerForestRadius - innerForestRadius);const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;createTree(x, z);}// 徑向公路兩側生成樹木,跳過最內側一圈const treeSpacing = 6;const radialOffset = radialRoadWidth / 2 + 1;for (let angle of radialAngles) {for (let i = 1; i < radialRoadLength / treeSpacing; i++) { const dist = i * treeSpacing;// 左側樹木const xLeft = Math.cos(angle) * (dist + roadRadius) + Math.sin(angle) * radialOffset;const zLeft = Math.sin(angle) * (dist + roadRadius) - Math.cos(angle) * radialOffset;createTree(xLeft, zLeft);// 右側樹木const xRight = Math.cos(angle) * (dist + roadRadius) - Math.sin(angle) * radialOffset;const zRight = Math.sin(angle) * (dist + roadRadius) + Math.cos(angle) * radialOffset;createTree(xRight, zRight);}}// ===== 優化車流 =====const cars = [];const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const numCars = 100;const laneOffset = radialRoadWidth / 4;const innerRadius = roadRadius - roadWidth / 4;const outerRadius = roadRadius + roadWidth / 4;for (let i = 0; i < numCars; i++) {const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const initialPath = Math.random() < 0.5 ? 'ring' : 'radial';let initialAngle, initialDirection, initialRadius, initialPosition, initialLane;if (initialPath === 'ring') {initialAngle = Math.random() * Math.PI * 2;initialDirection = Math.random() < 0.5 ? 1 : -1;initialRadius = initialDirection === 1 ? outerRadius : innerRadius;car.userData = {path: 'ring',angle: initialAngle,speed: (0.0015 + Math.random() * 0.0025) * initialDirection,radius: initialRadius,lane: 0};} else {initialAngle = radialAngles[Math.floor(Math.random() * numRadials)];initialDirection = Math.random() < 0.5 ? 1 : -1;initialPosition = Math.random() * radialRoadLength;initialLane = initialDirection === 1 ? laneOffset : -laneOffset;car.userData = {path: 'radial',angle: initialAngle,position: initialPosition,speed: (0.05 + Math.random() * 0.03) * initialDirection,lane: initialLane};}scene.add(car);cars.push(car);}// ===== 煙花代碼開始 =====const fireworks = [];const particleGeometry = new THREE.SphereGeometry(0.08, 8, 8);const innerCircleRadius = roadRadius - roadWidth / 2;// 煙花爆炸函數function createExplosion(originPosition, color) {const numParticles = 60 + Math.floor(Math.random() * 30);const explosionRadius = 0.5 + Math.random();for (let i = 0; i < numParticles; i++) {const particleMaterial = new THREE.MeshBasicMaterial({ color: color });const particle = new THREE.Mesh(particleGeometry, particleMaterial);particle.position.copy(originPosition);const velocity = new THREE.Vector3((Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius);particle.userData = {velocity: velocity,life: 1.5 + Math.random() * 1,state: 'exploded'};fireworks.push(particle);scene.add(particle);}}// 發射煙花(升空)function launchFirework(startPosition) {const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(startPosition);// 爆炸高度比凱旋門低一些,約12-18const targetHeight = 12 + Math.random() * 6;const color = new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex();rocket.userData = {velocity: new THREE.Vector3(0, 0.35 + Math.random() * 0.25, 0),state: 'rising',targetY: targetHeight,color: color};fireworks.push(rocket);scene.add(rocket);}// 場景初始化時的所有煙花一起升空function launchInitialFireworks() {for (let i = 0; i < 15; i++) {const angle = (i / 15) * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}}// 間歇性發射單個煙花function launchIntermittentFirework() {const angle = Math.random() * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}// 階梯式環形發射煙花function launchTieredFireworks() {const numTiers = 6;const delayPerTier = 400; // 毫秒const baseHeight = 12;const heightStep = 3; // 階梯高度for (let i = 0; i < numTiers; i++) {setTimeout(() => {const angle = (i / numTiers) * Math.PI * 2;const radius = innerCircleRadius * 0.8;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(position);rocket.userData = {velocity: new THREE.Vector3(0, 0.35, 0),state: 'rising',targetY: baseHeight + i * heightStep,color: new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex()};fireworks.push(rocket);scene.add(rocket);}, i * delayPerTier);}}function updateFireworks() {for (let i = fireworks.length - 1; i >= 0; i--) {const p = fireworks [i];if (p.userData.state === 'rising') {p.position.add(p.userData.velocity);if (p.position.y >= p.userData.targetY) {createExplosion(p.position, p.userData.color);scene.remove(p);fireworks.splice(i, 1);}} else if (p.userData.state === 'exploded') {p.userData.life -= 0.015;p.position.add(p.userData.velocity);p.material.opacity = Math.max(0, p.userData.life / 2);p.material.transparent = true;p.userData.velocity.y -= 0.002;if (p.userData.life <= 0) {scene.remove(p);fireworks.splice(i, 1);}}}}// ===== 煙花代碼結束 =====// 相機camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 動畫function animate() {requestAnimationFrame(animate);cars.forEach(car => {if (car.userData.path === 'ring') {car.userData.angle += car.userData.speed;const currentAngle = car.userData.angle % (Math.PI * 2);for (const radialAngle of radialAngles) {if (Math.abs(currentAngle - radialAngle) < 0.05 && Math.random() < 0.01) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'radial';car.userData.angle = radialAngle;car.userData.position = newDirection > 0 ? 0 : radialRoadLength;car.userData.speed = (0.05 + Math.random() * 0.03) * newDirection;car.userData.lane = car.userData.speed > 0 ? laneOffset : -laneOffset;break;}}car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;} else if (car.userData.path === 'radial') {car.userData.position += car.userData.speed;if ((car.userData.speed > 0 && car.userData.position > radialRoadLength) || (car.userData.speed < 0 && car.userData.position < 0)) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'ring';car.userData.angle = car.userData.angle + (Math.PI / 2);car.userData.speed = (0.0015 + Math.random() * 0.0025) * newDirection;car.userData.radius = newDirection > 0 ? outerRadius : innerRadius;}const baseX = Math.cos(car.userData.angle) * (car.userData.position + roadRadius);const baseZ = Math.sin(car.userData.angle) * (car.userData.position + roadRadius);const offsetX = Math.sin(car.userData.angle) * car.userData.lane;const offsetZ = -Math.cos(car.userData.angle) * car.userData.lane;car.position.set(baseX + offsetX, 0.4, baseZ + offsetZ);car.rotation.y = -car.userData.angle + (car.userData.speed > 0 ? 0 : Math.PI);}});// 間歇性發射新煙花if (Math.random() < 0.005) {launchIntermittentFirework();}// 更新煙花狀態updateFireworks();scene.rotation.y += 0.002;renderer.render(scene, camera);}// 初始煙花發射launchInitialFireworks();animate();// 按鈕點擊事件document.getElementById('controls').addEventListener('click', launchTieredFireworks);window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>
效果顯示
版本10.0:添加云朵
<!DOCTYPE html>
<html>
<head><title>3D凱旋門與暢通道路的建筑群 - 細節版</title><style>body { margin: 0; overflow: hidden; font-family: sans-serif; }canvas { display: block; }#controls {position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);padding: 10px 20px;font-size: 16px;cursor: pointer;border: none;background-color: rgba(0, 0, 0, 0.2);color: white;border-radius: 5px;}</style>
</head>
<body><button id="controls">啟動階梯式煙花</button><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 場景 & 相機 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0x87CEEB, 1); // 天藍色背景document.body.appendChild(renderer.domElement);// 燈光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 地面const groundGeometry = new THREE.PlaneGeometry(2000, 2000);const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 }); // 公路灰色const ground = new THREE.Mesh(groundGeometry, groundMaterial);ground.rotation.x = -Math.PI / 2;scene.add(ground);// 凱旋門材質const archMaterial = new THREE.MeshStandardMaterial({color: 0xFFFFFF, roughness: 0.8, metalness: 0.2}); // 白色石材// 凱旋門主體const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5, 2.2], [5.65, 7.5, 2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B}); // 金色雕塑const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3, 3.2], [5.65, 3, 3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 法式建筑群 =====const frenchRoofMaterial = new THREE.MeshPhongMaterial({color: 0x708090, shininess: 10}); // 曼薩德屋頂灰色const frenchBodyMaterial = new THREE.MeshPhongMaterial({color: 0xE0CDA9, roughness: 0.6}); // 米色石材const frenchDetailMaterial = new THREE.MeshPhongMaterial({color: 0xD4AF37, metalness: 0.5}); // 金色裝飾const frenchWindowMaterial = new THREE.MeshBasicMaterial({color: 0xADD8E6}); // 淺藍色玻璃function createFrenchBuilding(x, z, baseHeight) {const building = new THREE.Group();// 底部基座(矩形石材基礎)const baseGeo = new THREE.BoxGeometry(5, 0.8, 5);const base = new THREE.Mesh(baseGeo, frenchBodyMaterial);base.position.y = 0.4;building.add(base);// 主體(多層對稱結構)const bodyHeight = baseHeight * 0.7;const bodyGeo = new THREE.BoxGeometry(4.5, bodyHeight, 4.5);const body = new THREE.Mesh(bodyGeo, frenchBodyMaterial);body.position.y = 0.8 + bodyHeight / 2;building.add(body);// 柱子(簡化科林斯柱式,帶柱頭)const columnHeight = bodyHeight;const columnGeo = new THREE.CylinderGeometry(0.2, 0.2, columnHeight, 12);const capitalGeo = new THREE.BoxGeometry(0.4, 0.3, 0.4);const positions = [[-2, -2], [2, -2],[-2, 2], [2, 2]];positions.forEach(pos => {const column = new THREE.Mesh(columnGeo, frenchDetailMaterial);column.position.set(pos[0], 0.8 + columnHeight / 2, pos[1]);building.add(column);const capital = new THREE.Mesh(capitalGeo, frenchDetailMaterial);capital.position.set(pos[0], 0.8 + columnHeight + 0.15, pos[1]);building.add(capital);});// 窗戶(每層多個拱形窗,細節豐富)const windowGeo = new THREE.BoxGeometry(0.8, 1.5, 0.1);const archGeo = new THREE.TorusGeometry(0.4, 0.1, 8, 12, Math.PI);const frameGeo = new THREE.BoxGeometry(0.9, 1.6, 0.12);const numStories = 3;for (let story = 0; story < numStories; story++) {const storyY = 0.8 + (bodyHeight / numStories) * (story + 0.5);for (let side = 0; side < 4; side++) {const angle = side * Math.PI / 2;const windowMesh = new THREE.Mesh(windowGeo, frenchWindowMaterial);windowMesh.position.set(Math.cos(angle) * 2.3, storyY, Math.sin(angle) * 2.3);windowMesh.rotation.y = -angle;building.add(windowMesh);// 拱形頂部const arch = new THREE.Mesh(archGeo, frenchDetailMaterial);arch.position.set(0, 0.8, 0);arch.rotation.x = Math.PI / 2;windowMesh.add(arch);// 窗框const frame = new THREE.Mesh(frameGeo, frenchDetailMaterial);frame.position.set(0, 0, -0.01);windowMesh.add(frame);// 窗格(垂直和水平分隔)const paneGeo = new THREE.BoxGeometry(0.05, 1.5, 0.05);for (let p = -0.3; p <= 0.3; p += 0.3) {const vPane = new THREE.Mesh(paneGeo, frenchDetailMaterial);vPane.position.set(p, 0, 0.05);windowMesh.add(vPane);}for (let q = -0.6; q <= 0.6; q += 0.6) {const hPane = new THREE.Mesh(paneGeo, frenchDetailMaterial);hPane.rotation.z = Math.PI / 2;hPane.position.set(0, q, 0.05);windowMesh.add(hPane);}}}// 曼薩德屋頂(帶天窗)const roofHeight = baseHeight * 0.3;const roofGeo = new THREE.BoxGeometry(4.5, roofHeight, 4.5);const roof = new THREE.Mesh(roofGeo, frenchRoofMaterial);roof.position.y = 0.8 + bodyHeight + roofHeight / 2;building.add(roof);// 屋頂天窗const dormerGeo = new THREE.BoxGeometry(1, 1, 0.5);const dormerRoofGeo = new THREE.ConeGeometry(0.6, 0.8, 4);for (let side = 0; side < 4; side++) {const angle = side * Math.PI / 2;const dormer = new THREE.Mesh(dormerGeo, frenchBodyMaterial);dormer.position.set(Math.cos(angle) * 1.8, 0.8 + bodyHeight + roofHeight / 2, Math.sin(angle) * 1.8);dormer.rotation.y = -angle;building.add(dormer);const dormerRoof = new THREE.Mesh(dormerRoofGeo, frenchRoofMaterial);dormerRoof.position.set(0, 0.9, 0);dormer.add(dormerRoof);const dormerWindow = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.8, 0.1), frenchWindowMaterial);dormerWindow.position.set(0, 0, 0.26);dormer.add(dormerWindow);}// 裝飾線腳和檐口const corniceGeo = new THREE.BoxGeometry(5, 0.3, 5);const cornice = new THREE.Mesh(corniceGeo, frenchDetailMaterial);cornice.position.y = 0.8 + bodyHeight;building.add(cornice);building.position.set(x, 0, z);scene.add(building);}const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = [];const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) {onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 6 + Math.random() * 8;createFrenchBuilding(x, z, height);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 6;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);const radialRoadLength = 60;const radialRoadWidth = 5;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeoLeft = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowLeft = new THREE.Mesh(glowGeoLeft, glowMaterial);glowLeft.position.set(road.position.x + Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z - Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowLeft.rotation.y = road.rotation.y;scene.add(glowLeft);const glowGeoRight = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowRight = new THREE.Mesh(glowGeoRight, glowMaterial);glowRight.position.set(road.position.x - Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z + Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowRight.rotation.y = road.rotation.y;scene.add(glowRight);}// ===== 樹木 =====const treeTrunkGeometry = new THREE.CylinderGeometry(0.3, 0.3, 2, 8);const treeLeavesGeometry = new THREE.ConeGeometry(1.5, 3, 8);const treeTrunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });const treeLeavesMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });function createTree(x, z) {const trunk = new THREE.Mesh(treeTrunkGeometry, treeTrunkMaterial);trunk.position.set(x, 1, z);scene.add(trunk);const leaves = new THREE.Mesh(treeLeavesGeometry, treeLeavesMaterial);leaves.position.set(x, 3.5, z);scene.add(leaves);}const innerForestRadius = 15;const outerForestRadius = roadRadius - roadWidth / 2 - 2;const numForestTrees = 150;for (let i = 0; i < numForestTrees; i++) {const angle = Math.random() * Math.PI * 2;const radius = innerForestRadius + Math.random() * (outerForestRadius - innerForestRadius);const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;createTree(x, z);}const treeSpacing = 6;const radialOffset = radialRoadWidth / 2 + 1;for (let angle of radialAngles) {for (let i = 1; i < radialRoadLength / treeSpacing; i++) {const dist = i * treeSpacing;const xLeft = Math.cos(angle) * (dist + roadRadius) + Math.sin(angle) * radialOffset;const zLeft = Math.sin(angle) * (dist + roadRadius) - Math.cos(angle) * radialOffset;createTree(xLeft, zLeft);const xRight = Math.cos(angle) * (dist + roadRadius) - Math.sin(angle) * radialOffset;const zRight = Math.sin(angle) * (dist + roadRadius) + Math.cos(angle) * radialOffset;createTree(xRight, zRight);}}// ===== 云朵 =====const cloudMaterial = new THREE.MeshStandardMaterial({ color: 0xF5F5F5, transparent: true, opacity: 0.7, roughness: 0.8, metalness: 0.1 }); // 淺灰色半透明云朵,帶光影const clouds = [];const numClouds = 30; // 增加云朵數量function createCloud(x, z, y) {const cloud = new THREE.Group();const cloudGeo = new THREE.SphereGeometry(2, 8, 8); // 低多邊形球體const segments = 5 + Math.floor(Math.random() * 4); // 每朵云 5-8 個球體for (let i = 0; i < segments; i++) {const segment = new THREE.Mesh(cloudGeo, cloudMaterial);segment.position.set((Math.random() - 0.5) * 5, // 更寬的偏移(Math.random() - 0.5) * 3, // 更寬的垂直偏移(Math.random() - 0.5) * 5);segment.scale.set(0.7 + Math.random() * 0.8, // 更寬的縮放范圍0.5 + Math.random() * 0.5,0.7 + Math.random() * 0.8);cloud.add(segment);}cloud.position.set(x, y, z);cloud.userData = {velocity: new THREE.Vector3((Math.random() - 0.5) * 0.02, 0, (Math.random() - 0.5) * 0.02), // 漂移速度rotationSpeed: (Math.random() - 0.5) * 0.001 // 隨機旋轉速度};clouds.push(cloud);scene.add(cloud);}// 在寬廣區域生成云朵const cloudRadius = 100;for (let i = 0; i < numClouds; i++) {const angle = Math.random() * Math.PI * 2;const radius = 20 + Math.random() * cloudRadius;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;const y = 20 + Math.random() * 30; // 高度 20-50createCloud(x, z, y);}// ===== 優化車流 =====const cars = [];const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const numCars = 100;const laneOffset = radialRoadWidth / 4;const innerRadius = roadRadius - roadWidth / 4;const outerRadius = roadRadius + roadWidth / 4;for (let i = 0; i < numCars; i++) {const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const initialPath = Math.random() < 0.5 ? 'ring' : 'radial';let initialAngle, initialDirection, initialRadius, initialPosition, initialLane;if (initialPath === 'ring') {initialAngle = Math.random() * Math.PI * 2;initialDirection = Math.random() < 0.5 ? 1 : -1;initialRadius = initialDirection === 1 ? outerRadius : innerRadius;car.userData = {path: 'ring',angle: initialAngle,speed: (0.0015 + Math.random() * 0.0025) * initialDirection,radius: initialRadius,lane: 0};} else {initialAngle = radialAngles[Math.floor(Math.random() * numRadials)];initialDirection = Math.random() < 0.5 ? 1 : -1;initialPosition = Math.random() * radialRoadLength;initialLane = initialDirection === 1 ? laneOffset : -laneOffset;car.userData = {path: 'radial',angle: initialAngle,position: initialPosition,speed: (0.05 + Math.random() * 0.03) * initialDirection,lane: initialLane};}scene.add(car);cars.push(car);}// ===== 煙花代碼 =====const fireworks = [];const particleGeometry = new THREE.SphereGeometry(0.08, 8, 8);const innerCircleRadius = roadRadius - roadWidth / 2;function createExplosion(originPosition, color) {const numParticles = 60 + Math.floor(Math.random() * 30);const explosionRadius = 0.5 + Math.random();for (let i = 0; i < numParticles; i++) {const particleMaterial = new THREE.MeshBasicMaterial({ color: color });const particle = new THREE.Mesh(particleGeometry, particleMaterial);particle.position.copy(originPosition);const velocity = new THREE.Vector3((Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius);particle.userData = {velocity: velocity,life: 1.5 + Math.random() * 1,state: 'exploded'};fireworks.push(particle);scene.add(particle);}}function launchFirework(startPosition) {const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(startPosition);const targetHeight = 12 + Math.random() * 6;const color = new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex();rocket.userData = {velocity: new THREE.Vector3(0, 0.35 + Math.random() * 0.25, 0),state: 'rising',targetY: targetHeight,color: color};fireworks.push(rocket);scene.add(rocket);}function launchInitialFireworks() {for (let i = 0; i < 15; i++) {const angle = (i / 15) * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}}function launchIntermittentFirework() {const angle = Math.random() * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}function launchTieredFireworks() {const numTiers = 6;const delayPerTier = 400;const baseHeight = 12;const heightStep = 3;for (let i = 0; i < numTiers; i++) {setTimeout(() => {const angle = (i / numTiers) * Math.PI * 2;const radius = innerCircleRadius * 0.8;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(position);rocket.userData = {velocity: new THREE.Vector3(0, 0.35, 0),state: 'rising',targetY: baseHeight + i * heightStep,color: new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex()};fireworks.push(rocket);scene.add(rocket);}, i * delayPerTier);}}function updateFireworks() {for (let i = fireworks.length - 1; i >= 0; i--) {const p = fireworks[i];if (p.userData.state === 'rising') {p.position.add(p.userData.velocity);if (p.position.y >= p.userData.targetY) {createExplosion(p.position, p.userData.color);scene.remove(p);fireworks.splice(i, 1);}} else if (p.userData.state === 'exploded') {p.userData.life -= 0.015;p.position.add(p.userData.velocity);p.material.opacity = Math.max(0, p.userData.life / 2);p.material.transparent = true;p.userData.velocity.y -= 0.002;if (p.userData.life <= 0) {scene.remove(p);fireworks.splice(i, 1);}}}}// 相機camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 動畫function animate() {requestAnimationFrame(animate);// 更新云朵位置和旋轉clouds.forEach(cloud => {cloud.position.add(cloud.userData.velocity);cloud.rotation.y += cloud.userData.rotationSpeed; // 微小旋轉// 云朵超出邊界時回繞if (cloud.position.x > cloudRadius) cloud.position.x -= 2 * cloudRadius;if (cloud.position.x < -cloudRadius) cloud.position.x += 2 * cloudRadius;if (cloud.position.z > cloudRadius) cloud.position.z -= 2 * cloudRadius;if (cloud.position.z < -cloudRadius) cloud.position.z += 2 * cloudRadius;});// 更新車輛cars.forEach(car => {if (car.userData.path === 'ring') {car.userData.angle += car.userData.speed;const currentAngle = car.userData.angle % (Math.PI * 2);for (const radialAngle of radialAngles) {if (Math.abs(currentAngle - radialAngle) < 0.05 && Math.random() < 0.01) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'radial';car.userData.angle = radialAngle;car.userData.position = newDirection > 0 ? 0 : radialRoadLength;car.userData.speed = (0.05 + Math.random() * 0.03) * newDirection;car.userData.lane = car.userData.speed > 0 ? laneOffset : -laneOffset;break;}}car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;} else if (car.userData.path === 'radial') {car.userData.position += car.userData.speed;if ((car.userData.speed > 0 && car.userData.position > radialRoadLength) || (car.userData.speed < 0 && car.userData.position < 0)) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'ring';car.userData.angle = car.userData.angle + (Math.PI / 2);car.userData.speed = (0.0015 + Math.random() * 0.0025) * newDirection;car.userData.radius = newDirection > 0 ? outerRadius : innerRadius;}const baseX = Math.cos(car.userData.angle) * (car.userData.position + roadRadius);const baseZ = Math.sin(car.userData.angle) * (car.userData.position + roadRadius);const offsetX = Math.sin(car.userData.angle) * car.userData.lane;const offsetZ = -Math.cos(car.userData.angle) * car.userData.lane;car.position.set(baseX + offsetX, 0.4, baseZ + offsetZ);car.rotation.y = -car.userData.angle + (car.userData.speed > 0 ? 0 : Math.PI);}});// 間歇性發射煙花if (Math.random() < 0.005) {launchIntermittentFirework();}// 更新煙花updateFireworks();// 場景緩慢旋轉scene.rotation.y += 0.002;renderer.render(scene, camera);}// 初始煙花launchInitialFireworks();animate();// 按鈕事件document.getElementById('controls').addEventListener('click', launchTieredFireworks);// 窗口大小調整window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>
顯示效果
版本11.0:添加動態熱氣球
<!DOCTYPE html>
<html>
<head><title>3D凱旋門與暢通道路的建筑群 - 細節版</title><style>body { margin: 0; overflow: hidden; font-family: sans-serif; }canvas { display: block; }#controls {position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);padding: 10px 20px;font-size: 16px;cursor: pointer;border: none;background-color: rgba(0, 0, 0, 0.2);color: white;border-radius: 5px;}</style>
</head>
<body><button id="controls">啟動階梯式煙花</button><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 場景 & 相機 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0x87CEEB, 1); // 天藍色背景document.body.appendChild(renderer.domElement);// 燈光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 地面const groundGeometry = new THREE.PlaneGeometry(2000, 2000);const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 }); // 公路灰色const ground = new THREE.Mesh(groundGeometry, groundMaterial);ground.rotation.x = -Math.PI / 2;scene.add(ground);// 凱旋門材質const archMaterial = new THREE.MeshStandardMaterial({color: 0xFFFFFF, roughness: 0.8, metalness: 0.2}); // 白色石材// 凱旋門主體const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5, 2.2], [5.65, 7.5, 2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B}); // 金色雕塑const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3, 3.2], [5.65, 3, 3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 法式建筑群 =====const frenchRoofMaterial = new THREE.MeshPhongMaterial({color: 0x708090, shininess: 10}); // 曼薩德屋頂灰色const frenchBodyMaterial = new THREE.MeshPhongMaterial({color: 0xE0CDA9, roughness: 0.6}); // 米色石材const frenchDetailMaterial = new THREE.MeshPhongMaterial({color: 0xD4AF37, metalness: 0.5}); // 金色裝飾const frenchWindowMaterial = new THREE.MeshBasicMaterial({color: 0xADD8E6}); // 淺藍色玻璃function createFrenchBuilding(x, z, baseHeight) {const building = new THREE.Group();// 底部基座(矩形石材基礎)const baseGeo = new THREE.BoxGeometry(5, 0.8, 5);const base = new THREE.Mesh(baseGeo, frenchBodyMaterial);base.position.y = 0.4;building.add(base);// 主體(多層對稱結構)const bodyHeight = baseHeight * 0.7;const bodyGeo = new THREE.BoxGeometry(4.5, bodyHeight, 4.5);const body = new THREE.Mesh(bodyGeo, frenchBodyMaterial);body.position.y = 0.8 + bodyHeight / 2;building.add(body);// 柱子(簡化科林斯柱式,帶柱頭)const columnHeight = bodyHeight;const columnGeo = new THREE.CylinderGeometry(0.2, 0.2, columnHeight, 12);const capitalGeo = new THREE.BoxGeometry(0.4, 0.3, 0.4);const positions = [[-2, -2], [2, -2],[-2, 2], [2, 2]];positions.forEach(pos => {const column = new THREE.Mesh(columnGeo, frenchDetailMaterial);column.position.set(pos[0], 0.8 + columnHeight / 2, pos[1]);building.add(column);const capital = new THREE.Mesh(capitalGeo, frenchDetailMaterial);capital.position.set(pos[0], 0.8 + columnHeight + 0.15, pos[1]);building.add(capital);});// 窗戶(每層多個拱形窗,細節豐富)const windowGeo = new THREE.BoxGeometry(0.8, 1.5, 0.1);const archGeo = new THREE.TorusGeometry(0.4, 0.1, 8, 12, Math.PI);const frameGeo = new THREE.BoxGeometry(0.9, 1.6, 0.12);const numStories = 3;for (let story = 0; story < numStories; story++) {const storyY = 0.8 + (bodyHeight / numStories) * (story + 0.5);for (let side = 0; side < 4; side++) {const angle = side * Math.PI / 2;const windowMesh = new THREE.Mesh(windowGeo, frenchWindowMaterial);windowMesh.position.set(Math.cos(angle) * 2.3, storyY, Math.sin(angle) * 2.3);windowMesh.rotation.y = -angle;building.add(windowMesh);// 拱形頂部const arch = new THREE.Mesh(archGeo, frenchDetailMaterial);arch.position.set(0, 0.8, 0);arch.rotation.x = Math.PI / 2;windowMesh.add(arch);// 窗框const frame = new THREE.Mesh(frameGeo, frenchDetailMaterial);frame.position.set(0, 0, -0.01);windowMesh.add(frame);// 窗格(垂直和水平分隔)const paneGeo = new THREE.BoxGeometry(0.05, 1.5, 0.05);for (let p = -0.3; p <= 0.3; p += 0.3) {const vPane = new THREE.Mesh(paneGeo, frenchDetailMaterial);vPane.position.set(p, 0, 0.05);windowMesh.add(vPane);}for (let q = -0.6; q <= 0.6; q += 0.6) {const hPane = new THREE.Mesh(paneGeo, frenchDetailMaterial);hPane.rotation.z = Math.PI / 2;hPane.position.set(0, q, 0.05);windowMesh.add(hPane);}}}// 曼薩德屋頂(帶天窗)const roofHeight = baseHeight * 0.3;const roofGeo = new THREE.BoxGeometry(4.5, roofHeight, 4.5);const roof = new THREE.Mesh(roofGeo, frenchRoofMaterial);roof.position.y = 0.8 + bodyHeight + roofHeight / 2;building.add(roof);// 屋頂天窗const dormerGeo = new THREE.BoxGeometry(1, 1, 0.5);const dormerRoofGeo = new THREE.ConeGeometry(0.6, 0.8, 4);for (let side = 0; side < 4; side++) {const angle = side * Math.PI / 2;const dormer = new THREE.Mesh(dormerGeo, frenchBodyMaterial);dormer.position.set(Math.cos(angle) * 1.8, 0.8 + bodyHeight + roofHeight / 2, Math.sin(angle) * 1.8);dormer.rotation.y = -angle;building.add(dormer);const dormerRoof = new THREE.Mesh(dormerRoofGeo, frenchRoofMaterial);dormerRoof.position.set(0, 0.9, 0);dormer.add(dormerRoof);const dormerWindow = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.8, 0.1), frenchWindowMaterial);dormerWindow.position.set(0, 0, 0.26);dormer.add(dormerWindow);}// 裝飾線腳和檐口const corniceGeo = new THREE.BoxGeometry(5, 0.3, 5);const cornice = new THREE.Mesh(corniceGeo, frenchDetailMaterial);cornice.position.y = 0.8 + bodyHeight;building.add(cornice);building.position.set(x, 0, z);scene.add(building);}const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = [];const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) {onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 6 + Math.random() * 8;createFrenchBuilding(x, z, height);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 6;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);const radialRoadLength = 60;const radialRoadWidth = 5;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeoLeft = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowLeft = new THREE.Mesh(glowGeoLeft, glowMaterial);glowLeft.position.set(road.position.x + Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z - Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowLeft.rotation.y = road.rotation.y;scene.add(glowLeft);const glowGeoRight = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowRight = new THREE.Mesh(glowGeoRight, glowMaterial);glowRight.position.set(road.position.x - Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z + Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowRight.rotation.y = road.rotation.y;scene.add(glowRight);}// ===== 樹木 =====const treeTrunkGeometry = new THREE.CylinderGeometry(0.3, 0.3, 2, 8);const treeLeavesGeometry = new THREE.ConeGeometry(1.5, 3, 8);const treeTrunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });const treeLeavesMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });function createTree(x, z) {const trunk = new THREE.Mesh(treeTrunkGeometry, treeTrunkMaterial);trunk.position.set(x, 1, z);scene.add(trunk);const leaves = new THREE.Mesh(treeLeavesGeometry, treeLeavesMaterial);leaves.position.set(x, 3.5, z);scene.add(leaves);}const innerForestRadius = 15;const outerForestRadius = roadRadius - roadWidth / 2 - 2;const numForestTrees = 150;for (let i = 0; i < numForestTrees; i++) {const angle = Math.random() * Math.PI * 2;const radius = innerForestRadius + Math.random() * (outerForestRadius - innerForestRadius);const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;createTree(x, z);}const treeSpacing = 6;const radialOffset = radialRoadWidth / 2 + 1;for (let angle of radialAngles) {for (let i = 1; i < radialRoadLength / treeSpacing; i++) {const dist = i * treeSpacing;const xLeft = Math.cos(angle) * (dist + roadRadius) + Math.sin(angle) * radialOffset;const zLeft = Math.sin(angle) * (dist + roadRadius) - Math.cos(angle) * radialOffset;createTree(xLeft, zLeft);const xRight = Math.cos(angle) * (dist + roadRadius) - Math.sin(angle) * radialOffset;const zRight = Math.sin(angle) * (dist + roadRadius) + Math.cos(angle) * radialOffset;createTree(xRight, zRight);}}// ===== 云朵 =====const cloudMaterial = new THREE.MeshStandardMaterial({ color: 0xF5F5F5, transparent: true, opacity: 0.7, roughness: 0.8, metalness: 0.1 }); // 淺灰色半透明云朵,帶光影const clouds = [];const numClouds = 30; // 增加云朵數量function createCloud(x, z, y) {const cloud = new THREE.Group();const cloudGeo = new THREE.SphereGeometry(2, 8, 8); // 低多邊形球體const segments = 5 + Math.floor(Math.random() * 4); // 每朵云 5-8 個球體for (let i = 0; i < segments; i++) {const segment = new THREE.Mesh(cloudGeo, cloudMaterial);segment.position.set((Math.random() - 0.5) * 5, // 更寬的偏移(Math.random() - 0.5) * 3, // 更寬的垂直偏移(Math.random() - 0.5) * 5);segment.scale.set(0.7 + Math.random() * 0.8, // 更寬的縮放范圍0.5 + Math.random() * 0.5,0.7 + Math.random() * 0.8);cloud.add(segment);}cloud.position.set(x, y, z);cloud.userData = {velocity: new THREE.Vector3((Math.random() - 0.5) * 0.02, 0, (Math.random() - 0.5) * 0.02), // 漂移速度rotationSpeed: (Math.random() - 0.5) * 0.001 // 隨機旋轉速度};clouds.push(cloud);scene.add(cloud);}// 在寬廣區域生成云朵const cloudRadius = 100;for (let i = 0; i < numClouds; i++) {const angle = Math.random() * Math.PI * 2;const radius = 20 + Math.random() * cloudRadius;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;const y = 20 + Math.random() * 30; // 高度 20-50createCloud(x, z, y);}// ===== 熱氣球 =====const balloonMaterial = new THREE.MeshStandardMaterial({ roughness: 0.4, metalness: 0.2 });const basketMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513, roughness: 0.6 });const ropeMaterial = new THREE.LineBasicMaterial({ color: 0x333333, linewidth: 2 });const graffitiMaterial = new THREE.MeshBasicMaterial({ color: 0xFFFFFF, transparent: true, opacity: 0.8 });const hotAirBalloons = [];const numBalloons = 8; // 增加到8個熱氣球function createStarShape() {const shape = new THREE.Shape();const outerRadius = 0.8;const innerRadius = 0.4;for (let i = 0; i < 10; i++) {const angle = (i / 10) * Math.PI * 2;const radius = i % 2 === 0 ? outerRadius : innerRadius;const x = Math.cos(angle) * radius;const y = Math.sin(angle) * radius;if (i === 0) shape.moveTo(x, y);else shape.lineTo(x, y);}shape.closePath();return shape;}function createHotAirBalloon() {const balloon = new THREE.Group();// 氣球主體(球體,精細立體)const balloonGeo = new THREE.SphereGeometry(3, 32, 32);const balloonMesh = new THREE.Mesh(balloonGeo, balloonMaterial.clone());const hue = Math.random() < 0.5 ? Math.random() * 60 / 360 : (180 + Math.random() * 120) / 360; // 橙黃或藍紫balloonMesh.material.color.setHSL(hue, 0.5 + Math.random() * 0.2, 0.6 + Math.random() * 0.2);balloonMesh.position.y = 4;balloon.add(balloonMesh);// 刻度紋理(垂直和水平線)for (let i = 0; i < 12; i++) { // 12條經線const linePoints = [new THREE.Vector3(0, -3, 0),new THREE.Vector3(0, 3, 0)];const lineGeo = new THREE.BufferGeometry().setFromPoints(linePoints);const line = new THREE.Line(lineGeo, ropeMaterial);line.rotation.y = i * (Math.PI / 6);balloonMesh.add(line);}for (let y = -2; y <= 2; y += 1) { // 4條緯線if (y === 0) continue;const radius = Math.sqrt(9 - y * y);const circlePoints = [];for (let i = 0; i <= 32; i++) {const angle = (i / 32) * Math.PI * 2;circlePoints.push(new THREE.Vector3(Math.cos(angle) * radius, y, Math.sin(angle) * radius));}const circleGeo = new THREE.BufferGeometry().setFromPoints(circlePoints);const circle = new THREE.Line(circleGeo, ropeMaterial);balloonMesh.add(circle);}// 涂鴉(圓形或星形)const numGraffiti = 3 + Math.floor(Math.random() * 3);for (let i = 0; i < numGraffiti; i++) {const isCircle = Math.random() < 0.5;const graffitiGeo = isCircle ? new THREE.CircleGeometry(0.5 + Math.random() * 0.5, 8) : new THREE.ShapeGeometry(createStarShape());const graffiti = new THREE.Mesh(graffitiGeo, graffitiMaterial.clone());if (!isCircle) graffiti.material.color.setHex(0xCCCCCC);const theta = Math.random() * Math.PI;const phi = Math.random() * Math.PI * 2;const r = 3.01; // 稍超出球體表面graffiti.position.set(r * Math.sin(theta) * Math.cos(phi),r * Math.cos(theta),r * Math.sin(theta) * Math.sin(phi));graffiti.lookAt(balloonMesh.position);balloonMesh.add(graffiti);}// 籃子const basketGeo = new THREE.BoxGeometry(1.5, 1, 1.5);const basket = new THREE.Mesh(basketGeo, basketMaterial);basket.position.y = -1;balloon.add(basket);// 連接繩const ropeGeo = new THREE.CylinderGeometry(0.05, 0.05, 5, 8);for (let i = 0; i < 4; i++) {const rope = new THREE.Mesh(ropeGeo, basketMaterial);const angle = i * (Math.PI / 2);rope.position.set(Math.cos(angle) * 0.6, -0.5, Math.sin(angle) * 0.6);balloon.add(rope);}// 隨機初始角度和高度balloon.userData = {angle: Math.random() * Math.PI * 2,speed: 0.001 + Math.random() * 0.001,trajectoryRadius: 50 + Math.random() * 20,height: 20 + Math.random() * 30};hotAirBalloons.push(balloon);scene.add(balloon);}// 生成熱氣球for (let i = 0; i < numBalloons; i++) {createHotAirBalloon();}// ===== 優化車流 =====const cars = [];const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const numCars = 100;const laneOffset = radialRoadWidth / 4;const innerRadius = roadRadius - roadWidth / 4;const outerRadius = roadRadius + roadWidth / 4;for (let i = 0; i < numCars; i++) {const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const initialPath = Math.random() < 0.5 ? 'ring' : 'radial';let initialAngle, initialDirection, initialRadius, initialPosition, initialLane;if (initialPath === 'ring') {initialAngle = Math.random() * Math.PI * 2;initialDirection = Math.random() < 0.5 ? 1 : -1;initialRadius = initialDirection === 1 ? outerRadius : innerRadius;car.userData = {path: 'ring',angle: initialAngle,speed: (0.0015 + Math.random() * 0.0025) * initialDirection,radius: initialRadius,lane: 0};} else {initialAngle = radialAngles[Math.floor(Math.random() * numRadials)];initialDirection = Math.random() < 0.5 ? 1 : -1;initialPosition = Math.random() * radialRoadLength;initialLane = initialDirection === 1 ? laneOffset : -laneOffset;car.userData = {path: 'radial',angle: initialAngle,position: initialPosition,speed: (0.05 + Math.random() * 0.03) * initialDirection,lane: initialLane};}scene.add(car);cars.push(car);}// ===== 煙花代碼 =====const fireworks = [];const particleGeometry = new THREE.SphereGeometry(0.08, 8, 8);const innerCircleRadius = roadRadius - roadWidth / 2;function createExplosion(originPosition, color) {const numParticles = 60 + Math.floor(Math.random() * 30);const explosionRadius = 0.5 + Math.random();for (let i = 0; i < numParticles; i++) {const particleMaterial = new THREE.MeshBasicMaterial({ color: color });const particle = new THREE.Mesh(particleGeometry, particleMaterial);particle.position.copy(originPosition);const velocity = new THREE.Vector3((Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius);particle.userData = {velocity: velocity,life: 1.5 + Math.random() * 1,state: 'exploded'};fireworks.push(particle);scene.add(particle);}}function launchFirework(startPosition) {const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(startPosition);const targetHeight = 12 + Math.random() * 6;const color = new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex();rocket.userData = {velocity: new THREE.Vector3(0, 0.35 + Math.random() * 0.25, 0),state: 'rising',targetY: targetHeight,color: color};fireworks.push(rocket);scene.add(rocket);}function launchInitialFireworks() {for (let i = 0; i < 15; i++) {const angle = (i / 15) * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}}function launchIntermittentFirework() {const angle = Math.random() * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}function launchTieredFireworks() {const numTiers = 6;const delayPerTier = 400;const baseHeight = 12;const heightStep = 3;for (let i = 0; i < numTiers; i++) {setTimeout(() => {const angle = (i / numTiers) * Math.PI * 2;const radius = innerCircleRadius * 0.8;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(position);rocket.userData = {velocity: new THREE.Vector3(0, 0.35, 0),state: 'rising',targetY: baseHeight + i * heightStep,color: new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex()};fireworks.push(rocket);scene.add(rocket);}, i * delayPerTier);}}function updateFireworks() {for (let i = fireworks.length - 1; i >= 0; i--) {const p = fireworks[i];if (p.userData.state === 'rising') {p.position.add(p.userData.velocity);if (p.position.y >= p.userData.targetY) {createExplosion(p.position, p.userData.color);scene.remove(p);fireworks.splice(i, 1);}} else if (p.userData.state === 'exploded') {p.userData.life -= 0.015;p.position.add(p.userData.velocity);p.material.opacity = Math.max(0, p.userData.life / 2);p.material.transparent = true;p.userData.velocity.y -= 0.002;if (p.userData.life <= 0) {scene.remove(p);fireworks.splice(i, 1);}}}}// 相機camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 動畫function animate() {requestAnimationFrame(animate);// 更新云朵位置和旋轉clouds.forEach(cloud => {cloud.position.add(cloud.userData.velocity);cloud.rotation.y += cloud.userData.rotationSpeed; // 微小旋轉// 云朵超出邊界時回繞if (cloud.position.x > cloudRadius) cloud.position.x -= 2 * cloudRadius;if (cloud.position.x < -cloudRadius) cloud.position.x += 2 * cloudRadius;if (cloud.position.z > cloudRadius) cloud.position.z -= 2 * cloudRadius;if (cloud.position.z < -cloudRadius) cloud.position.z += 2 * cloudRadius;});// 更新熱氣球位置hotAirBalloons.forEach(balloon => {balloon.userData.angle += balloon.userData.speed;const trajRadius = balloon.userData.trajectoryRadius;balloon.position.x = Math.cos(balloon.userData.angle) * trajRadius;balloon.position.z = Math.sin(balloon.userData.angle) * trajRadius;balloon.position.y = balloon.userData.height + Math.sin(balloon.userData.angle * 5) * 1; // 輕微上下浮動});// 更新車輛cars.forEach(car => {if (car.userData.path === 'ring') {car.userData.angle += car.userData.speed;const currentAngle = car.userData.angle % (Math.PI * 2);for (const radialAngle of radialAngles) {if (Math.abs(currentAngle - radialAngle) < 0.05 && Math.random() < 0.01) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'radial';car.userData.angle = radialAngle;car.userData.position = newDirection > 0 ? 0 : radialRoadLength;car.userData.speed = (0.05 + Math.random() * 0.03) * newDirection;car.userData.lane = car.userData.speed > 0 ? laneOffset : -laneOffset;break;}}car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;} else if (car.userData.path === 'radial') {car.userData.position += car.userData.speed;if ((car.userData.speed > 0 && car.userData.position > radialRoadLength) || (car.userData.speed < 0 && car.userData.position < 0)) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'ring';car.userData.angle = car.userData.angle + (Math.PI / 2);car.userData.speed = (0.0015 + Math.random() * 0.0025) * newDirection;car.userData.radius = newDirection > 0 ? outerRadius : innerRadius;}const baseX = Math.cos(car.userData.angle) * (car.userData.position + roadRadius);const baseZ = Math.sin(car.userData.angle) * (car.userData.position + roadRadius);const offsetX = Math.sin(car.userData.angle) * car.userData.lane;const offsetZ = -Math.cos(car.userData.angle) * car.userData.lane;car.position.set(baseX + offsetX, 0.4, baseZ + offsetZ);car.rotation.y = -car.userData.angle + (car.userData.speed > 0 ? 0 : Math.PI);}});// 間歇性發射煙花if (Math.random() < 0.005) {launchIntermittentFirework();}// 更新煙花updateFireworks();// 場景緩慢旋轉scene.rotation.y += 0.002;renderer.render(scene, camera);}// 初始煙花launchInitialFireworks();animate();// 按鈕事件document.getElementById('controls').addEventListener('click', launchTieredFireworks);// 窗口大小調整window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>
顯示效果