目录

版本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;将它们融入新的…