当前位置: 首页 > news >正文

【HTML】3D动态凯旋门

目录

版本1.0:简易版本

版本2.0:建筑渲染

版本3.0:优化建筑群

版本4.0:增加公路和车流

版本5.0:去除压在公路上的建筑

版本6.0:优化车流群

版本7.0:添加烟花效果

版本8.0:添加树木

版本9.0:美化建筑群

版本10.0:添加云朵

版本11.0:添加动态热气球


版本1.0:简易版本

<!DOCTYPE html>
<html>
<head><title>3D凯旋门与扩展建筑群(斜角俯视)</title><style>body { margin: 0; }canvas { display: block; }</style>
</head>
<body><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 设置场景const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0xffffff, 1); // 白色背景document.body.appendChild(renderer.domElement);// 添加光源const ambientLight = new THREE.AmbientLight(0x404040);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);directionalLight.position.set(0, 1, 1);scene.add(directionalLight);// 创建凯旋门主要结构const archMaterial = new THREE.MeshPhongMaterial({ color: 0xD2B48C });// 底部基座const baseGeometry = new THREE.BoxGeometry(8, 2, 4);const base = new THREE.Mesh(baseGeometry, archMaterial);base.position.y = 1;scene.add(base);// 左右立柱const pillarGeometry = new THREE.BoxGeometry(2, 6, 4);const leftPillar = new THREE.Mesh(pillarGeometry, archMaterial);leftPillar.position.set(-3, 5, 0);scene.add(leftPillar);const rightPillar = new THREE.Mesh(pillarGeometry, archMaterial);rightPillar.position.set(3, 5, 0);scene.add(rightPillar);// 顶部横梁const topGeometry = new THREE.BoxGeometry(8, 2, 4);const topBeam = new THREE.Mesh(topGeometry, archMaterial);topBeam.position.y = 8;scene.add(topBeam);// 添加简单的装饰细节const detailGeometry = new THREE.BoxGeometry(7, 0.5, 0.5);const detail = new THREE.Mesh(detailGeometry, archMaterial);detail.position.set(0, 7, 2);scene.add(detail);// 添加扩展建筑群(5环布局,不遮挡凯旋门)const buildingMaterial = new THREE.MeshPhongMaterial({ color: 0x808080 }); // 灰色高楼材质const buildingGeometry = new THREE.BoxGeometry(3, 10, 3); // 高楼基本形状// 定义5环,调整半径和高度const rings = 5;const baseRadius = 20; // 起始环半径const ringSpacing = 10; // 每环间距for (let ring = 1; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4; // 每环建筑数量递增for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;const height = 6 + Math.random() * 6 * ring; // 高度6-12到6-36const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height / 2, z);buildingMesh.scale.y = height / 10; // 调整高度缩放scene.add(buildingMesh);}}// 设置相机位置(斜角俯视)camera.position.set(30, 30, 30); // 斜上方位置camera.lookAt(0, 5, 0); // 聚焦于凯旋门中心// 动画循环function animate() {requestAnimationFrame(animate);// 添加旋转动画scene.rotation.y += 0.002; // 减慢旋转速度renderer.render(scene, camera);}animate();// 处理窗口大小变化window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>

效果图:


版本2.0:建筑渲染

<!DOCTYPE html>
<html>
<head><title>3D凯旋门与多彩建筑群(斜角俯视)</title><style>body { margin: 0; }canvas { display: block; }</style>
</head>
<body><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 设置场景const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0xffffff, 1); // 白色背景document.body.appendChild(renderer.domElement);// 添加光源const ambientLight = new THREE.AmbientLight(0x404040);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);directionalLight.position.set(0, 1, 1);scene.add(directionalLight);// 创建凯旋门主要结构const archMaterial = new THREE.MeshPhongMaterial({ color: 0xD2B48C });// 底部基座const baseGeometry = new THREE.BoxGeometry(8, 2, 4);const base = new THREE.Mesh(baseGeometry, archMaterial);base.position.y = 1;scene.add(base);// 左右立柱const pillarGeometry = new THREE.BoxGeometry(2, 6, 4);const leftPillar = new THREE.Mesh(pillarGeometry, archMaterial);leftPillar.position.set(-3, 5, 0); // 修正:移除 .demoscene.add(leftPillar);const rightPillar = new THREE.Mesh(pillarGeometry, archMaterial);rightPillar.position.set(3, 5, 0);scene.add(rightPillar);// 顶部横梁const topGeometry = new THREE.BoxGeometry(8, 2, 4);const topBeam = new THREE.Mesh(topGeometry, archMaterial);topBeam.position.y = 8;scene.add(topBeam);// 添加简单的装饰细节const detailGeometry = new THREE.BoxGeometry(7, 0.5, 0.5);const detail = new THREE.Mesh(detailGeometry, archMaterial);detail.position.set(0, 7, 2);scene.add(detail);// 添加扩展建筑群(5环布局,丰富颜色和窗户)const buildingGeometry = new THREE.BoxGeometry(3, 10, 3); // 高楼基本形状const windowMaterial = new THREE.MeshPhongMaterial({ color: 0xFFFFFF }); // 白色窗户材质const windowGeometry = new THREE.BoxGeometry(0.4, 0.4, 0.1); // 窗户形状// 定义颜色调色板const buildingColors = [0xFF6347, // 番茄红0x4682B4, // 钢蓝0x32CD32, // 柠檬绿0xFFD700, // 金黄0x9932CC, // 深紫0xFF4500, // 橙红0x00CED1  // 深青];// 定义5环const rings = 5;const baseRadius = 20; // 起始环半径const ringSpacing = 10; // 每环间距for (let ring = 1; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4; // 每环建筑数量递增for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;const height = 6 + Math.random() * 6 * ring; // 高度6-12到6-36// 创建建筑const buildingMaterial = new THREE.MeshPhongMaterial({ color: buildingColors[Math.floor(Math.random() * buildingColors.length)] // 随机颜色});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height / 2, z);buildingMesh.scale.y = height / 10; // 调整高度缩放scene.add(buildingMesh);// 添加窗户(在建筑正面和侧面)const numWindows = Math.floor(height / 2); // 根据高度确定窗户数量for (let w = 0; w < numWindows; w++) {// 正面窗户const windowMesh1 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh1.position.set(x + 1.2, (w * 1.5) + 1, z + 1.51); // 建筑正面windowMesh1.scale.y = height / 10;scene.add(windowMesh1);const windowMesh2 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh2.position.set(x - 1.2, (w * 1.5) + 1, z + 1.51); // 建筑正面另一侧windowMesh2.scale.y = height / 10;scene.add(windowMesh2);// 侧面窗户const windowMesh3 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh3.position.set(x + 1.51, (w * 1.5) + 1, z + 1.2); // 建筑侧面windowMesh3.scale.y = height / 10;scene.add(windowMesh3);const windowMesh4 = new THREE.Mesh(windowGeometry, windowMaterial); // 修正:outraTHREE 改为 THREEwindowMesh4.position.set(x + 1.51, (w * 1.5) + 1, z - 1.2); // 建筑侧面另一侧windowMesh4.scale.y = height / 10;scene.add(windowMesh4);}}}// 设置相机位置(斜角俯视)camera.position.set(30, 30, 30); // 斜上方位置camera.lookAt(0, 5, 0); // 聚焦于凯旋门中心// 动画循环function animate() {requestAnimationFrame(animate);// 添加旋转动画scene.rotation.y += 0.002; // 减慢旋转速度renderer.render(scene, camera);}animate();// 处理窗口大小变化window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>

效果图


版本3.0:优化建筑群

<!DOCTYPE html>
<html>
<head><title>3D超大精细凯旋门与多彩建筑群(斜角俯视)</title><style>body { margin: 0; }canvas { display: block; }</style>
</head>
<body><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 设置场景const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0xffffff, 1); // 白色背景document.body.appendChild(renderer.domElement);// 添加光源(增强细节)const ambientLight = new THREE.AmbientLight(0x404040, 1.2);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 凯旋门材质(增加石材质感)const archMaterial = new THREE.MeshStandardMaterial({ color: 0xD2B48C, roughness: 0.8, metalness: 0.2 });// 精细凯旋门结构(宽13.5、高15、深6.6,放大1.5倍)// 基座const baseGeometry = new THREE.BoxGeometry(13.5, 1.5, 6.6);const base = new THREE.Mesh(baseGeometry, archMaterial);base.position.y = 0.75;scene.add(base);// 四个支撑柱子(更宽大,模拟拱门)const pillarWidth = 2.2;const pillarHeight = 12;const pillarGeometry = new THREE.BoxGeometry(pillarWidth, pillarHeight, pillarWidth);const frontLeftPillar = new THREE.Mesh(pillarGeometry, archMaterial);frontLeftPillar.position.set(-5.65, 7.5, -2.2);scene.add(frontLeftPillar);const frontRightPillar = new THREE.Mesh(pillarGeometry, archMaterial);frontRightPillar.position.set(5.65, 7.5, -2.2);scene.add(frontRightPillar);const backLeftPillar = new THREE.Mesh(pillarGeometry, archMaterial);backLeftPillar.position.set(-5.65, 7.5, 2.2);scene.add(backLeftPillar);const backRightPillar = new THREE.Mesh(pillarGeometry, archMaterial);backRightPillar.position.set(5.65, 7.5, 2.2);scene.add(backRightPillar);// 中间横梁(中央拱门顶部)const midBeamGeometry = new THREE.BoxGeometry(13.5, 1, 6.6);const midBeam = new THREE.Mesh(midBeamGeometry, archMaterial);midBeam.position.y = 10;scene.add(midBeam);// 顶部atticconst atticGeometry = new THREE.BoxGeometry(13.5, 1.5, 6.6);const attic = new THREE.Mesh(atticGeometry, archMaterial);attic.position.y = 13.5;scene.add(attic);// 顶部雕塑(简化的Quadriga)const quadrigaGeometry = new THREE.BoxGeometry(2, 1, 2);const quadrigaMaterial = new THREE.MeshPhongMaterial({ color: 0xB8860B });const quadriga = new THREE.Mesh(quadrigaGeometry, quadrigaMaterial);quadriga.position.set(0, 14.5, 0);scene.add(quadriga);// Frieze(带状装饰,增加细节)const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1);scene.add(frieze);const friezeBack = frieze.clone();friezeBack.position.z = -3.1;scene.add(friezeBack);// Frieze上的小装饰(模拟胜利盾牌)const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2);scene.add(detail);const detailBack = detail.clone();detailBack.position.z = -3.2;scene.add(detailBack);}// 基座雕塑(四个雕塑组)const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({ color: 0xB8860B });const sculpture1 = new THREE.Mesh(sculptureGeometry, sculptureMaterial);sculpture1.position.set(-5.65, 3, -3.2);scene.add(sculpture1);const sculpture2 = sculpture1.clone();sculpture2.position.set(5.65, 3, -3.2);scene.add(sculpture2);const sculpture3 = sculpture1.clone();sculpture3.position.set(-5.65, 3, 3.2);scene.add(sculpture3);const sculpture4 = sculpture1.clone();sculpture4.position.set(5.65, 3, 3.2);scene.add(sculpture4);// 扩展建筑群(高度4-8单位)const buildingGeometry = new THREE.BoxGeometry(3, 10, 3);const windowMaterial = new THREE.MeshPhongMaterial({ color: 0xFFFFFF });const windowGeometry = new THREE.BoxGeometry(0.4, 0.4, 0.1);const buildingColors = [0xFF6347, 0x4682B4, 0x32CD32, 0xFFD700, 0x9932CC, 0xFF4500, 0x00CED1];const rings = 5;const baseRadius = 25; // 增大半径以避免遮挡const ringSpacing = 10;for (let ring = 1; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;const height = 4 + Math.random() * 4; // 高度4-8单位const buildingMaterial = new THREE.MeshPhongMaterial({ color: buildingColors[Math.floor(Math.random() * buildingColors.length)]});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height / 2, z);buildingMesh.scale.y = height / 10;scene.add(buildingMesh);const numWindows = Math.floor(height / 2);for (let w = 0; w < numWindows; w++) {const windowMesh1 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh1.position.set(x + 1.2, (w * 1.5) + 1, z + 1.51);windowMesh1.scale.y = height / 10;scene.add(windowMesh1);const windowMesh2 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh2.position.set(x - 1.2, (w * 1.5) + 1, z + 1.51);windowMesh2.scale.y = height / 10;scene.add(windowMesh2);const windowMesh3 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh3.position.set(x + 1.51, (w * 1.5) + 1, z + 1.2);windowMesh3.scale.y = height / 10;scene.add(windowMesh3);const windowMesh4 = new THREE.Mesh(windowGeometry, windowMaterial);windowMesh4.position.set(x + 1.51, (w * 1.5) + 1, z - 1.2);windowMesh4.scale.y = height / 10;scene.add(windowMesh4);}}}// 设置相机位置(斜角俯视,适应更大凯旋门)camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 动画循环function animate() {requestAnimationFrame(animate);scene.rotation.y += 0.002;renderer.render(scene, camera);}animate();// 处理窗口大小变化window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>

