【HTML】3D動態凱旋門

目錄

版本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 > radialRoadLengthcar.userData.position < 0path'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>

顯示效果


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/96152.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/96152.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/96152.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

(論文閱讀)FedViT:邊緣視覺轉換器的聯邦持續學習

FedViT&#xff1a;邊緣視覺轉換器的聯邦持續學習 FedViT: Federated continual learning of vision transformer at edge (北京理工大學-2023年發表于《Future Generation Computer Systems》中科院二區) highlight&#xff1a; ?提出一種輕量級的客戶端聯合持續學習方法。 ?…

微算法科技(NASDAQ: MLGO)研究利用PBFT中的動態視圖變換機制,實現區塊鏈系統高效運轉

隨著區塊鏈技術的飛速發展&#xff0c;其去中心化、透明性、不可篡改等特性使得它在金融、供應鏈管理、物聯網等多個領域得到了廣泛應用。然而&#xff0c;區塊鏈系統在高并發場景下的性能瓶頸問題一直是制約其大規模應用的關鍵因素。傳統的共識算法如PoW&#xff08;工作量證明…

從數據匯總到高級分析,SQL 查詢進階實戰(下篇)—— 分組、子查詢與窗口函數全攻略

引言&#xff1a;從 “提取數據” 到 “洞察價值”&#xff0c;SQL 進階之路 在掌握了基礎查詢與多表關聯后&#xff0c;你是否曾遇到這樣的挑戰&#xff1a;如何按部門統計平均薪資&#xff1f;怎樣找出每個崗位薪資最高的員工&#xff1f;或者如何計算銷售額的月度環比增長率…

Spring 和 Lettuce 源碼分析 Redis 節點狀態檢查與失敗重連的工作原理

關鍵步驟&#xff1a;Spring Boot 啟動時創建 LettuceConnectionFactory根據配置類型&#xff08;集群/哨兵/單機&#xff09;初始化客戶端對于集群模式&#xff1a;創建 RedisClusterClient調用 setOptions(getClusterClientOptions(configuration)) 應用配置2. 節點狀態檢查機…

從ChatGPT到智能助手:Agent智能體如何顛覆AI應用

從ChatGPT到智能助手&#xff1a;Agent智能體如何顛覆AI應用 更多大模型知識分享&#xff0c;盡在>>>GitHub<<< Agent 智能體是什么 簡單來說&#xff0c;Agent 智能體是一種能夠感知環境&#xff0c;并根據自身目標自主采取行動的智能實體。它就像是一個擁…

Spring Boot應用實現圖片資源服務

在這篇文章中&#xff0c;我們將介紹如何使用Spring Boot創建一個REST API來提供服務器上的靜態圖片資源。該API包括路徑安全檢查、文件存在性驗證以及緩存控制等功能&#xff0c;并且代碼包含詳細的注釋以幫助理解。Maven依賴 首先&#xff0c;在您的pom.xml文件中添加以下依賴…

Word 中 MathType 公式編號問題與解決

注&#xff1a;本文為 “Word 中 MathType 公式編號” 相關合輯。 圖片清晰度受引文原圖所限。 略作重排&#xff0c;未整理去重。 如有內容異常&#xff0c;請看原文。 【Word】解決 MathType 已插入公式按新章節開始編號的問題 Allan326 于 2020-03-25 15:30:08 發布 問題…

19. 大數據-產品概念

文章目錄前言一、數據庫1. 簡介2. 使用場景3. 數據庫類型4. 數據類型二、數據倉庫1. 簡介2. 使用場景3. 數據倉庫架構三、數據平臺1. 簡介2. 使用場景3. 數據倉庫架構四、數據中臺1. 簡介2. 使用場景3. 數據中臺架構五、數據湖1. 簡介2. 使用場景3. 數據湖架構六、總結1. 區別2…

python學習DAY46打卡

DAY 46 通道注意力(SE注意力) 內容&#xff1a; 不同CNN層的特征圖&#xff1a;不同通道的特征圖什么是注意力&#xff1a;注意力家族&#xff0c;類似于動物園&#xff0c;都是不同的模塊&#xff0c;好不好試了才知道。通道注意力&#xff1a;模型的定義和插入的位置通道注意…

Ansible 中的文件包含與導入機制

Ansible 中的文件包含與導入機制本文介紹了在 Ansible 中如何通過模塊化方式管理復雜的 Playbook&#xff0c;包括使用 include 和 import 系列語句來拆分和重用代碼。概述 當 Playbook 變得冗長或復雜時&#xff0c;可以將其拆分為多個小文件以提高可管理性。Ansible 提供了模…

