Three.js 城市光效教程 城市光效 ·City Effect· ▶ 在线运行案例案例合集三维可视化功能案例threehub.cn开源仓库github地址https://github.com/z2586300277/three-cesium-examples400个案例代码:网盘链接你将学到什么Material.onBeforeCompile在不写完整 ShaderMaterial 的情况下改内置 shader替换#include/#include注入 GLSL四套大屏特效生长、上升流光、圆扩散、扫光FBX 分层处理建筑 / 地面 / 道路 EdgesGeometry线框效果说明加载上海 FBX 城市模型建筑从低到高「长出来」表面有蓝色上升带、紫色同心扩散波、青色 X 向扫光建筑边线同步生长地面与道路单独设色。核心概念onBeforeCompile 工作方式Three.js 内置MeshStandardMaterial等会先拼好 vertex/fragment shader再调用material.onBeforeCompile (shader) {shader.uniforms.uProgress { value: 0 }; shader.vertexShader shader.vertexShader.replace( #include ,#include transformed.z position.z * min(uProgress, 1.0);); };#include是shaderChunk片段可在 Three 源码renderers/shaders/ShaderChunk/查原文。四套特效分工| 函数 | 注入位置 | 视觉 | |------|---------|------| |applyGrowShader| vertexbegin_vertex|uProgress压扁 Z建筑生长 | |applyRiseShader| fragmentdithering_fragment| 沿高度smoothstep上升亮带 | |applySpreadShader| fragment | 距原点距离环形波mod(uSpreadTime)| |applySweepShader| fragment | 沿 X 的扫光条 |uniform 在 rAF 里通过renderList回调统一更新const renderList [];renderList.push((time) { shader.uniforms.uRiseTime.value time * 30.0; });function animate() { renderList.forEach(fn fn(clock.getElapsedTime())); // ... }FBX 分层 modelHandlerMapconst modelHandlerMap {CITY_UNTRIANGULATED: (model, group) { /建筑 线框 四套 shader/ }, LANDMASS: (model) { /深色地面/ }, ROADS: (model) { /道路色/ }, };线框EdgesGeometryLineSegments需rotateX(-Math.PI/2)对齐 FBX 坐标系。实现步骤Scene / Camera / Renderer / OrbitControlsCubeTexture 天空盒FBXLoader 加载城市按child.name走 handler建筑材质onBeforeCompile链式调用四个 apply 函数线框材质同样applyGrowShader同步生长Clock renderList 驱动所有 uniform 动画代码要点import * as THREE from threeimport { OrbitControls } from three/examples/jsm/controls/OrbitControls.js import { FBXLoader } from three/examples/jsm/loaders/FBXLoader.jsconst box document.getElementById(box)const scene new THREE.Scene()const camera new THREE.PerspectiveCamera(50, box.clientWidth / box.clientHeight, 0.1, 100000)camera.position.set(0, 400, 1000)const renderer new THREE.WebGLRenderer({ antialias: true, alpha: true, logarithmicDepthBuffer: true })renderer.setPixelRatio(window.devicePixelRatio * 1.3)renderer.setSize(box.clientWidth, box.clientHeight)box.appendChild(renderer.domElement)const controls new OrbitControls(camera, renderer.domElement)controls.enableDamping true// 文件地址 const urls [0, 1, 2, 3, 4, 5].map(k (FILE_HOST files/sky/skyBox0/ (k 1) .png));const textureCube new THREE.CubeTextureLoader().load(urls);scene.background textureCube;const renderList []const light new THREE.AmbientLight(0xadadad)scene.add(light)const directionalLight new THREE.DirectionalLight(0xffffff, 0.5)directionalLight.position.set(600, 600, 0)scene.add(directionalLight)/**对于shader内容的修改需要根据具体内容进行处理shader中会存在#include 等语句这些事three定义的glsl具体脚本内容查看three源码中renderer/shaders/shaderChunk下对应脚本文件而修改shader就是在对应的脚本语句后修改脚本或增加语句*/ const applyGrowShader (shader) { shader.uniforms.uProgress { value: 0 } shader.vertexShader uniform float uProgress; ${shader.vertexShader}shader.vertexShader shader.vertexShader.replace( #include ,#include transformed.z position.z * min(uProgress, 1.0);) renderList.push((progress) { shader.uniforms.uProgress.value progress }) } // 建筑表面流动上升效果 const applyRiseShader (shader) { shader.uniforms.uRiseTime { value: 0 } shader.uniforms.uRiseColor { value: new THREE.Color(#87CEEB) }shader.vertexShader shader.vertexShader.replace( #include ,#include varying vec3 vTransformedNormal; varying float vHeight;) shader.vertexShader shader.vertexShader.replace( #include ,#include vTransformedNormal normalize(normal); vHeight transformed.z;)shader.fragmentShader shader.fragmentShader.replace( #include ,#include uniform vec3 uRiseColor; uniform float uRiseTime; varying float vHeight; varying vec3 vTransformedNormal; vec3 riseLine() { float smoothness 1.8; float speed uRiseTime; bool isTopBottom (vTransformedNormal.z 0.0 || vTransformedNormal.z 0.0) vTransformedNormal.x 0.0 vTransformedNormal.y 0.0; float ratio isTopBottom ? 0.0 : smoothstep(speed, speed smoothness, vHeight) - smoothstep(speed smoothness, speed smoothness * 2.0, vHeight); return uRiseColor * ratio; }) shader.fragmentShader shader.fragmentShader.replace( #include ,#include gl_FragColor gl_FragColor vec4(riseLine(), 1.0);) renderList.push((time) { shader.uniforms.uRiseTime.value time * 30.0 }) }// 扩散波效果 const applySpreadShader (shader) { shader.uniforms.uSpreadTime { value: 0 } shader.uniforms.uSpreadColor { value: new THREE.Color(#9932CC) }shader.vertexShader shader.vertexShader.replace( #include ,#include varying vec2 vTransformedPosition;) shader.vertexShader shader.vertexShader.replace( #include ,#include vTransformedPosition vec2(position.x, position.y);) shader.fragmentShader shader.fragmentShader.replace( #include ,#include uniform vec3 uSpreadColor; uniform float uSpreadTime; varying vec2 vTransformedPosition; vec3 spread() { vec2 center vec2(0.0); float smoothness 60.0; float start mod(uSpreadTime, 1800.0); float distance length(vTransformedPosition - center); float ratio smoothstep(start, start smoothness, distance) - smoothstep(start smoothness, start smoothness * 2.0, distance); return uSpreadColor * ratio; }) shader.fragmentShader shader.fragmentShader.replace( #include ,#include gl_FragColor gl_FragColor vec4(spread(), 1.0);) renderList.push((time) { shader.uniforms.uSpreadTime.value time * 200.0 }) } // 扫光 const applySweepShader (shader) { shader.uniforms.uSweepTime { value: 0 } shader.uniforms.uSweepColor { value: new THREE.Color(#00FFFF) }shader.vertexShader shader.vertexShader.replace( #include ,#include varying vec2 vSweepPosition;) shader.vertexShader shader.vertexShader.replace( #include ,#include vSweepPosition vec2(position.x, position.y);) shader.fragmentShader shader.fragmentShader.replace( #include ,#include uniform vec3 uSweepColor; uniform float uSweepTime; varying vec2 vSweepPosition; vec3 sweep() { vec2 center vec2(0.0); float smoothness 60.0; float start mod(uSweepTime, 1800.0) - 800.0; float ratio smoothstep(start, start smoothness, vSweepPosition.x) - smoothstep(start smoothness, start smoothness * 2.0, vSweepPosition.x); return uSweepColor * ratio; }) shader.fragmentShader shader.fragmentShader.replace( #include ,#include gl_FragColor gl_FragColor vec4(sweep(), 1.0);) renderList.push((time) { shader.uniforms.uSweepTime.value time * 160.0 }) }const modelHandlerMap { CITY_UNTRIANGULATED: (model, group) { // 城市建筑 const { geometry, position, material } model// 模型线框化 const lienMaterial new THREE.LineBasicMaterial({ color: #2685fe }) const lineBox new THREE.LineSegments(new THREE.EdgesGeometry(geometry, 1), lienMaterial) lineBox.position.copy(position) // 模型坐标系与WebGL坐标系不同需要处理 lineBox.rotateX(-Math.PI / 2) group.add(lineBox)// 在原先材质效果的基础上修改shader material.onBeforeCompile (shader) { material.color new THREE.Color(#0e233d) material.transparent true material.opacity 0.9 // 实现生长效果 applyGrowShader(shader) applyRiseShader(shader) applySpreadShader(shader) applySweepShader(shader) } lienMaterial.onBeforeCompile (shader) { applyGrowShader(shader) } }, LANDMASS: (model) { // 地面 const material model.material material.color new THREE.Color(#040912) material.transparent true material.opacity 0.8 }, ROADS: (model) { // 道路 const material model.material material.color new THREE.Color(#292e4c) } }new FBXLoader().load(FILE_HOST models/fbx/shanghai.FBX, cityScene {const group new THREE.Group()cityScene.children.forEach((item) {const clonedData item.clone()modelHandlerMap[clonedData.name]?.(clonedData, group)group.add(clonedData)})scene.add(group) })const clock new THREE.Clock()animate()function animate() {renderList.forEach(fn fn(clock.getElapsedTime()))requestAnimationFrame(animate)controls.update()renderer.render(scene, camera)}window.onresize () {renderer.setSize(box.clientWidth, box.clientHeight)camera.aspect box.clientWidth / box.clientHeightcamera.updateProjectionMatrix()}完整源码GitHub小结本文提供城市光效完整 Three.js 源码与在线 Demo建议先运行案例再改 uniform/参数做二次实验更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库