图示效果


版本4.0:增加公路和车流

  • 在原本凯旋门和建筑群的基础上,加上 环形公路 + 放射状公路 + 科技感灯光 + 内外环对向车流
<!DOCTYPE html>
<html>
<head><title>3D超大精细凯旋门与多彩建筑群(斜角俯视)</title><style>body { margin: 0; }canvas { display: block; }</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<script>// 场景 & 相机 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0xffffff, 1);document.body.appendChild(renderer.domElement);// 灯光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 凯旋门材质const archMaterial = new THREE.MeshStandardMaterial({color: 0xD2B48C, roughness: 0.8, metalness: 0.2});// 凯旋门主体const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5,  2.2], [5.65, 7.5,  2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3,  3.2], [5.65, 3,  3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// 建筑群const buildingGeometry = new THREE.BoxGeometry(3, 10, 3);const windowMaterial = new THREE.MeshPhongMaterial({color: 0xFFFFFF});const windowGeometry = new THREE.BoxGeometry(0.4, 0.4, 0.1);const buildingColors = [0xFF6347,0x4682B4,0x32CD32,0xFFD700,0x9932CC,0xFF4500,0x00CED1];const rings = 5, baseRadius = 25, ringSpacing = 10;for (let ring = 1; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;const height = 4 + Math.random() * 4;const buildingMaterial = new THREE.MeshPhongMaterial({color: buildingColors[Math.floor(Math.random()*buildingColors.length)]});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height/2, z);buildingMesh.scale.y = height / 10;scene.add(buildingMesh);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 4;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);// 放射状公路const radialRoadLength = 60;const radialRoadWidth = 3;for (let i = 0; i < 8; i++) {const angle = (i / 8) * Math.PI * 2;const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeo = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glow = new THREE.Mesh(glowGeo, glowMaterial);glow.position.copy(road.position);glow.rotation.y = road.rotation.y;glow.position.y = 0.06;scene.add(glow);}// ===== 动态车群(内外环对向行驶) =====const cars = [];const numCars = 30;const innerRadius = roadRadius - 1;const outerRadius = roadRadius + 1;for (let i = 0; i < numCars; i++) {const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const isOuter = i % 2 === 0; // 偶数外环,奇数内环car.userData = {angle: Math.random() * Math.PI * 2,speed: (0.002 + Math.random() * 0.002) * (isOuter ? 1 : -1),radius: isOuter ? outerRadius : innerRadius};scene.add(car);cars.push(car);}// 相机位置camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 动画function animate() {requestAnimationFrame(animate);cars.forEach(car => {car.userData.angle += car.userData.speed;car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;});scene.rotation.y += 0.002;renderer.render(scene, camera);}animate();window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});
</script>
</body>
</html>

效果图


版本5.0:去除压在公路上的建筑

优化点

  • 公路(环形和放射状)完全没有建筑压住

  • 所有道路都畅通可见

  • 建筑分布更合理

<!DOCTYPE html>
<html>
<head><title>3D凯旋门与畅通道路的建筑群</title><style>body { margin: 0; }canvas { display: block; }</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<script>// 场景 & 相机 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0xffffff, 1);document.body.appendChild(renderer.domElement);// 灯光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 凯旋门材质const archMaterial = new THREE.MeshStandardMaterial({color: 0xD2B48C, roughness: 0.8, metalness: 0.2});// 凯旋门主体const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5,  2.2], [5.65, 7.5,  2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3,  3.2], [5.65, 3,  3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 建筑群(避开环形和放射状道路) =====const buildingGeometry = new THREE.BoxGeometry(3, 10, 3);const buildingColors = [0xFF6347,0x4682B4,0x32CD32,0xFFD700,0x9932CC,0xFF4500,0x00CED1];const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = []; // 放射状道路角度const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) { // 从第2圈开始const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;// 检测是否在放射状道路范围内let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) { // 道路两侧 6 米范围内不建楼onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 4 + Math.random() * 4;const buildingMaterial = new THREE.MeshPhongMaterial({color: buildingColors[Math.floor(Math.random()*buildingColors.length)]});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height/2, z);buildingMesh.scale.y = height / 10;scene.add(buildingMesh);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 4;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);// 放射状公路const radialRoadLength = 60;const radialRoadWidth = 3;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeo = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glow = new THREE.Mesh(glowGeo, glowMaterial);glow.position.copy(road.position);glow.rotation.y = road.rotation.y;glow.position.y = 0.06;scene.add(glow);}// ===== 动态车群 =====const cars = [];const numCars = 30;const innerRadius = roadRadius - 1;const outerRadius = roadRadius + 1;for (let i = 0; i < numCars; i++) {const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const isOuter = i % 2 === 0;car.userData = {angle: Math.random() * Math.PI * 2,speed: (0.002 + Math.random() * 0.002) * (isOuter ? 1 : -1),radius: isOuter ? outerRadius : innerRadius};scene.add(car);cars.push(car);}// 相机camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 动画function animate() {requestAnimationFrame(animate);cars.forEach(car => {car.userData.angle += car.userData.speed;car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;});scene.rotation.y += 0.002;renderer.render(scene, camera);}animate();window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});
</script>
</body>
</html>

效果图


版本6.0:优化车流群

  • 环形公路:双向环形车流(内圈逆时针,外圈顺时针)

  • 放射状公路:每条都有双向车流,到端点自动掉头

  • 建筑:完全避开公路,不会挡路

  • 统一的交通逻辑:汽车不再有两个完全独立的交通环路,而是具有可以是 或 的属性。这个单一的动画循环可以处理所有汽车,并允许动态路径切换。userData.path'ring''radial'

  • 无缝路径转换

    • 径向到环形:当径向道路上的汽车到达终点 () 或中心 () 时,它会自动切换到 。然后,它计算出一个新的角度和速度,以继续沿环形道路行驶,从而创建平滑过渡。car.userData.position > radialRoadLengthcar.userData.position < 0path'ring'

    • 环形到径向:当环路上的汽车经过径向道路交叉口时,它有很小的机会切换到径向道路。这是通过防止所有汽车同时改变路径并确保动态、逼真的流动进行控制的。Math.random() < 0.01

  • 改进的代码结构:汽车创建循环更加集中。创建汽车并给出初始路径和位置。然后,单个动画循环管理所有汽车的状态和运动,无论其当前路径如何。

  • 动态摄像机:场景现在围绕原点旋转 ()。这让用户更好地感受到一个充满活力、不断运动的充满活力的城市。scene.rotation.y += 0.002

<!DOCTYPE html>
<html>
<head><title>3D凯旋门与畅通道路的建筑群 - 优化版</title><style>body { margin: 0; overflow: hidden; }canvas { display: block; }</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<script>// 场景 & 相机 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0xffffff, 1);document.body.appendChild(renderer.domElement);// 灯光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 凯旋门材质const archMaterial = new THREE.MeshStandardMaterial({color: 0xD2B48C, roughness: 0.8, metalness: 0.2});// 凯旋门主体const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5,  2.2], [5.65, 7.5,  2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3,  3.2], [5.65, 3,  3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 建筑群 =====const buildingGeometry = new THREE.BoxGeometry(3, 10, 3);const buildingColors = [0xFF6347,0x4682B4,0x32CD32,0xFFD700,0x9932CC,0xFF4500,0x00CED1];const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = [];const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) {onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 4 + Math.random() * 4;const buildingMaterial = new THREE.MeshPhongMaterial({color: buildingColors[Math.floor(Math.random()*buildingColors.length)]});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height/2, z);buildingMesh.scale.y = height / 10;scene.add(buildingMesh);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 6;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);const radialRoadLength = 60;const radialRoadWidth = 5;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeoLeft = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowLeft = new THREE.Mesh(glowGeoLeft, glowMaterial);glowLeft.position.set(road.position.x + Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z - Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowLeft.rotation.y = road.rotation.y;scene.add(glowLeft);const glowGeoRight = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowRight = new THREE.Mesh(glowGeoRight, glowMaterial);glowRight.position.set(road.position.x - Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z + Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowRight.rotation.y = road.rotation.y;scene.add(glowRight);}// ===== 优化车流 =====const cars = [];const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const numCars = 100;const laneOffset = radialRoadWidth / 4;const innerRadius = roadRadius - roadWidth / 4;const outerRadius = roadRadius + roadWidth / 4;for (let i = 0; i < numCars; i++) {const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const initialPath = Math.random() < 0.5 ? 'ring' : 'radial';let initialAngle, initialDirection, initialRadius, initialPosition, initialLane;if (initialPath === 'ring') {initialAngle = Math.random() * Math.PI * 2;initialDirection = Math.random() < 0.5 ? 1 : -1;initialRadius = initialDirection === 1 ? outerRadius : innerRadius;car.userData = {path: 'ring',angle: initialAngle,speed: (0.0015 + Math.random() * 0.0025) * initialDirection,radius: initialRadius,lane: 0 // Not used for ring, but for consistency};} else {initialAngle = radialAngles[Math.floor(Math.random() * numRadials)];initialDirection = Math.random() < 0.5 ? 1 : -1;initialPosition = Math.random() * radialRoadLength;initialLane = initialDirection === 1 ? laneOffset : -laneOffset;car.userData = {path: 'radial',angle: initialAngle,position: initialPosition,speed: (0.05 + Math.random() * 0.03) * initialDirection,lane: initialLane};}scene.add(car);cars.push(car);}// 相机camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 动画function animate() {requestAnimationFrame(animate);cars.forEach(car => {if (car.userData.path === 'ring') {car.userData.angle += car.userData.speed;// 检查是否接近放射状道路的交叉口const currentAngle = car.userData.angle % (Math.PI * 2);for (const radialAngle of radialAngles) {if (Math.abs(currentAngle - radialAngle) < 0.05 && Math.random() < 0.01) { // 随机决定是否转向const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'radial';car.userData.angle = radialAngle;car.userData.position = newDirection > 0 ? 0 : radialRoadLength;car.userData.speed = (0.05 + Math.random() * 0.03) * newDirection;car.userData.lane = car.userData.speed > 0 ? laneOffset : -laneOffset;break;}}car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;} else if (car.userData.path === 'radial') {car.userData.position += car.userData.speed;// 检查是否到达道路尽头或中心if ((car.userData.speed > 0 && car.userData.position > radialRoadLength) || (car.userData.speed < 0 && car.userData.position < 0)) {// 切换到环形道路const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'ring';car.userData.angle = car.userData.angle + (Math.PI / 2); // 从径向到环形,角度需要偏移car.userData.speed = (0.0015 + Math.random() * 0.0025) * newDirection;car.userData.radius = newDirection > 0 ? outerRadius : innerRadius;}const baseX = Math.cos(car.userData.angle) * (car.userData.position + roadRadius);const baseZ = Math.sin(car.userData.angle) * (car.userData.position + roadRadius);const offsetX = Math.sin(car.userData.angle) * car.userData.lane;const offsetZ = -Math.cos(car.userData.angle) * car.userData.lane;car.position.set(baseX + offsetX, 0.4, baseZ + offsetZ);car.rotation.y = -car.userData.angle + (car.userData.speed > 0 ? 0 : Math.PI);}});scene.rotation.y += 0.002;renderer.render(scene, camera);}animate();window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});
</script>
</body>
</html>

效果图


版本7.0:添加烟花效果