OpenCV-循環讀取視頻幀,對每一幀進行處理

原型代碼 內存模型&#xff1a; 核心變量&#xff1a;frame&#xff0c;Numpy ndarray&#xff0c;每次會被覆蓋&#xff0c;大小保持恒定import cv2video_path your_video.mp4cap cv2.VideoCapture(video_path)if not cap.isOpened():print("Cant open Video")exi…

決策樹的學習(二)

一、整體框架本 PPT 聚焦機器學習中的決策樹算法&#xff0c;圍繞 “核心算法&#xff08;ID3、C4.5、CART&#xff09;→ 特殊問題&#xff08;連續值處理&#xff09;→ 優化策略&#xff08;剪枝&#xff09;→ 代碼實現→ 課堂練習” 展開&#xff0c;系統補充決策樹的進階…

粗糧廠的基于spark的通用olap之間的同步工具項目

粗糧廠的基于spark的通用olap之間的同步工具項目1 項目背景2 項目實現2.1 實現原理2.2 細節要點3 抽樣說明4 項目運行狀態4.1 運行速度4.2 項目吞吐4.3 穩定性說的比較簡單&#xff0c;有需要的可以留言&#xff0c;我不斷補充完善1 項目背景 我們公司內部的需要一款&#xff…

C# 時間戳

在C#中&#xff0c;獲取當前時間的毫秒級時間戳可以通過多種方式實現。以下是幾種常見的方法&#xff1a;方法1&#xff1a;使用DateTime和DateTimeOffsetlong timestamp (long)(DateTimeOffset.Now.ToUnixTimeMilliseconds()); Console.WriteLine(timestamp);方法2&#xff1…

【牛客刷題】REAL792 小O的平面畫圓

文章目錄 一、題目介紹 1.1 輸入描述 1.2 輸出描述 1.3 示例 二、算法設計思路 2.1 核心問題分析 2.2 圖解兩個圓的位置關系 2.2.1. 相離 (Separate) 2.2.2. 外切 (Externally Tangent) 2.2.3. 相交 (Intersecting) 2.2.4. 內切 (Internally Tangent) 2.2.5. 包含 (Containing)…

uniapp:微信小程序使用Canvas 和Canvas 2D繪制圖形

一、Canvas 畫布 canvas 組件 提供了繪制界面&#xff0c;可以在之上進行任意繪制 功能描述 Canvas 畫布。2.9.0 起支持一套新 Canvas 2D 接口&#xff08;需指定 type 屬性&#xff09;&#xff0c;同時支持同層渲染&#xff0c;原有接口不再維護。 二、Canvas 和Canvas 2D 區…

word如何轉換為pdf

pip install pywin32import os import win32com.client import pythoncom # 新增&#xff1a;用于處理COM線程 import sysdef docx_to_pdf(docx_path, pdf_pathNone):"""將Word文檔轉換為PDF格式&#xff0c;修復退出時的COM錯誤"""if not os.p…

服務器Linux防火墻怎樣實現訪問控制

在互聯網世界里&#xff0c;Linux服務器就像一座城池&#xff0c;而防火墻便是城池的守衛者。沒有防火墻&#xff0c;外部的任何流量都能毫無阻攔地進入服務器;而有了防火墻&#xff0c;就可以像設關卡一樣&#xff0c;對進出城門的人進行盤查和控制。對企業運維人員來說&#…

【原創理論】Stochastic Coupled Dyadic System (SCDS):一個用于兩性關系動力學建模的隨機耦合系統框架

【原創理論】Stochastic Coupled Dyadic System (SCDS)&#xff1a;一個用于兩性關系動力學建模的隨機耦合系統框架 作者&#xff1a;[望月&#xff0c;GPT5,GPT-O3,Gemini2.5pro] 分類&#xff1a; 人工智能 理論模型 交叉學科 系統科學 人性 愛情 標簽&#xff1a; 關系動力…

星圖云開發者平臺新功能速遞 | 微服務管理器:無縫整合異構服務,釋放云原生開發潛能

在構建現代數字化應用的過程中&#xff0c;開發者常常面臨一個關鍵挑戰&#xff1a;如何高效、安全地集成和復用既有的復雜服務或自有業務系統&#xff1f;這些服務可能是核心算法引擎、遺留業務邏輯模塊&#xff0c;或是特定的SaaS能力。傳統方式下&#xff0c;將它們融入新的…