实用指南:【HTML】3D动态凯旋门
目录

版本1.0:简易版本
3D凯旋门与扩展建筑群(斜角俯视)
body { margin: 0; }
canvas { display: block; }
// 设置场景
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 {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
效果图:

版本2.0:建筑渲染
3D凯旋门与多彩建筑群(斜角俯视)
body { margin: 0; }
canvas { display: block; }
// 设置场景
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); // 修正:移除 .demo
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 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 {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
效果图

版本3.0:优化建筑群
3D超大精细凯旋门与多彩建筑群(斜角俯视)
body { margin: 0; }
canvas { display: block; }
// 设置场景
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);
// 顶部attic
const 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 {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
图示效果

版本4.0:增加公路和车流
- 在原本凯旋门和建筑群的基础上,加上 环形公路 + 放射状公路 + 科技感灯光 + 内外环对向车流。
3D超大精细凯旋门与多彩建筑群(斜角俯视)
body { margin: 0; }
canvas { display: block; }
// 场景 & 相机 & 渲染器
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 {
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 {
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);
});
效果图

版本5.0:去除压在公路上的建筑
优化点
公路(环形和放射状)完全没有建筑压住
所有道路都畅通可见
建筑分布更合理
3D凯旋门与畅通道路的建筑群
body { margin: 0; }
canvas { display: block; }
// 场景 & 相机 & 渲染器
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 {
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 {
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);
});
效果图

版本6.0:优化车流群
环形公路:双向环形车流(内圈逆时针,外圈顺时针)
放射状公路:每条都有双向车流,到端点自动掉头
建筑:完全避开公路,不会挡路
统一的交通逻辑:汽车不再有两个完全独立的交通环路,而是具有可以是 或 的属性。这个单一的动画循环可以处理所有汽车,并允许动态路径切换。
userData.path'ring''radial'无缝路径转换:
径向到环形:当径向道路上的汽车到达终点 () 或中心 () 时,它会自动切换到 。然后,它计算出一个新的角度和速度,以继续沿环形道路行驶,从而创建平滑过渡。
car.userData.position > radialRoadLengthcar.userData.position < 0path'ring'环形到径向:当环路上的汽车经过径向道路交叉口时,它有很小的机会切换到径向道路。这是通过防止所有汽车同时改变路径并确保动态、逼真的流动进行控制的。
Math.random() < 0.01改进的代码结构:汽车创建循环更加集中。创建汽车并给出初始路径和位置。然后,单个动画循环管理所有汽车的状态和运动,无论其当前路径如何。
动态摄像机:场景现在围绕原点旋转 ()。这让用户更好地感受到一个充满活力、不断运动的充满活力的城市。
scene.rotation.y += 0.002
3D凯旋门与畅通道路的建筑群 - 优化版
body { margin: 0; overflow: hidden; }
canvas { display: block; }
// 场景 & 相机 & 渲染器
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 {
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 {
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 ? 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 ? 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);
});
效果图

版本7.0:添加烟花效果
3D凯旋门与畅通道路的建筑群 - 天蓝色背景版
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;
}
// 场景 & 相机 & 渲染器
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 {
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 {
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 {
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 ? 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 ? 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() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
显示效果

版本8.0:添加树木
背景颜色: 天蓝色
地面颜色: 略浅于公路的深灰色
凯旋门颜色: 黄褐色
树木: 在环形道路内侧和放射状公路两侧都增加了树木
放射状树木: 去除了最靠近环形公路的那一圈树木
3D凯旋门与畅通道路的建筑群 - 最终版
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;
}
// 场景 & 相机 & 渲染器
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 {
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 {
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 {
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 ? 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 ? 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() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
显示效果

版本9.0:美化建筑群
3D凯旋门与畅通道路的建筑群 - 细节版
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;
}
// 场景 & 相机 & 渲染器
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 {
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 roofs
const frenchBodyMaterial = new THREE.MeshPhongMaterial({color: 0xE0CDA9, roughness: 0.6}); // Beige stone
const frenchDetailMaterial = new THREE.MeshPhongMaterial({color: 0xD4AF37, metalness: 0.5}); // Gold accents
const frenchWindowMaterial = new THREE.MeshBasicMaterial({color: 0xADD8E6}); // Light blue glass
function 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 stories
for (let story = 0; story {
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 {
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 ? 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 ? 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() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
效果显示

版本10.0:添加云朵
3D凯旋门与畅通道路的建筑群 - 细节版
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;
}
// 场景 & 相机 & 渲染器
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 {
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 {
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 {
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.z -= 2 * cloudRadius;
if (cloud.position.z {
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 ? 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 ? 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() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
显示效果

版本11.0:添加动态热气球
3D凯旋门与畅通道路的建筑群 - 细节版
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;
}
// 场景 & 相机 & 渲染器
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 {
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 {
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 {
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.z -= 2 * cloudRadius;
if (cloud.position.z {
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 ? 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 ? 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() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
显示效果


浙公网安备 33010602011771号