<!DOCTYPE html>
<html>
<head><title>3D凯旋门与畅通道路的建筑群 - 天蓝色背景版</title><style>body { margin: 0; overflow: hidden; font-family: sans-serif; }canvas { display: block; }#controls {position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);padding: 10px 20px;font-size: 16px;cursor: pointer;border: none;background-color: rgba(0, 0, 0, 0.2);color: white;border-radius: 5px;}</style>
</head>
<body><button id="controls">启动阶梯式烟花</button><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 场景 & 相机 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0x87CEEB, 1); // 天蓝色背景document.body.appendChild(renderer.domElement);// 灯光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 地面const groundGeometry = new THREE.PlaneGeometry(2000, 2000);const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 }); // 接近公路的颜色const ground = new THREE.Mesh(groundGeometry, groundMaterial);ground.rotation.x = -Math.PI / 2;scene.add(ground);// 凯旋门材质const archMaterial = new THREE.MeshStandardMaterial({color: 0xD2B48C, roughness: 0.8, metalness: 0.2}); // 黄褐色凯旋门// 凯旋门主体const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5,  2.2], [5.65, 7.5,  2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3,  3.2], [5.65, 3,  3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 建筑群 =====const buildingGeometry = new THREE.BoxGeometry(3, 10, 3);const buildingColors = [0xFF6347,0x4682B4,0x32CD32,0xFFD700,0x9932CC,0xFF4500,0x00CED1];const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = [];const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) {onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 4 + Math.random() * 4;const buildingMaterial = new THREE.MeshPhongMaterial({color: buildingColors[Math.floor(Math.random()*buildingColors.length)]});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height/2, z);buildingMesh.scale.y = height / 10;scene.add(buildingMesh);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 6;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);// 移除闪烁效果,直接添加不闪烁的发光环const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);const radialRoadLength = 60;const radialRoadWidth = 5;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeoLeft = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowLeft = new THREE.Mesh(glowGeoLeft, glowMaterial);glowLeft.position.set(road.position.x + Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z - Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowLeft.rotation.y = road.rotation.y;scene.add(glowLeft);const glowGeoRight = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowRight = new THREE.Mesh(glowGeoRight, glowMaterial);glowRight.position.set(road.position.x - Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z + Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowRight.rotation.y = road.rotation.y;scene.add(glowRight);}// ===== 优化车流 =====const cars = [];const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const numCars = 100;const laneOffset = radialRoadWidth / 4;const innerRadius = roadRadius - roadWidth / 4;const outerRadius = roadRadius + roadWidth / 4;for (let i = 0; i < numCars; i++) {const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const initialPath = Math.random() < 0.5 ? 'ring' : 'radial';let initialAngle, initialDirection, initialRadius, initialPosition, initialLane;if (initialPath === 'ring') {initialAngle = Math.random() * Math.PI * 2;initialDirection = Math.random() < 0.5 ? 1 : -1;initialRadius = initialDirection === 1 ? outerRadius : innerRadius;car.userData = {path: 'ring',angle: initialAngle,speed: (0.0015 + Math.random() * 0.0025) * initialDirection,radius: initialRadius,lane: 0};} else {initialAngle = radialAngles[Math.floor(Math.random() * numRadials)];initialDirection = Math.random() < 0.5 ? 1 : -1;initialPosition = Math.random() * radialRoadLength;initialLane = initialDirection === 1 ? laneOffset : -laneOffset;car.userData = {path: 'radial',angle: initialAngle,position: initialPosition,speed: (0.05 + Math.random() * 0.03) * initialDirection,lane: initialLane};}scene.add(car);cars.push(car);}// ===== 烟花代码开始 =====const fireworks = [];const particleGeometry = new THREE.SphereGeometry(0.08, 8, 8);const innerCircleRadius = roadRadius - roadWidth / 2;// 烟花爆炸函数function createExplosion(originPosition, color) {const numParticles = 60 + Math.floor(Math.random() * 30);const explosionRadius = 0.5 + Math.random();for (let i = 0; i < numParticles; i++) {const particleMaterial = new THREE.MeshBasicMaterial({ color: color });const particle = new THREE.Mesh(particleGeometry, particleMaterial);particle.position.copy(originPosition);const velocity = new THREE.Vector3((Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius);particle.userData = {velocity: velocity,life: 1.5 + Math.random() * 1,state: 'exploded'};fireworks.push(particle);scene.add(particle);}}// 发射烟花(升空)function launchFirework(startPosition) {const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(startPosition);// 爆炸高度比凯旋门低一些,约12-18const targetHeight = 12 + Math.random() * 6;const color = new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex();rocket.userData = {velocity: new THREE.Vector3(0, 0.35 + Math.random() * 0.25, 0),state: 'rising',targetY: targetHeight,color: color};fireworks.push(rocket);scene.add(rocket);}// 场景初始化时的所有烟花一起升空function launchInitialFireworks() {for (let i = 0; i < 15; i++) {const angle = (i / 15) * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}}// 间歇性发射单个烟花function launchIntermittentFirework() {const angle = Math.random() * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}// 阶梯式环形发射烟花function launchTieredFireworks() {const numTiers = 6;const delayPerTier = 400; // 毫秒const baseHeight = 12;const heightStep = 3; // 阶梯高度for (let i = 0; i < numTiers; i++) {setTimeout(() => {const angle = (i / numTiers) * Math.PI * 2;const radius = innerCircleRadius * 0.8;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(position);rocket.userData = {velocity: new THREE.Vector3(0, 0.35, 0),state: 'rising',targetY: baseHeight + i * heightStep,color: new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex()};fireworks.push(rocket);scene.add(rocket);}, i * delayPerTier);}}function updateFireworks() {for (let i = fireworks.length - 1; i >= 0; i--) {const p = fireworks [i];if (p.userData.state === 'rising') {p.position.add(p.userData.velocity);if (p.position.y >= p.userData.targetY) {createExplosion(p.position, p.userData.color);scene.remove(p);fireworks.splice(i, 1);}} else if (p.userData.state === 'exploded') {p.userData.life -= 0.015;p.position.add(p.userData.velocity);p.material.opacity = Math.max(0, p.userData.life / 2);p.material.transparent = true;p.userData.velocity.y -= 0.002;if (p.userData.life <= 0) {scene.remove(p);fireworks.splice(i, 1);}}}}// ===== 烟花代码结束 =====// 相机camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 动画function animate() {requestAnimationFrame(animate);cars.forEach(car => {if (car.userData.path === 'ring') {car.userData.angle += car.userData.speed;const currentAngle = car.userData.angle % (Math.PI * 2);for (const radialAngle of radialAngles) {if (Math.abs(currentAngle - radialAngle) < 0.05 && Math.random() < 0.01) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'radial';car.userData.angle = radialAngle;car.userData.position = newDirection > 0 ? 0 : radialRoadLength;car.userData.speed = (0.05 + Math.random() * 0.03) * newDirection;car.userData.lane = car.userData.speed > 0 ? laneOffset : -laneOffset;break;}}car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;} else if (car.userData.path === 'radial') {car.userData.position += car.userData.speed;if ((car.userData.speed > 0 && car.userData.position > radialRoadLength) || (car.userData.speed < 0 && car.userData.position < 0)) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'ring';car.userData.angle = car.userData.angle + (Math.PI / 2);car.userData.speed = (0.0015 + Math.random() * 0.0025) * newDirection;car.userData.radius = newDirection > 0 ? outerRadius : innerRadius;}const baseX = Math.cos(car.userData.angle) * (car.userData.position + roadRadius);const baseZ = Math.sin(car.userData.angle) * (car.userData.position + roadRadius);const offsetX = Math.sin(car.userData.angle) * car.userData.lane;const offsetZ = -Math.cos(car.userData.angle) * car.userData.lane;car.position.set(baseX + offsetX, 0.4, baseZ + offsetZ);car.rotation.y = -car.userData.angle + (car.userData.speed > 0 ? 0 : Math.PI);}});// 间歇性发射新烟花if (Math.random() < 0.005) {launchIntermittentFirework();}// 更新烟花状态updateFireworks();scene.rotation.y += 0.002;renderer.render(scene, camera);}// 初始烟花发射launchInitialFireworks();animate();// 按钮点击事件document.getElementById('controls').addEventListener('click', launchTieredFireworks);window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>

显示效果


版本8.0:添加树木

  • 背景颜色: 天蓝色

  • 地面颜色: 略浅于公路的深灰色

  • 凯旋门颜色: 黄褐色

  • 树木: 在环形道路内侧和放射状公路两侧都增加了树木

  • 放射状树木: 去除了最靠近环形公路的那一圈树木

<!DOCTYPE html>
<html>
<head><title>3D凯旋门与畅通道路的建筑群 - 最终版</title><style>body { margin: 0; overflow: hidden; font-family: sans-serif; }canvas { display: block; }#controls {position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);padding: 10px 20px;font-size: 16px;cursor: pointer;border: none;background-color: rgba(0, 0, 0, 0.2);color: white;border-radius: 5px;}</style>
</head>
<body><button id="controls">启动阶梯式烟花</button><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 场景 & 相机 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0x87CEEB, 1); // 天蓝色背景document.body.appendChild(renderer.domElement);// 灯光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 地面const groundGeometry = new THREE.PlaneGeometry(2000, 2000);const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 }); // 接近公路的颜色const ground = new THREE.Mesh(groundGeometry, groundMaterial);ground.rotation.x = -Math.PI / 2;scene.add(ground);// 凯旋门材质const archMaterial = new THREE.MeshStandardMaterial({color: 0xD2B48C, roughness: 0.8, metalness: 0.2}); // 黄褐色凯旋门// 凯旋门主体const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5,  2.2], [5.65, 7.5,  2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3,  3.2], [5.65, 3,  3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 建筑群 =====const buildingGeometry = new THREE.BoxGeometry(3, 10, 3);const buildingColors = [0xFF6347,0x4682B4,0x32CD32,0xFFD700,0x9932CC,0xFF4500,0x00CED1];const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = [];const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) {onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 4 + Math.random() * 4;const buildingMaterial = new THREE.MeshPhongMaterial({color: buildingColors[Math.floor(Math.random()*buildingColors.length)]});const buildingMesh = new THREE.Mesh(buildingGeometry, buildingMaterial);buildingMesh.position.set(x, height/2, z);buildingMesh.scale.y = height / 10;scene.add(buildingMesh);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 6;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);// 移除闪烁效果,直接添加不闪烁的发光环const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);const radialRoadLength = 60;const radialRoadWidth = 5;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeoLeft = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowLeft = new THREE.Mesh(glowGeoLeft, glowMaterial);glowLeft.position.set(road.position.x + Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z - Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowLeft.rotation.y = road.rotation.y;scene.add(glowLeft);const glowGeoRight = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowRight = new THREE.Mesh(glowGeoRight, glowMaterial);glowRight.position.set(road.position.x - Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z + Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowRight.rotation.y = road.rotation.y;scene.add(glowRight);}// ===== 树木 =====const treeTrunkGeometry = new THREE.CylinderGeometry(0.3, 0.3, 2, 8);const treeLeavesGeometry = new THREE.ConeGeometry(1.5, 3, 8);const treeTrunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });const treeLeavesMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });function createTree(x, z) {const trunk = new THREE.Mesh(treeTrunkGeometry, treeTrunkMaterial);trunk.position.set(x, 1, z);scene.add(trunk);const leaves = new THREE.Mesh(treeLeavesGeometry, treeLeavesMaterial);leaves.position.set(x, 3.5, z);scene.add(leaves);}// 在凯旋门和道路之间生成树林const innerForestRadius = 15;const outerForestRadius = roadRadius - roadWidth / 2 - 2;const numForestTrees = 150;for (let i = 0; i < numForestTrees; i++) {const angle = Math.random() * Math.PI * 2;const radius = innerForestRadius + Math.random() * (outerForestRadius - innerForestRadius);const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;createTree(x, z);}// 沿径向公路两侧生成树木,跳过最内侧一圈const treeSpacing = 6;const radialOffset = radialRoadWidth / 2 + 1;for (let angle of radialAngles) {for (let i = 1; i < radialRoadLength / treeSpacing; i++) { // i从1开始,跳过最里面一圈const dist = i * treeSpacing;// 左侧树木const xLeft = Math.cos(angle) * (dist + roadRadius) + Math.sin(angle) * radialOffset;const zLeft = Math.sin(angle) * (dist + roadRadius) - Math.cos(angle) * radialOffset;createTree(xLeft, zLeft);// 右侧树木const xRight = Math.cos(angle) * (dist + roadRadius) - Math.sin(angle) * radialOffset;const zRight = Math.sin(angle) * (dist + roadRadius) + Math.cos(angle) * radialOffset;createTree(xRight, zRight);}}// ===== 优化车流 =====const cars = [];const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const numCars = 100;const laneOffset = radialRoadWidth / 4;const innerRadius = roadRadius - roadWidth / 4;const outerRadius = roadRadius + roadWidth / 4;for (let i = 0; i < numCars; i++) {const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const initialPath = Math.random() < 0.5 ? 'ring' : 'radial';let initialAngle, initialDirection, initialRadius, initialPosition, initialLane;if (initialPath === 'ring') {initialAngle = Math.random() * Math.PI * 2;initialDirection = Math.random() < 0.5 ? 1 : -1;initialRadius = initialDirection === 1 ? outerRadius : innerRadius;car.userData = {path: 'ring',angle: initialAngle,speed: (0.0015 + Math.random() * 0.0025) * initialDirection,radius: initialRadius,lane: 0};} else {initialAngle = radialAngles[Math.floor(Math.random() * numRadials)];initialDirection = Math.random() < 0.5 ? 1 : -1;initialPosition = Math.random() * radialRoadLength;initialLane = initialDirection === 1 ? laneOffset : -laneOffset;car.userData = {path: 'radial',angle: initialAngle,position: initialPosition,speed: (0.05 + Math.random() * 0.03) * initialDirection,lane: initialLane};}scene.add(car);cars.push(car);}// ===== 烟花代码开始 =====const fireworks = [];const particleGeometry = new THREE.SphereGeometry(0.08, 8, 8);const innerCircleRadius = roadRadius - roadWidth / 2;// 烟花爆炸函数function createExplosion(originPosition, color) {const numParticles = 60 + Math.floor(Math.random() * 30);const explosionRadius = 0.5 + Math.random();for (let i = 0; i < numParticles; i++) {const particleMaterial = new THREE.MeshBasicMaterial({ color: color });const particle = new THREE.Mesh(particleGeometry, particleMaterial);particle.position.copy(originPosition);const velocity = new THREE.Vector3((Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius);particle.userData = {velocity: velocity,life: 1.5 + Math.random() * 1,state: 'exploded'};fireworks.push(particle);scene.add(particle);}}// 发射烟花(升空)function launchFirework(startPosition) {const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(startPosition);// 爆炸高度比凯旋门低一些,约12-18const targetHeight = 12 + Math.random() * 6;const color = new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex();rocket.userData = {velocity: new THREE.Vector3(0, 0.35 + Math.random() * 0.25, 0),state: 'rising',targetY: targetHeight,color: color};fireworks.push(rocket);scene.add(rocket);}// 场景初始化时的所有烟花一起升空function launchInitialFireworks() {for (let i = 0; i < 15; i++) {const angle = (i / 15) * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}}// 间歇性发射单个烟花function launchIntermittentFirework() {const angle = Math.random() * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}// 阶梯式环形发射烟花function launchTieredFireworks() {const numTiers = 6;const delayPerTier = 400; // 毫秒const baseHeight = 12;const heightStep = 3; // 阶梯高度for (let i = 0; i < numTiers; i++) {setTimeout(() => {const angle = (i / numTiers) * Math.PI * 2;const radius = innerCircleRadius * 0.8;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(position);rocket.userData = {velocity: new THREE.Vector3(0, 0.35, 0),state: 'rising',targetY: baseHeight + i * heightStep,color: new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex()};fireworks.push(rocket);scene.add(rocket);}, i * delayPerTier);}}function updateFireworks() {for (let i = fireworks.length - 1; i >= 0; i--) {const p = fireworks [i];if (p.userData.state === 'rising') {p.position.add(p.userData.velocity);if (p.position.y >= p.userData.targetY) {createExplosion(p.position, p.userData.color);scene.remove(p);fireworks.splice(i, 1);}} else if (p.userData.state === 'exploded') {p.userData.life -= 0.015;p.position.add(p.userData.velocity);p.material.opacity = Math.max(0, p.userData.life / 2);p.material.transparent = true;p.userData.velocity.y -= 0.002;if (p.userData.life <= 0) {scene.remove(p);fireworks.splice(i, 1);}}}}// ===== 烟花代码结束 =====// 相机camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 动画function animate() {requestAnimationFrame(animate);cars.forEach(car => {if (car.userData.path === 'ring') {car.userData.angle += car.userData.speed;const currentAngle = car.userData.angle % (Math.PI * 2);for (const radialAngle of radialAngles) {if (Math.abs(currentAngle - radialAngle) < 0.05 && Math.random() < 0.01) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'radial';car.userData.angle = radialAngle;car.userData.position = newDirection > 0 ? 0 : radialRoadLength;car.userData.speed = (0.05 + Math.random() * 0.03) * newDirection;car.userData.lane = car.userData.speed > 0 ? laneOffset : -laneOffset;break;}}car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;} else if (car.userData.path === 'radial') {car.userData.position += car.userData.speed;if ((car.userData.speed > 0 && car.userData.position > radialRoadLength) || (car.userData.speed < 0 && car.userData.position < 0)) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'ring';car.userData.angle = car.userData.angle + (Math.PI / 2);car.userData.speed = (0.0015 + Math.random() * 0.0025) * newDirection;car.userData.radius = newDirection > 0 ? outerRadius : innerRadius;}const baseX = Math.cos(car.userData.angle) * (car.userData.position + roadRadius);const baseZ = Math.sin(car.userData.angle) * (car.userData.position + roadRadius);const offsetX = Math.sin(car.userData.angle) * car.userData.lane;const offsetZ = -Math.cos(car.userData.angle) * car.userData.lane;car.position.set(baseX + offsetX, 0.4, baseZ + offsetZ);car.rotation.y = -car.userData.angle + (car.userData.speed > 0 ? 0 : Math.PI);}});// 间歇性发射新烟花if (Math.random() < 0.005) {launchIntermittentFirework();}// 更新烟花状态updateFireworks();scene.rotation.y += 0.002;renderer.render(scene, camera);}// 初始烟花发射launchInitialFireworks();animate();// 按钮点击事件document.getElementById('controls').addEventListener('click', launchTieredFireworks);window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>

显示效果


版本9.0:美化建筑群

<!DOCTYPE html>
<html>
<head><title>3D凯旋门与畅通道路的建筑群 - 细节版</title><style>body { margin: 0; overflow: hidden; font-family: sans-serif; }canvas { display: block; }#controls {position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);padding: 10px 20px;font-size: 16px;cursor: pointer;border: none;background-color: rgba(0, 0, 0, 0.2);color: white;border-radius: 5px;}</style>
</head>
<body><button id="controls">启动阶梯式烟花</button><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 场景 & 相机 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0x87CEEB, 1); // 天蓝色背景document.body.appendChild(renderer.domElement);// 灯光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 地面const groundGeometry = new THREE.PlaneGeometry(2000, 2000);const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 }); // 接近公路的颜色const ground = new THREE.Mesh(groundGeometry, groundMaterial);ground.rotation.x = -Math.PI / 2;scene.add(ground);// 凯旋门材质const archMaterial = new THREE.MeshStandardMaterial({color: 0xFFFFFF, roughness: 0.8, metalness: 0.2}); // 白色凯旋门// 凯旋门主体const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5,  2.2], [5.65, 7.5,  2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3,  3.2], [5.65, 3,  3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 建筑群 =====const frenchRoofMaterial = new THREE.MeshPhongMaterial({color: 0x708090, shininess: 10}); // Slate gray for mansard roofsconst frenchBodyMaterial = new THREE.MeshPhongMaterial({color: 0xE0CDA9, roughness: 0.6}); // Beige stoneconst frenchDetailMaterial = new THREE.MeshPhongMaterial({color: 0xD4AF37, metalness: 0.5}); // Gold accentsconst frenchWindowMaterial = new THREE.MeshBasicMaterial({color: 0xADD8E6}); // Light blue glassfunction createFrenchBuilding(x, z, baseHeight) {const building = new THREE.Group();// Bottom base (rectangular foundation with stone texture effect)const baseGeo = new THREE.BoxGeometry(5, 0.8, 5);const base = new THREE.Mesh(baseGeo, frenchBodyMaterial);base.position.y = 0.4;building.add(base);// Main body (multi-story with classical symmetry)const bodyHeight = baseHeight * 0.7;const bodyGeo = new THREE.BoxGeometry(4.5, bodyHeight, 4.5);const body = new THREE.Mesh(bodyGeo, frenchBodyMaterial);body.position.y = 0.8 + bodyHeight / 2;building.add(body);// Columns (Corinthian-style, simplified with capitals)const columnHeight = bodyHeight;const columnGeo = new THREE.CylinderGeometry(0.2, 0.2, columnHeight, 12);const capitalGeo = new THREE.BoxGeometry(0.4, 0.3, 0.4);const positions = [[-2, -2], [2, -2],[-2, 2], [2, 2]];positions.forEach(pos => {const column = new THREE.Mesh(columnGeo, frenchDetailMaterial);column.position.set(pos[0], 0.8 + columnHeight / 2, pos[1]);building.add(column);const capital = new THREE.Mesh(capitalGeo, frenchDetailMaterial);capital.position.set(pos[0], 0.8 + columnHeight + 0.15, pos[1]);building.add(capital);});// Windows (detailed arched windows on each face, multi per story)const windowGeo = new THREE.BoxGeometry(0.8, 1.5, 0.1);const archGeo = new THREE.TorusGeometry(0.4, 0.1, 8, 12, Math.PI);const frameGeo = new THREE.BoxGeometry(0.9, 1.6, 0.12);const numStories = 3; // Fine detail with multiple storiesfor (let story = 0; story < numStories; story++) {const storyY = 0.8 + (bodyHeight / numStories) * (story + 0.5);for (let side = 0; side < 4; side++) {const angle = side * Math.PI / 2;const windowMesh = new THREE.Mesh(windowGeo, frenchWindowMaterial);windowMesh.position.set(Math.cos(angle) * 2.3, storyY, Math.sin(angle) * 2.3);windowMesh.rotation.y = -angle;building.add(windowMesh);// Arched topconst arch = new THREE.Mesh(archGeo, frenchDetailMaterial);arch.position.set(0, 0.8, 0);arch.rotation.x = Math.PI / 2;windowMesh.add(arch);// Frameconst frame = new THREE.Mesh(frameGeo, frenchDetailMaterial);frame.position.set(0, 0, -0.01);windowMesh.add(frame);// Pane divisions (vertical and horizontal for fine detail)const paneGeo = new THREE.BoxGeometry(0.05, 1.5, 0.05);for (let p = -0.3; p <= 0.3; p += 0.3) {const vPane = new THREE.Mesh(paneGeo, frenchDetailMaterial);vPane.position.set(p, 0, 0.05);windowMesh.add(vPane);}for (let q = -0.6; q <= 0.6; q += 0.6) {const hPane = new THREE.Mesh(paneGeo, frenchDetailMaterial);hPane.rotation.z = Math.PI / 2;hPane.position.set(0, q, 0.05);windowMesh.add(hPane);}}}// Mansard roof (sloped with dormers for French style)const roofHeight = baseHeight * 0.3;const roofGeo = new THREE.BoxGeometry(4.5, roofHeight, 4.5);const roof = new THREE.Mesh(roofGeo, frenchRoofMaterial);roof.position.y = 0.8 + bodyHeight + roofHeight / 2;building.add(roof);// Dormer windows on roof (fine detail)const dormerGeo = new THREE.BoxGeometry(1, 1, 0.5);const dormerRoofGeo = new THREE.ConeGeometry(0.6, 0.8, 4);for (let side = 0; side < 4; side++) {const angle = side * Math.PI / 2;const dormer = new THREE.Mesh(dormerGeo, frenchBodyMaterial);dormer.position.set(Math.cos(angle) * 1.8, 0.8 + bodyHeight + roofHeight / 2, Math.sin(angle) * 1.8);dormer.rotation.y = -angle;building.add(dormer);const dormerRoof = new THREE.Mesh(dormerRoofGeo, frenchRoofMaterial);dormerRoof.position.set(0, 0.9, 0);dormer.add(dormerRoof);const dormerWindow = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.8, 0.1), frenchWindowMaterial);dormerWindow.position.set(0, 0, 0.26);dormer.add(dormerWindow);}// Ornate cornices and friezes (fine decorative bands)const corniceGeo = new THREE.BoxGeometry(5, 0.3, 5);const cornice = new THREE.Mesh(corniceGeo, frenchDetailMaterial);cornice.position.y = 0.8 + bodyHeight;building.add(cornice);building.position.set(x, 0, z);scene.add(building);}const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = [];const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) {onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 6 + Math.random() * 8;createFrenchBuilding(x, z, height);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 6;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);// 移除闪烁效果,直接添加不闪烁的发光环const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);const radialRoadLength = 60;const radialRoadWidth = 5;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeoLeft = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowLeft = new THREE.Mesh(glowGeoLeft, glowMaterial);glowLeft.position.set(road.position.x + Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z - Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowLeft.rotation.y = road.rotation.y;scene.add(glowLeft);const glowGeoRight = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowRight = new THREE.Mesh(glowGeoRight, glowMaterial);glowRight.position.set(road.position.x - Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z + Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowRight.rotation.y = road.rotation.y;scene.add(glowRight);}// ===== 树木 =====const treeTrunkGeometry = new THREE.CylinderGeometry(0.3, 0.3, 2, 8);const treeLeavesGeometry = new THREE.ConeGeometry(1.5, 3, 8);const treeTrunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });const treeLeavesMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });function createTree(x, z) {const trunk = new THREE.Mesh(treeTrunkGeometry, treeTrunkMaterial);trunk.position.set(x, 1, z);scene.add(trunk);const leaves = new THREE.Mesh(treeLeavesGeometry, treeLeavesMaterial);leaves.position.set(x, 3.5, z);scene.add(leaves);}// 在凯旋门和道路之间生成树林const innerForestRadius = 15;const outerForestRadius = roadRadius - roadWidth / 2 - 2;const numForestTrees = 150;for (let i = 0; i < numForestTrees; i++) {const angle = Math.random() * Math.PI * 2;const radius = innerForestRadius + Math.random() * (outerForestRadius - innerForestRadius);const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;createTree(x, z);}// 径向公路两侧生成树木,跳过最内侧一圈const treeSpacing = 6;const radialOffset = radialRoadWidth / 2 + 1;for (let angle of radialAngles) {for (let i = 1; i < radialRoadLength / treeSpacing; i++) { const dist = i * treeSpacing;// 左侧树木const xLeft = Math.cos(angle) * (dist + roadRadius) + Math.sin(angle) * radialOffset;const zLeft = Math.sin(angle) * (dist + roadRadius) - Math.cos(angle) * radialOffset;createTree(xLeft, zLeft);// 右侧树木const xRight = Math.cos(angle) * (dist + roadRadius) - Math.sin(angle) * radialOffset;const zRight = Math.sin(angle) * (dist + roadRadius) + Math.cos(angle) * radialOffset;createTree(xRight, zRight);}}// ===== 优化车流 =====const cars = [];const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const numCars = 100;const laneOffset = radialRoadWidth / 4;const innerRadius = roadRadius - roadWidth / 4;const outerRadius = roadRadius + roadWidth / 4;for (let i = 0; i < numCars; i++) {const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const initialPath = Math.random() < 0.5 ? 'ring' : 'radial';let initialAngle, initialDirection, initialRadius, initialPosition, initialLane;if (initialPath === 'ring') {initialAngle = Math.random() * Math.PI * 2;initialDirection = Math.random() < 0.5 ? 1 : -1;initialRadius = initialDirection === 1 ? outerRadius : innerRadius;car.userData = {path: 'ring',angle: initialAngle,speed: (0.0015 + Math.random() * 0.0025) * initialDirection,radius: initialRadius,lane: 0};} else {initialAngle = radialAngles[Math.floor(Math.random() * numRadials)];initialDirection = Math.random() < 0.5 ? 1 : -1;initialPosition = Math.random() * radialRoadLength;initialLane = initialDirection === 1 ? laneOffset : -laneOffset;car.userData = {path: 'radial',angle: initialAngle,position: initialPosition,speed: (0.05 + Math.random() * 0.03) * initialDirection,lane: initialLane};}scene.add(car);cars.push(car);}// ===== 烟花代码开始 =====const fireworks = [];const particleGeometry = new THREE.SphereGeometry(0.08, 8, 8);const innerCircleRadius = roadRadius - roadWidth / 2;// 烟花爆炸函数function createExplosion(originPosition, color) {const numParticles = 60 + Math.floor(Math.random() * 30);const explosionRadius = 0.5 + Math.random();for (let i = 0; i < numParticles; i++) {const particleMaterial = new THREE.MeshBasicMaterial({ color: color });const particle = new THREE.Mesh(particleGeometry, particleMaterial);particle.position.copy(originPosition);const velocity = new THREE.Vector3((Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius);particle.userData = {velocity: velocity,life: 1.5 + Math.random() * 1,state: 'exploded'};fireworks.push(particle);scene.add(particle);}}// 发射烟花(升空)function launchFirework(startPosition) {const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(startPosition);// 爆炸高度比凯旋门低一些,约12-18const targetHeight = 12 + Math.random() * 6;const color = new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex();rocket.userData = {velocity: new THREE.Vector3(0, 0.35 + Math.random() * 0.25, 0),state: 'rising',targetY: targetHeight,color: color};fireworks.push(rocket);scene.add(rocket);}// 场景初始化时的所有烟花一起升空function launchInitialFireworks() {for (let i = 0; i < 15; i++) {const angle = (i / 15) * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}}// 间歇性发射单个烟花function launchIntermittentFirework() {const angle = Math.random() * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}// 阶梯式环形发射烟花function launchTieredFireworks() {const numTiers = 6;const delayPerTier = 400; // 毫秒const baseHeight = 12;const heightStep = 3; // 阶梯高度for (let i = 0; i < numTiers; i++) {setTimeout(() => {const angle = (i / numTiers) * Math.PI * 2;const radius = innerCircleRadius * 0.8;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(position);rocket.userData = {velocity: new THREE.Vector3(0, 0.35, 0),state: 'rising',targetY: baseHeight + i * heightStep,color: new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex()};fireworks.push(rocket);scene.add(rocket);}, i * delayPerTier);}}function updateFireworks() {for (let i = fireworks.length - 1; i >= 0; i--) {const p = fireworks [i];if (p.userData.state === 'rising') {p.position.add(p.userData.velocity);if (p.position.y >= p.userData.targetY) {createExplosion(p.position, p.userData.color);scene.remove(p);fireworks.splice(i, 1);}} else if (p.userData.state === 'exploded') {p.userData.life -= 0.015;p.position.add(p.userData.velocity);p.material.opacity = Math.max(0, p.userData.life / 2);p.material.transparent = true;p.userData.velocity.y -= 0.002;if (p.userData.life <= 0) {scene.remove(p);fireworks.splice(i, 1);}}}}// ===== 烟花代码结束 =====// 相机camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 动画function animate() {requestAnimationFrame(animate);cars.forEach(car => {if (car.userData.path === 'ring') {car.userData.angle += car.userData.speed;const currentAngle = car.userData.angle % (Math.PI * 2);for (const radialAngle of radialAngles) {if (Math.abs(currentAngle - radialAngle) < 0.05 && Math.random() < 0.01) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'radial';car.userData.angle = radialAngle;car.userData.position = newDirection > 0 ? 0 : radialRoadLength;car.userData.speed = (0.05 + Math.random() * 0.03) * newDirection;car.userData.lane = car.userData.speed > 0 ? laneOffset : -laneOffset;break;}}car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;} else if (car.userData.path === 'radial') {car.userData.position += car.userData.speed;if ((car.userData.speed > 0 && car.userData.position > radialRoadLength) || (car.userData.speed < 0 && car.userData.position < 0)) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'ring';car.userData.angle = car.userData.angle + (Math.PI / 2);car.userData.speed = (0.0015 + Math.random() * 0.0025) * newDirection;car.userData.radius = newDirection > 0 ? outerRadius : innerRadius;}const baseX = Math.cos(car.userData.angle) * (car.userData.position + roadRadius);const baseZ = Math.sin(car.userData.angle) * (car.userData.position + roadRadius);const offsetX = Math.sin(car.userData.angle) * car.userData.lane;const offsetZ = -Math.cos(car.userData.angle) * car.userData.lane;car.position.set(baseX + offsetX, 0.4, baseZ + offsetZ);car.rotation.y = -car.userData.angle + (car.userData.speed > 0 ? 0 : Math.PI);}});// 间歇性发射新烟花if (Math.random() < 0.005) {launchIntermittentFirework();}// 更新烟花状态updateFireworks();scene.rotation.y += 0.002;renderer.render(scene, camera);}// 初始烟花发射launchInitialFireworks();animate();// 按钮点击事件document.getElementById('controls').addEventListener('click', launchTieredFireworks);window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>

效果显示


版本10.0:添加云朵

<!DOCTYPE html>
<html>
<head><title>3D凯旋门与畅通道路的建筑群 - 细节版</title><style>body { margin: 0; overflow: hidden; font-family: sans-serif; }canvas { display: block; }#controls {position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);padding: 10px 20px;font-size: 16px;cursor: pointer;border: none;background-color: rgba(0, 0, 0, 0.2);color: white;border-radius: 5px;}</style>
</head>
<body><button id="controls">启动阶梯式烟花</button><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 场景 & 相机 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0x87CEEB, 1); // 天蓝色背景document.body.appendChild(renderer.domElement);// 灯光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 地面const groundGeometry = new THREE.PlaneGeometry(2000, 2000);const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 }); // 公路灰色const ground = new THREE.Mesh(groundGeometry, groundMaterial);ground.rotation.x = -Math.PI / 2;scene.add(ground);// 凯旋门材质const archMaterial = new THREE.MeshStandardMaterial({color: 0xFFFFFF, roughness: 0.8, metalness: 0.2}); // 白色石材// 凯旋门主体const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5, 2.2], [5.65, 7.5, 2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B}); // 金色雕塑const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3, 3.2], [5.65, 3, 3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 法式建筑群 =====const frenchRoofMaterial = new THREE.MeshPhongMaterial({color: 0x708090, shininess: 10}); // 曼萨德屋顶灰色const frenchBodyMaterial = new THREE.MeshPhongMaterial({color: 0xE0CDA9, roughness: 0.6}); // 米色石材const frenchDetailMaterial = new THREE.MeshPhongMaterial({color: 0xD4AF37, metalness: 0.5}); // 金色装饰const frenchWindowMaterial = new THREE.MeshBasicMaterial({color: 0xADD8E6}); // 浅蓝色玻璃function createFrenchBuilding(x, z, baseHeight) {const building = new THREE.Group();// 底部基座(矩形石材基础)const baseGeo = new THREE.BoxGeometry(5, 0.8, 5);const base = new THREE.Mesh(baseGeo, frenchBodyMaterial);base.position.y = 0.4;building.add(base);// 主体(多层对称结构)const bodyHeight = baseHeight * 0.7;const bodyGeo = new THREE.BoxGeometry(4.5, bodyHeight, 4.5);const body = new THREE.Mesh(bodyGeo, frenchBodyMaterial);body.position.y = 0.8 + bodyHeight / 2;building.add(body);// 柱子(简化科林斯柱式,带柱头)const columnHeight = bodyHeight;const columnGeo = new THREE.CylinderGeometry(0.2, 0.2, columnHeight, 12);const capitalGeo = new THREE.BoxGeometry(0.4, 0.3, 0.4);const positions = [[-2, -2], [2, -2],[-2, 2], [2, 2]];positions.forEach(pos => {const column = new THREE.Mesh(columnGeo, frenchDetailMaterial);column.position.set(pos[0], 0.8 + columnHeight / 2, pos[1]);building.add(column);const capital = new THREE.Mesh(capitalGeo, frenchDetailMaterial);capital.position.set(pos[0], 0.8 + columnHeight + 0.15, pos[1]);building.add(capital);});// 窗户(每层多个拱形窗,细节丰富)const windowGeo = new THREE.BoxGeometry(0.8, 1.5, 0.1);const archGeo = new THREE.TorusGeometry(0.4, 0.1, 8, 12, Math.PI);const frameGeo = new THREE.BoxGeometry(0.9, 1.6, 0.12);const numStories = 3;for (let story = 0; story < numStories; story++) {const storyY = 0.8 + (bodyHeight / numStories) * (story + 0.5);for (let side = 0; side < 4; side++) {const angle = side * Math.PI / 2;const windowMesh = new THREE.Mesh(windowGeo, frenchWindowMaterial);windowMesh.position.set(Math.cos(angle) * 2.3, storyY, Math.sin(angle) * 2.3);windowMesh.rotation.y = -angle;building.add(windowMesh);// 拱形顶部const arch = new THREE.Mesh(archGeo, frenchDetailMaterial);arch.position.set(0, 0.8, 0);arch.rotation.x = Math.PI / 2;windowMesh.add(arch);// 窗框const frame = new THREE.Mesh(frameGeo, frenchDetailMaterial);frame.position.set(0, 0, -0.01);windowMesh.add(frame);// 窗格(垂直和水平分隔)const paneGeo = new THREE.BoxGeometry(0.05, 1.5, 0.05);for (let p = -0.3; p <= 0.3; p += 0.3) {const vPane = new THREE.Mesh(paneGeo, frenchDetailMaterial);vPane.position.set(p, 0, 0.05);windowMesh.add(vPane);}for (let q = -0.6; q <= 0.6; q += 0.6) {const hPane = new THREE.Mesh(paneGeo, frenchDetailMaterial);hPane.rotation.z = Math.PI / 2;hPane.position.set(0, q, 0.05);windowMesh.add(hPane);}}}// 曼萨德屋顶(带天窗)const roofHeight = baseHeight * 0.3;const roofGeo = new THREE.BoxGeometry(4.5, roofHeight, 4.5);const roof = new THREE.Mesh(roofGeo, frenchRoofMaterial);roof.position.y = 0.8 + bodyHeight + roofHeight / 2;building.add(roof);// 屋顶天窗const dormerGeo = new THREE.BoxGeometry(1, 1, 0.5);const dormerRoofGeo = new THREE.ConeGeometry(0.6, 0.8, 4);for (let side = 0; side < 4; side++) {const angle = side * Math.PI / 2;const dormer = new THREE.Mesh(dormerGeo, frenchBodyMaterial);dormer.position.set(Math.cos(angle) * 1.8, 0.8 + bodyHeight + roofHeight / 2, Math.sin(angle) * 1.8);dormer.rotation.y = -angle;building.add(dormer);const dormerRoof = new THREE.Mesh(dormerRoofGeo, frenchRoofMaterial);dormerRoof.position.set(0, 0.9, 0);dormer.add(dormerRoof);const dormerWindow = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.8, 0.1), frenchWindowMaterial);dormerWindow.position.set(0, 0, 0.26);dormer.add(dormerWindow);}// 装饰线脚和檐口const corniceGeo = new THREE.BoxGeometry(5, 0.3, 5);const cornice = new THREE.Mesh(corniceGeo, frenchDetailMaterial);cornice.position.y = 0.8 + bodyHeight;building.add(cornice);building.position.set(x, 0, z);scene.add(building);}const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = [];const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) {onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 6 + Math.random() * 8;createFrenchBuilding(x, z, height);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 6;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);const radialRoadLength = 60;const radialRoadWidth = 5;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeoLeft = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowLeft = new THREE.Mesh(glowGeoLeft, glowMaterial);glowLeft.position.set(road.position.x + Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z - Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowLeft.rotation.y = road.rotation.y;scene.add(glowLeft);const glowGeoRight = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowRight = new THREE.Mesh(glowGeoRight, glowMaterial);glowRight.position.set(road.position.x - Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z + Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowRight.rotation.y = road.rotation.y;scene.add(glowRight);}// ===== 树木 =====const treeTrunkGeometry = new THREE.CylinderGeometry(0.3, 0.3, 2, 8);const treeLeavesGeometry = new THREE.ConeGeometry(1.5, 3, 8);const treeTrunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });const treeLeavesMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });function createTree(x, z) {const trunk = new THREE.Mesh(treeTrunkGeometry, treeTrunkMaterial);trunk.position.set(x, 1, z);scene.add(trunk);const leaves = new THREE.Mesh(treeLeavesGeometry, treeLeavesMaterial);leaves.position.set(x, 3.5, z);scene.add(leaves);}const innerForestRadius = 15;const outerForestRadius = roadRadius - roadWidth / 2 - 2;const numForestTrees = 150;for (let i = 0; i < numForestTrees; i++) {const angle = Math.random() * Math.PI * 2;const radius = innerForestRadius + Math.random() * (outerForestRadius - innerForestRadius);const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;createTree(x, z);}const treeSpacing = 6;const radialOffset = radialRoadWidth / 2 + 1;for (let angle of radialAngles) {for (let i = 1; i < radialRoadLength / treeSpacing; i++) {const dist = i * treeSpacing;const xLeft = Math.cos(angle) * (dist + roadRadius) + Math.sin(angle) * radialOffset;const zLeft = Math.sin(angle) * (dist + roadRadius) - Math.cos(angle) * radialOffset;createTree(xLeft, zLeft);const xRight = Math.cos(angle) * (dist + roadRadius) - Math.sin(angle) * radialOffset;const zRight = Math.sin(angle) * (dist + roadRadius) + Math.cos(angle) * radialOffset;createTree(xRight, zRight);}}// ===== 云朵 =====const cloudMaterial = new THREE.MeshStandardMaterial({ color: 0xF5F5F5, transparent: true, opacity: 0.7, roughness: 0.8, metalness: 0.1 }); // 浅灰色半透明云朵,带光影const clouds = [];const numClouds = 30; // 增加云朵数量function createCloud(x, z, y) {const cloud = new THREE.Group();const cloudGeo = new THREE.SphereGeometry(2, 8, 8); // 低多边形球体const segments = 5 + Math.floor(Math.random() * 4); // 每朵云 5-8 个球体for (let i = 0; i < segments; i++) {const segment = new THREE.Mesh(cloudGeo, cloudMaterial);segment.position.set((Math.random() - 0.5) * 5, // 更宽的偏移(Math.random() - 0.5) * 3, // 更宽的垂直偏移(Math.random() - 0.5) * 5);segment.scale.set(0.7 + Math.random() * 0.8, // 更宽的缩放范围0.5 + Math.random() * 0.5,0.7 + Math.random() * 0.8);cloud.add(segment);}cloud.position.set(x, y, z);cloud.userData = {velocity: new THREE.Vector3((Math.random() - 0.5) * 0.02, 0, (Math.random() - 0.5) * 0.02), // 漂移速度rotationSpeed: (Math.random() - 0.5) * 0.001 // 随机旋转速度};clouds.push(cloud);scene.add(cloud);}// 在宽广区域生成云朵const cloudRadius = 100;for (let i = 0; i < numClouds; i++) {const angle = Math.random() * Math.PI * 2;const radius = 20 + Math.random() * cloudRadius;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;const y = 20 + Math.random() * 30; // 高度 20-50createCloud(x, z, y);}// ===== 优化车流 =====const cars = [];const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const numCars = 100;const laneOffset = radialRoadWidth / 4;const innerRadius = roadRadius - roadWidth / 4;const outerRadius = roadRadius + roadWidth / 4;for (let i = 0; i < numCars; i++) {const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const initialPath = Math.random() < 0.5 ? 'ring' : 'radial';let initialAngle, initialDirection, initialRadius, initialPosition, initialLane;if (initialPath === 'ring') {initialAngle = Math.random() * Math.PI * 2;initialDirection = Math.random() < 0.5 ? 1 : -1;initialRadius = initialDirection === 1 ? outerRadius : innerRadius;car.userData = {path: 'ring',angle: initialAngle,speed: (0.0015 + Math.random() * 0.0025) * initialDirection,radius: initialRadius,lane: 0};} else {initialAngle = radialAngles[Math.floor(Math.random() * numRadials)];initialDirection = Math.random() < 0.5 ? 1 : -1;initialPosition = Math.random() * radialRoadLength;initialLane = initialDirection === 1 ? laneOffset : -laneOffset;car.userData = {path: 'radial',angle: initialAngle,position: initialPosition,speed: (0.05 + Math.random() * 0.03) * initialDirection,lane: initialLane};}scene.add(car);cars.push(car);}// ===== 烟花代码 =====const fireworks = [];const particleGeometry = new THREE.SphereGeometry(0.08, 8, 8);const innerCircleRadius = roadRadius - roadWidth / 2;function createExplosion(originPosition, color) {const numParticles = 60 + Math.floor(Math.random() * 30);const explosionRadius = 0.5 + Math.random();for (let i = 0; i < numParticles; i++) {const particleMaterial = new THREE.MeshBasicMaterial({ color: color });const particle = new THREE.Mesh(particleGeometry, particleMaterial);particle.position.copy(originPosition);const velocity = new THREE.Vector3((Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius);particle.userData = {velocity: velocity,life: 1.5 + Math.random() * 1,state: 'exploded'};fireworks.push(particle);scene.add(particle);}}function launchFirework(startPosition) {const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(startPosition);const targetHeight = 12 + Math.random() * 6;const color = new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex();rocket.userData = {velocity: new THREE.Vector3(0, 0.35 + Math.random() * 0.25, 0),state: 'rising',targetY: targetHeight,color: color};fireworks.push(rocket);scene.add(rocket);}function launchInitialFireworks() {for (let i = 0; i < 15; i++) {const angle = (i / 15) * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}}function launchIntermittentFirework() {const angle = Math.random() * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}function launchTieredFireworks() {const numTiers = 6;const delayPerTier = 400;const baseHeight = 12;const heightStep = 3;for (let i = 0; i < numTiers; i++) {setTimeout(() => {const angle = (i / numTiers) * Math.PI * 2;const radius = innerCircleRadius * 0.8;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(position);rocket.userData = {velocity: new THREE.Vector3(0, 0.35, 0),state: 'rising',targetY: baseHeight + i * heightStep,color: new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex()};fireworks.push(rocket);scene.add(rocket);}, i * delayPerTier);}}function updateFireworks() {for (let i = fireworks.length - 1; i >= 0; i--) {const p = fireworks[i];if (p.userData.state === 'rising') {p.position.add(p.userData.velocity);if (p.position.y >= p.userData.targetY) {createExplosion(p.position, p.userData.color);scene.remove(p);fireworks.splice(i, 1);}} else if (p.userData.state === 'exploded') {p.userData.life -= 0.015;p.position.add(p.userData.velocity);p.material.opacity = Math.max(0, p.userData.life / 2);p.material.transparent = true;p.userData.velocity.y -= 0.002;if (p.userData.life <= 0) {scene.remove(p);fireworks.splice(i, 1);}}}}// 相机camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 动画function animate() {requestAnimationFrame(animate);// 更新云朵位置和旋转clouds.forEach(cloud => {cloud.position.add(cloud.userData.velocity);cloud.rotation.y += cloud.userData.rotationSpeed; // 微小旋转// 云朵超出边界时回绕if (cloud.position.x > cloudRadius) cloud.position.x -= 2 * cloudRadius;if (cloud.position.x < -cloudRadius) cloud.position.x += 2 * cloudRadius;if (cloud.position.z > cloudRadius) cloud.position.z -= 2 * cloudRadius;if (cloud.position.z < -cloudRadius) cloud.position.z += 2 * cloudRadius;});// 更新车辆cars.forEach(car => {if (car.userData.path === 'ring') {car.userData.angle += car.userData.speed;const currentAngle = car.userData.angle % (Math.PI * 2);for (const radialAngle of radialAngles) {if (Math.abs(currentAngle - radialAngle) < 0.05 && Math.random() < 0.01) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'radial';car.userData.angle = radialAngle;car.userData.position = newDirection > 0 ? 0 : radialRoadLength;car.userData.speed = (0.05 + Math.random() * 0.03) * newDirection;car.userData.lane = car.userData.speed > 0 ? laneOffset : -laneOffset;break;}}car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;} else if (car.userData.path === 'radial') {car.userData.position += car.userData.speed;if ((car.userData.speed > 0 && car.userData.position > radialRoadLength) || (car.userData.speed < 0 && car.userData.position < 0)) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'ring';car.userData.angle = car.userData.angle + (Math.PI / 2);car.userData.speed = (0.0015 + Math.random() * 0.0025) * newDirection;car.userData.radius = newDirection > 0 ? outerRadius : innerRadius;}const baseX = Math.cos(car.userData.angle) * (car.userData.position + roadRadius);const baseZ = Math.sin(car.userData.angle) * (car.userData.position + roadRadius);const offsetX = Math.sin(car.userData.angle) * car.userData.lane;const offsetZ = -Math.cos(car.userData.angle) * car.userData.lane;car.position.set(baseX + offsetX, 0.4, baseZ + offsetZ);car.rotation.y = -car.userData.angle + (car.userData.speed > 0 ? 0 : Math.PI);}});// 间歇性发射烟花if (Math.random() < 0.005) {launchIntermittentFirework();}// 更新烟花updateFireworks();// 场景缓慢旋转scene.rotation.y += 0.002;renderer.render(scene, camera);}// 初始烟花launchInitialFireworks();animate();// 按钮事件document.getElementById('controls').addEventListener('click', launchTieredFireworks);// 窗口大小调整window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>

显示效果


版本11.0:添加动态热气球

<!DOCTYPE html>
<html>
<head><title>3D凯旋门与畅通道路的建筑群 - 细节版</title><style>body { margin: 0; overflow: hidden; font-family: sans-serif; }canvas { display: block; }#controls {position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);padding: 10px 20px;font-size: 16px;cursor: pointer;border: none;background-color: rgba(0, 0, 0, 0.2);color: white;border-radius: 5px;}</style>
</head>
<body><button id="controls">启动阶梯式烟花</button><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script><script>// 场景 & 相机 & 渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0x87CEEB, 1); // 天蓝色背景document.body.appendChild(renderer.domElement);// 灯光scene.add(new THREE.AmbientLight(0x404040, 1.2));const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(30, 50, 30);scene.add(directionalLight);// 地面const groundGeometry = new THREE.PlaneGeometry(2000, 2000);const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 }); // 公路灰色const ground = new THREE.Mesh(groundGeometry, groundMaterial);ground.rotation.x = -Math.PI / 2;scene.add(ground);// 凯旋门材质const archMaterial = new THREE.MeshStandardMaterial({color: 0xFFFFFF, roughness: 0.8, metalness: 0.2}); // 白色石材// 凯旋门主体const base = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);base.position.y = 0.75; scene.add(base);const pillarGeometry = new THREE.BoxGeometry(2.2, 12, 2.2);const pillars = [[-5.65, 7.5, -2.2], [5.65, 7.5, -2.2],[-5.65, 7.5, 2.2], [5.65, 7.5, 2.2]];pillars.forEach(p => {const m = new THREE.Mesh(pillarGeometry, archMaterial);m.position.set(...p);scene.add(m);});const midBeam = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1, 6.6), archMaterial);midBeam.position.y = 10; scene.add(midBeam);const attic = new THREE.Mesh(new THREE.BoxGeometry(13.5, 1.5, 6.6), archMaterial);attic.position.y = 13.5; scene.add(attic);const quadrigaMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B}); // 金色雕塑const quadriga = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), quadrigaMaterial);quadriga.position.set(0, 14.5, 0); scene.add(quadriga);const friezeGeometry = new THREE.BoxGeometry(12.5, 0.4, 0.4);const frieze = new THREE.Mesh(friezeGeometry, archMaterial);frieze.position.set(0, 12.8, 3.1); scene.add(frieze);const friezeBack = frieze.clone(); friezeBack.position.z = -3.1; scene.add(friezeBack);const friezeDetailGeometry = new THREE.BoxGeometry(0.5, 0.3, 0.2);for (let i = -5; i <= 5; i += 2) {const detail = new THREE.Mesh(friezeDetailGeometry, quadrigaMaterial);detail.position.set(i, 12.8, 3.2); scene.add(detail);const detailBack = detail.clone(); detailBack.position.z = -3.2; scene.add(detailBack);}const sculptureGeometry = new THREE.BoxGeometry(2.2, 3, 0.3);const sculptureMaterial = new THREE.MeshPhongMaterial({color: 0xB8860B});const sculptures = [[-5.65, 3, -3.2], [5.65, 3, -3.2],[-5.65, 3, 3.2], [5.65, 3, 3.2]];sculptures.forEach(p => {const s = new THREE.Mesh(sculptureGeometry, sculptureMaterial);s.position.set(...p); scene.add(s);});// ===== 法式建筑群 =====const frenchRoofMaterial = new THREE.MeshPhongMaterial({color: 0x708090, shininess: 10}); // 曼萨德屋顶灰色const frenchBodyMaterial = new THREE.MeshPhongMaterial({color: 0xE0CDA9, roughness: 0.6}); // 米色石材const frenchDetailMaterial = new THREE.MeshPhongMaterial({color: 0xD4AF37, metalness: 0.5}); // 金色装饰const frenchWindowMaterial = new THREE.MeshBasicMaterial({color: 0xADD8E6}); // 浅蓝色玻璃function createFrenchBuilding(x, z, baseHeight) {const building = new THREE.Group();// 底部基座(矩形石材基础)const baseGeo = new THREE.BoxGeometry(5, 0.8, 5);const base = new THREE.Mesh(baseGeo, frenchBodyMaterial);base.position.y = 0.4;building.add(base);// 主体(多层对称结构)const bodyHeight = baseHeight * 0.7;const bodyGeo = new THREE.BoxGeometry(4.5, bodyHeight, 4.5);const body = new THREE.Mesh(bodyGeo, frenchBodyMaterial);body.position.y = 0.8 + bodyHeight / 2;building.add(body);// 柱子(简化科林斯柱式,带柱头)const columnHeight = bodyHeight;const columnGeo = new THREE.CylinderGeometry(0.2, 0.2, columnHeight, 12);const capitalGeo = new THREE.BoxGeometry(0.4, 0.3, 0.4);const positions = [[-2, -2], [2, -2],[-2, 2], [2, 2]];positions.forEach(pos => {const column = new THREE.Mesh(columnGeo, frenchDetailMaterial);column.position.set(pos[0], 0.8 + columnHeight / 2, pos[1]);building.add(column);const capital = new THREE.Mesh(capitalGeo, frenchDetailMaterial);capital.position.set(pos[0], 0.8 + columnHeight + 0.15, pos[1]);building.add(capital);});// 窗户(每层多个拱形窗,细节丰富)const windowGeo = new THREE.BoxGeometry(0.8, 1.5, 0.1);const archGeo = new THREE.TorusGeometry(0.4, 0.1, 8, 12, Math.PI);const frameGeo = new THREE.BoxGeometry(0.9, 1.6, 0.12);const numStories = 3;for (let story = 0; story < numStories; story++) {const storyY = 0.8 + (bodyHeight / numStories) * (story + 0.5);for (let side = 0; side < 4; side++) {const angle = side * Math.PI / 2;const windowMesh = new THREE.Mesh(windowGeo, frenchWindowMaterial);windowMesh.position.set(Math.cos(angle) * 2.3, storyY, Math.sin(angle) * 2.3);windowMesh.rotation.y = -angle;building.add(windowMesh);// 拱形顶部const arch = new THREE.Mesh(archGeo, frenchDetailMaterial);arch.position.set(0, 0.8, 0);arch.rotation.x = Math.PI / 2;windowMesh.add(arch);// 窗框const frame = new THREE.Mesh(frameGeo, frenchDetailMaterial);frame.position.set(0, 0, -0.01);windowMesh.add(frame);// 窗格(垂直和水平分隔)const paneGeo = new THREE.BoxGeometry(0.05, 1.5, 0.05);for (let p = -0.3; p <= 0.3; p += 0.3) {const vPane = new THREE.Mesh(paneGeo, frenchDetailMaterial);vPane.position.set(p, 0, 0.05);windowMesh.add(vPane);}for (let q = -0.6; q <= 0.6; q += 0.6) {const hPane = new THREE.Mesh(paneGeo, frenchDetailMaterial);hPane.rotation.z = Math.PI / 2;hPane.position.set(0, q, 0.05);windowMesh.add(hPane);}}}// 曼萨德屋顶(带天窗)const roofHeight = baseHeight * 0.3;const roofGeo = new THREE.BoxGeometry(4.5, roofHeight, 4.5);const roof = new THREE.Mesh(roofGeo, frenchRoofMaterial);roof.position.y = 0.8 + bodyHeight + roofHeight / 2;building.add(roof);// 屋顶天窗const dormerGeo = new THREE.BoxGeometry(1, 1, 0.5);const dormerRoofGeo = new THREE.ConeGeometry(0.6, 0.8, 4);for (let side = 0; side < 4; side++) {const angle = side * Math.PI / 2;const dormer = new THREE.Mesh(dormerGeo, frenchBodyMaterial);dormer.position.set(Math.cos(angle) * 1.8, 0.8 + bodyHeight + roofHeight / 2, Math.sin(angle) * 1.8);dormer.rotation.y = -angle;building.add(dormer);const dormerRoof = new THREE.Mesh(dormerRoofGeo, frenchRoofMaterial);dormerRoof.position.set(0, 0.9, 0);dormer.add(dormerRoof);const dormerWindow = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.8, 0.1), frenchWindowMaterial);dormerWindow.position.set(0, 0, 0.26);dormer.add(dormerWindow);}// 装饰线脚和檐口const corniceGeo = new THREE.BoxGeometry(5, 0.3, 5);const cornice = new THREE.Mesh(corniceGeo, frenchDetailMaterial);cornice.position.y = 0.8 + bodyHeight;building.add(cornice);building.position.set(x, 0, z);scene.add(building);}const rings = 5, baseRadius = 25, ringSpacing = 10;const radialAngles = [];const numRadials = 8;for (let i = 0; i < numRadials; i++) {radialAngles.push((i / numRadials) * Math.PI * 2);}for (let ring = 2; ring <= rings; ring++) {const radius = baseRadius + (ring - 1) * ringSpacing;const numBuildings = 8 + ring * 4;for (let i = 0; i < numBuildings; i++) {const angle = (i / numBuildings) * Math.PI * 2;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;let onRadialRoad = false;for (const roadAngle of radialAngles) {const dx = x;const dz = z;const distFromRoadCenter = Math.abs(Math.sin(roadAngle) * dx - Math.cos(roadAngle) * dz);if (distFromRoadCenter < 6) {onRadialRoad = true;break;}}if (onRadialRoad) continue;const height = 6 + Math.random() * 8;createFrenchBuilding(x, z, height);}}// ===== 道路 =====const roadMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });const glowMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 1 });const roadRadius = baseRadius - 3;const roadWidth = 6;const roadGeometry = new THREE.RingGeometry(roadRadius - roadWidth / 2, roadRadius + roadWidth / 2, 128);const roadMesh = new THREE.Mesh(roadGeometry, roadMaterial);roadMesh.rotation.x = -Math.PI / 2;scene.add(roadMesh);const glowRingGeometry = new THREE.RingGeometry(roadRadius + roadWidth / 2, roadRadius + roadWidth / 2 + 0.3, 128);const glowRingMesh = new THREE.Mesh(glowRingGeometry, glowMaterial);glowRingMesh.rotation.x = -Math.PI / 2;scene.add(glowRingMesh);const radialRoadLength = 60;const radialRoadWidth = 5;for (let angle of radialAngles) {const roadGeo = new THREE.BoxGeometry(radialRoadLength, 0.1, radialRoadWidth);const road = new THREE.Mesh(roadGeo, roadMaterial);road.position.set(Math.cos(angle) * (radialRoadLength / 2 + roadRadius), 0.05, Math.sin(angle) * (radialRoadLength / 2 + roadRadius));road.rotation.y = -angle;scene.add(road);const glowGeoLeft = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowLeft = new THREE.Mesh(glowGeoLeft, glowMaterial);glowLeft.position.set(road.position.x + Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z - Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowLeft.rotation.y = road.rotation.y;scene.add(glowLeft);const glowGeoRight = new THREE.BoxGeometry(radialRoadLength, 0.05, 0.2);const glowRight = new THREE.Mesh(glowGeoRight, glowMaterial);glowRight.position.set(road.position.x - Math.sin(angle) * (radialRoadWidth / 2 + 0.1), 0.06, road.position.z + Math.cos(angle) * (radialRoadWidth / 2 + 0.1));glowRight.rotation.y = road.rotation.y;scene.add(glowRight);}// ===== 树木 =====const treeTrunkGeometry = new THREE.CylinderGeometry(0.3, 0.3, 2, 8);const treeLeavesGeometry = new THREE.ConeGeometry(1.5, 3, 8);const treeTrunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });const treeLeavesMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });function createTree(x, z) {const trunk = new THREE.Mesh(treeTrunkGeometry, treeTrunkMaterial);trunk.position.set(x, 1, z);scene.add(trunk);const leaves = new THREE.Mesh(treeLeavesGeometry, treeLeavesMaterial);leaves.position.set(x, 3.5, z);scene.add(leaves);}const innerForestRadius = 15;const outerForestRadius = roadRadius - roadWidth / 2 - 2;const numForestTrees = 150;for (let i = 0; i < numForestTrees; i++) {const angle = Math.random() * Math.PI * 2;const radius = innerForestRadius + Math.random() * (outerForestRadius - innerForestRadius);const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;createTree(x, z);}const treeSpacing = 6;const radialOffset = radialRoadWidth / 2 + 1;for (let angle of radialAngles) {for (let i = 1; i < radialRoadLength / treeSpacing; i++) {const dist = i * treeSpacing;const xLeft = Math.cos(angle) * (dist + roadRadius) + Math.sin(angle) * radialOffset;const zLeft = Math.sin(angle) * (dist + roadRadius) - Math.cos(angle) * radialOffset;createTree(xLeft, zLeft);const xRight = Math.cos(angle) * (dist + roadRadius) - Math.sin(angle) * radialOffset;const zRight = Math.sin(angle) * (dist + roadRadius) + Math.cos(angle) * radialOffset;createTree(xRight, zRight);}}// ===== 云朵 =====const cloudMaterial = new THREE.MeshStandardMaterial({ color: 0xF5F5F5, transparent: true, opacity: 0.7, roughness: 0.8, metalness: 0.1 }); // 浅灰色半透明云朵,带光影const clouds = [];const numClouds = 30; // 增加云朵数量function createCloud(x, z, y) {const cloud = new THREE.Group();const cloudGeo = new THREE.SphereGeometry(2, 8, 8); // 低多边形球体const segments = 5 + Math.floor(Math.random() * 4); // 每朵云 5-8 个球体for (let i = 0; i < segments; i++) {const segment = new THREE.Mesh(cloudGeo, cloudMaterial);segment.position.set((Math.random() - 0.5) * 5, // 更宽的偏移(Math.random() - 0.5) * 3, // 更宽的垂直偏移(Math.random() - 0.5) * 5);segment.scale.set(0.7 + Math.random() * 0.8, // 更宽的缩放范围0.5 + Math.random() * 0.5,0.7 + Math.random() * 0.8);cloud.add(segment);}cloud.position.set(x, y, z);cloud.userData = {velocity: new THREE.Vector3((Math.random() - 0.5) * 0.02, 0, (Math.random() - 0.5) * 0.02), // 漂移速度rotationSpeed: (Math.random() - 0.5) * 0.001 // 随机旋转速度};clouds.push(cloud);scene.add(cloud);}// 在宽广区域生成云朵const cloudRadius = 100;for (let i = 0; i < numClouds; i++) {const angle = Math.random() * Math.PI * 2;const radius = 20 + Math.random() * cloudRadius;const x = Math.cos(angle) * radius;const z = Math.sin(angle) * radius;const y = 20 + Math.random() * 30; // 高度 20-50createCloud(x, z, y);}// ===== 热气球 =====const balloonMaterial = new THREE.MeshStandardMaterial({ roughness: 0.4, metalness: 0.2 });const basketMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513, roughness: 0.6 });const ropeMaterial = new THREE.LineBasicMaterial({ color: 0x333333, linewidth: 2 });const graffitiMaterial = new THREE.MeshBasicMaterial({ color: 0xFFFFFF, transparent: true, opacity: 0.8 });const hotAirBalloons = [];const numBalloons = 8; // 增加到8个热气球function createStarShape() {const shape = new THREE.Shape();const outerRadius = 0.8;const innerRadius = 0.4;for (let i = 0; i < 10; i++) {const angle = (i / 10) * Math.PI * 2;const radius = i % 2 === 0 ? outerRadius : innerRadius;const x = Math.cos(angle) * radius;const y = Math.sin(angle) * radius;if (i === 0) shape.moveTo(x, y);else shape.lineTo(x, y);}shape.closePath();return shape;}function createHotAirBalloon() {const balloon = new THREE.Group();// 气球主体(球体,精细立体)const balloonGeo = new THREE.SphereGeometry(3, 32, 32);const balloonMesh = new THREE.Mesh(balloonGeo, balloonMaterial.clone());const hue = Math.random() < 0.5 ? Math.random() * 60 / 360 : (180 + Math.random() * 120) / 360; // 橙黄或蓝紫balloonMesh.material.color.setHSL(hue, 0.5 + Math.random() * 0.2, 0.6 + Math.random() * 0.2);balloonMesh.position.y = 4;balloon.add(balloonMesh);// 刻度纹理(垂直和水平线)for (let i = 0; i < 12; i++) { // 12条经线const linePoints = [new THREE.Vector3(0, -3, 0),new THREE.Vector3(0, 3, 0)];const lineGeo = new THREE.BufferGeometry().setFromPoints(linePoints);const line = new THREE.Line(lineGeo, ropeMaterial);line.rotation.y = i * (Math.PI / 6);balloonMesh.add(line);}for (let y = -2; y <= 2; y += 1) { // 4条纬线if (y === 0) continue;const radius = Math.sqrt(9 - y * y);const circlePoints = [];for (let i = 0; i <= 32; i++) {const angle = (i / 32) * Math.PI * 2;circlePoints.push(new THREE.Vector3(Math.cos(angle) * radius, y, Math.sin(angle) * radius));}const circleGeo = new THREE.BufferGeometry().setFromPoints(circlePoints);const circle = new THREE.Line(circleGeo, ropeMaterial);balloonMesh.add(circle);}// 涂鸦(圆形或星形)const numGraffiti = 3 + Math.floor(Math.random() * 3);for (let i = 0; i < numGraffiti; i++) {const isCircle = Math.random() < 0.5;const graffitiGeo = isCircle ? new THREE.CircleGeometry(0.5 + Math.random() * 0.5, 8) : new THREE.ShapeGeometry(createStarShape());const graffiti = new THREE.Mesh(graffitiGeo, graffitiMaterial.clone());if (!isCircle) graffiti.material.color.setHex(0xCCCCCC);const theta = Math.random() * Math.PI;const phi = Math.random() * Math.PI * 2;const r = 3.01; // 稍超出球体表面graffiti.position.set(r * Math.sin(theta) * Math.cos(phi),r * Math.cos(theta),r * Math.sin(theta) * Math.sin(phi));graffiti.lookAt(balloonMesh.position);balloonMesh.add(graffiti);}// 篮子const basketGeo = new THREE.BoxGeometry(1.5, 1, 1.5);const basket = new THREE.Mesh(basketGeo, basketMaterial);basket.position.y = -1;balloon.add(basket);// 连接绳const ropeGeo = new THREE.CylinderGeometry(0.05, 0.05, 5, 8);for (let i = 0; i < 4; i++) {const rope = new THREE.Mesh(ropeGeo, basketMaterial);const angle = i * (Math.PI / 2);rope.position.set(Math.cos(angle) * 0.6, -0.5, Math.sin(angle) * 0.6);balloon.add(rope);}// 随机初始角度和高度balloon.userData = {angle: Math.random() * Math.PI * 2,speed: 0.001 + Math.random() * 0.001,trajectoryRadius: 50 + Math.random() * 20,height: 20 + Math.random() * 30};hotAirBalloons.push(balloon);scene.add(balloon);}// 生成热气球for (let i = 0; i < numBalloons; i++) {createHotAirBalloon();}// ===== 优化车流 =====const cars = [];const carGeometry = new THREE.BoxGeometry(1.5, 0.7, 0.8);const numCars = 100;const laneOffset = radialRoadWidth / 4;const innerRadius = roadRadius - roadWidth / 4;const outerRadius = roadRadius + roadWidth / 4;for (let i = 0; i < numCars; i++) {const carMaterial = new THREE.MeshStandardMaterial({color: new THREE.Color(Math.random(), Math.random(), Math.random()),emissive: new THREE.Color(Math.random(), Math.random(), Math.random()),emissiveIntensity: 0.8});const car = new THREE.Mesh(carGeometry, carMaterial);const initialPath = Math.random() < 0.5 ? 'ring' : 'radial';let initialAngle, initialDirection, initialRadius, initialPosition, initialLane;if (initialPath === 'ring') {initialAngle = Math.random() * Math.PI * 2;initialDirection = Math.random() < 0.5 ? 1 : -1;initialRadius = initialDirection === 1 ? outerRadius : innerRadius;car.userData = {path: 'ring',angle: initialAngle,speed: (0.0015 + Math.random() * 0.0025) * initialDirection,radius: initialRadius,lane: 0};} else {initialAngle = radialAngles[Math.floor(Math.random() * numRadials)];initialDirection = Math.random() < 0.5 ? 1 : -1;initialPosition = Math.random() * radialRoadLength;initialLane = initialDirection === 1 ? laneOffset : -laneOffset;car.userData = {path: 'radial',angle: initialAngle,position: initialPosition,speed: (0.05 + Math.random() * 0.03) * initialDirection,lane: initialLane};}scene.add(car);cars.push(car);}// ===== 烟花代码 =====const fireworks = [];const particleGeometry = new THREE.SphereGeometry(0.08, 8, 8);const innerCircleRadius = roadRadius - roadWidth / 2;function createExplosion(originPosition, color) {const numParticles = 60 + Math.floor(Math.random() * 30);const explosionRadius = 0.5 + Math.random();for (let i = 0; i < numParticles; i++) {const particleMaterial = new THREE.MeshBasicMaterial({ color: color });const particle = new THREE.Mesh(particleGeometry, particleMaterial);particle.position.copy(originPosition);const velocity = new THREE.Vector3((Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius,(Math.random() - 0.5) * explosionRadius);particle.userData = {velocity: velocity,life: 1.5 + Math.random() * 1,state: 'exploded'};fireworks.push(particle);scene.add(particle);}}function launchFirework(startPosition) {const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(startPosition);const targetHeight = 12 + Math.random() * 6;const color = new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex();rocket.userData = {velocity: new THREE.Vector3(0, 0.35 + Math.random() * 0.25, 0),state: 'rising',targetY: targetHeight,color: color};fireworks.push(rocket);scene.add(rocket);}function launchInitialFireworks() {for (let i = 0; i < 15; i++) {const angle = (i / 15) * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}}function launchIntermittentFirework() {const angle = Math.random() * Math.PI * 2;const radius = Math.random() * innerCircleRadius;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);launchFirework(position);}function launchTieredFireworks() {const numTiers = 6;const delayPerTier = 400;const baseHeight = 12;const heightStep = 3;for (let i = 0; i < numTiers; i++) {setTimeout(() => {const angle = (i / numTiers) * Math.PI * 2;const radius = innerCircleRadius * 0.8;const position = new THREE.Vector3(Math.cos(angle) * radius, 0.1, Math.sin(angle) * radius);const rocketMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });const rocket = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.1), rocketMaterial);rocket.position.copy(position);rocket.userData = {velocity: new THREE.Vector3(0, 0.35, 0),state: 'rising',targetY: baseHeight + i * heightStep,color: new THREE.Color().setHSL(Math.random(), 1, 0.5).getHex()};fireworks.push(rocket);scene.add(rocket);}, i * delayPerTier);}}function updateFireworks() {for (let i = fireworks.length - 1; i >= 0; i--) {const p = fireworks[i];if (p.userData.state === 'rising') {p.position.add(p.userData.velocity);if (p.position.y >= p.userData.targetY) {createExplosion(p.position, p.userData.color);scene.remove(p);fireworks.splice(i, 1);}} else if (p.userData.state === 'exploded') {p.userData.life -= 0.015;p.position.add(p.userData.velocity);p.material.opacity = Math.max(0, p.userData.life / 2);p.material.transparent = true;p.userData.velocity.y -= 0.002;if (p.userData.life <= 0) {scene.remove(p);fireworks.splice(i, 1);}}}}// 相机camera.position.set(45, 45, 45);camera.lookAt(0, 7.5, 0);// 动画function animate() {requestAnimationFrame(animate);// 更新云朵位置和旋转clouds.forEach(cloud => {cloud.position.add(cloud.userData.velocity);cloud.rotation.y += cloud.userData.rotationSpeed; // 微小旋转// 云朵超出边界时回绕if (cloud.position.x > cloudRadius) cloud.position.x -= 2 * cloudRadius;if (cloud.position.x < -cloudRadius) cloud.position.x += 2 * cloudRadius;if (cloud.position.z > cloudRadius) cloud.position.z -= 2 * cloudRadius;if (cloud.position.z < -cloudRadius) cloud.position.z += 2 * cloudRadius;});// 更新热气球位置hotAirBalloons.forEach(balloon => {balloon.userData.angle += balloon.userData.speed;const trajRadius = balloon.userData.trajectoryRadius;balloon.position.x = Math.cos(balloon.userData.angle) * trajRadius;balloon.position.z = Math.sin(balloon.userData.angle) * trajRadius;balloon.position.y = balloon.userData.height + Math.sin(balloon.userData.angle * 5) * 1; // 轻微上下浮动});// 更新车辆cars.forEach(car => {if (car.userData.path === 'ring') {car.userData.angle += car.userData.speed;const currentAngle = car.userData.angle % (Math.PI * 2);for (const radialAngle of radialAngles) {if (Math.abs(currentAngle - radialAngle) < 0.05 && Math.random() < 0.01) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'radial';car.userData.angle = radialAngle;car.userData.position = newDirection > 0 ? 0 : radialRoadLength;car.userData.speed = (0.05 + Math.random() * 0.03) * newDirection;car.userData.lane = car.userData.speed > 0 ? laneOffset : -laneOffset;break;}}car.position.set(Math.cos(car.userData.angle) * car.userData.radius,0.4,Math.sin(car.userData.angle) * car.userData.radius);car.rotation.y = -car.userData.angle + Math.PI / 2;} else if (car.userData.path === 'radial') {car.userData.position += car.userData.speed;if ((car.userData.speed > 0 && car.userData.position > radialRoadLength) || (car.userData.speed < 0 && car.userData.position < 0)) {const newDirection = car.userData.speed > 0 ? 1 : -1;car.userData.path = 'ring';car.userData.angle = car.userData.angle + (Math.PI / 2);car.userData.speed = (0.0015 + Math.random() * 0.0025) * newDirection;car.userData.radius = newDirection > 0 ? outerRadius : innerRadius;}const baseX = Math.cos(car.userData.angle) * (car.userData.position + roadRadius);const baseZ = Math.sin(car.userData.angle) * (car.userData.position + roadRadius);const offsetX = Math.sin(car.userData.angle) * car.userData.lane;const offsetZ = -Math.cos(car.userData.angle) * car.userData.lane;car.position.set(baseX + offsetX, 0.4, baseZ + offsetZ);car.rotation.y = -car.userData.angle + (car.userData.speed > 0 ? 0 : Math.PI);}});// 间歇性发射烟花if (Math.random() < 0.005) {launchIntermittentFirework();}// 更新烟花updateFireworks();// 场景缓慢旋转scene.rotation.y += 0.002;renderer.render(scene, camera);}// 初始烟花launchInitialFireworks();animate();// 按钮事件document.getElementById('controls').addEventListener('click', launchTieredFireworks);// 窗口大小调整window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>

显示效果


http://www.lryc.cn/news/625586.html

相关文章:

  • Leetcode 343. 整数拆分 动态规划
  • C++入门自学Day14-- Stack和Queue的自实现(适配器)
  • 神经网络中的那些关键设计:从输入输出到参数更新
  • 面试题储备-MQ篇 3-说说你对Kafka的理解
  • 图论\dp 两题
  • 设计模式笔记_行为型_命令模式
  • 【React】事件绑定和组件基础使用
  • 从线性回归到神经网络到自注意力机制 —— 激活函数与参数的演进
  • java基础(十二)redis 日志机制以及常见问题
  • 2025年12大AI测试自动化工具
  • 多模态大模型应用落地:从图文生成到音视频交互的技术选型与实践
  • 【模块系列】STM32W25Q64
  • TDengine IDMP 运维指南(4. 使用 Docker 部署)
  • 第六天~提取Arxml中CAN物理通道信息CANChannel--Physical Channel
  • 5. Dataloader 自定义数据集制作
  • C语言基础:(十八)C语言内存函数
  • java17学习笔记-Deprecate the Applet API for Removal
  • 算法——质数筛法
  • yolov5s.onnx转rk模型以及相关使用详细教程
  • 假设检验的原理
  • python的社区互助养老系统
  • word如何转换为pdf
  • MFC中使用EXCEL的方法之一
  • ios使用saveVideoToPhotosAlbum 保存视频失败提示 invalid video
  • 基于单片机的智能声控窗帘
  • 437. 路径总和 III
  • Qt 插件开发全解析:从接口定义,插件封装,插件调用到插件间的通信
  • SWMM排水管网水力、水质建模及在海绵与水环境中的应用
  • 第5章 高级状态管理
  • 结合BI多维度异常分析(日期-> 商家/渠道->日期(商家/渠道))