波纹效果,在智慧城市可视化开发中经常用到,这里分享一个比较好玩的案例
以下是详细的步骤:
初始化部分:设置 Three.js 环境,包括场景、相机、渲染器和控制器
几何体和纹理:创建平面几何体并加载波纹纹理
着色器材质:使用自定义着色器实现波纹效果,包括顶点着色器和片段着色器
波纹算法:核心是 circleWave 函数,通过计算距离和时间来生成向外扩散的波纹
颜色混合:根据波纹强度混合两种颜色,形成视觉层次感
交互控制:使用 GUI 工具允许用户实时调整波纹颜色
动画循环:通过不断更新时间变量来驱动波纹动画
浏览地址:https://aivogenx.github.io/threejs-cesium-webgpu-vue-js/#/codeMirror?navigation=Three.js%E6%A1%88%E4%BE%8B&classify=shader&id=circleWave
代码
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Three.js波纹效果演示</title><!-- 这里可以添加CSS样式文件 -->
</head><body><!-- 导入模块映射,配置Three.js依赖路径 --><script type="importmap">{"imports": {"three": "./threejs/build/three.module.js","three/addons/": "./threejs/examples/jsm/"}}</script><script type="module">// 导入Three.js核心库和辅助工具import * as THREE from 'three'import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { GUI } from "three/addons/libs/lil-gui.module.min.js";// 获取渲染容器,使用整个body元素const box = document.body;// 创建Three.js场景,作为所有3D对象的容器const scene = new THREE.Scene()// 创建透视相机,参数依次为:视野角度、宽高比、近裁剪面、远裁剪面const camera = new THREE.PerspectiveCamera(50, box.clientWidth / box.clientHeight, 0.1, 1000)// 设置相机位置,从(1,1,1)的位置观察场景camera.position.set(1, 1, 1)// 创建WebGL渲染器,启用抗锯齿、透明背景和对数深度缓冲区const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, logarithmicDepthBuffer: true })// 设置渲染器尺寸为容器大小renderer.setSize(box.clientWidth, box.clientHeight)// 将渲染器的DOM元素添加到页面中box.appendChild(renderer.domElement)// 创建轨道控制器,允许用户通过鼠标交互旋转和缩放场景const controls = new OrbitControls(camera, renderer.domElement)// 启用阻尼效果,使相机移动更平滑controls.enableDamping = true// 窗口大小变化时的响应函数,保持渲染内容适配窗口window.onresize = () => {// 更新渲染器尺寸renderer.setSize(box.clientWidth, box.clientHeight)// 更新相机的宽高比camera.aspect = box.clientWidth / box.clientHeight// 更新相机投影矩阵camera.updateProjectionMatrix()}// 创建平面几何体,作为波纹效果的载体const geometry = new THREE.PlaneGeometry(2, 2);// 加载波纹纹理,这是实现波纹效果的基础纹理// 注意:FILE_HOST变量需要在实际使用时替换为真实的资源路径const texture = new THREE.TextureLoader().load(FILE_HOST + 'images/channels/wave.png')// 设置纹理在水平和垂直方向上重复texture.wrapS = THREE.RepeatWrappingtexture.wrapT = THREE.RepeatWrapping// 创建自定义着色器材质,实现波纹效果的核心部分const material = new THREE.ShaderMaterial({side: THREE.DoubleSide, // 双面渲染transparent: true, // 启用透明度blending: THREE.AdditiveBlending, // 添加混合模式让效果更亮uniforms: {uTime: { value: 0.0 }, // 时间变量,控制波纹动画uScanTex: { value: texture }, // 波纹纹理uScanColor: { value: new THREE.Color(0x00ffff) }, // 主要扫描颜色uScanColorDark: { value: new THREE.Color(0x0088ff) } // 暗部扫描颜色},// 顶点着色器:处理几何体顶点,传递纹理坐标和位置信息给片段着色器vertexShader: `varying vec2 vUv; // 传递给片段着色器的纹理坐标varying vec3 vPosition; // 传递给片段着色器的顶点位置void main() {vUv = uv;vPosition = position;gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);}`,// 片段着色器:计算每个像素的颜色,实现波纹效果的核心逻辑fragmentShader: `// 波纹原点和波纹扩展速度常量#define uScanOrigin vec3(0.0, 0.0, 0.0)#define uScanWaveRatio1 3.2#define uScanWaveRatio2 2.8uniform float uTime; // 时间变量uniform sampler2D uScanTex; // 波纹纹理uniform vec3 uScanColor; // 波纹主颜色uniform vec3 uScanColorDark; // 波纹暗部颜色varying vec2 vUv; // 从顶点着色器传递的纹理坐标varying vec3 vPosition; // 从顶点着色器传递的顶点位置// 计算圆形波纹效果float circleWave(vec3 p, vec3 origin, float distRatio) {float t = uTime;float dist = distance(p, origin) * distRatio; // 计算到原点的距离并应用缩放float radialMove = fract(dist - t); // 创建随时间移动的波纹float fadeOutMask = 1.0 - smoothstep(1.0, 3.0, dist); // 波纹随距离衰减radialMove *= fadeOutMask;float cutInitialMask = 1.0 - step(t, dist); // 控制波纹从中心向外扩展return radialMove * cutInitialMask;}// 根据位置和纹理计算波纹颜色vec3 getScanColor(vec3 worldPos, vec2 uv, vec3 col) {// 从纹理采样,获取波纹基础图案float scanMask = texture2D(uScanTex, uv).r;// 计算两种不同速度的波纹效果,形成层次感float cw = circleWave(worldPos, uScanOrigin, uScanWaveRatio1);float cw2 = circleWave(worldPos, uScanOrigin, uScanWaveRatio2);// 为第一种波纹创建遮罩,控制波纹的显示范围和强度float mask1 = smoothstep(0.3, 0.0, 1.0 - cw);mask1 *= (1.0 + scanMask * 0.7); // 结合纹理增强效果// 为第二种波纹创建遮罩float mask2 = smoothstep(0.07, 0.0, 1.0 - cw2) * 0.8;mask1 += mask2;// 创建波纹边缘高亮效果float mask3 = smoothstep(0.09, 0.0, 1.0 - cw) * 1.5;mask1 += mask3;// 根据遮罩强度混合两种颜色,形成波纹的颜色变化vec3 scanCol = mix(uScanColorDark, uScanColor, mask1);return scanCol * mask1; // 返回最终的波纹颜色}void main() {vec3 col = vec3(0.0);// 计算波纹颜色,纹理坐标乘以10.0增强波纹密度col = getScanColor(vPosition, vUv * 10.0, col);// 根据颜色强度计算透明度,使波纹边缘更柔和float alpha = length(col);gl_FragColor = vec4(col, alpha);}`});// 创建网格对象,将几何体和材质组合,并添加到场景中const mesh = new THREE.Mesh(geometry, material);// 将平面旋转90度,使其水平放置mesh.rotation.x = Math.PI / 2scene.add(mesh);// 创建图形界面控制器,允许用户交互调整参数const gui = new GUI()const params = {uScanColor: '#00ffff', // 波纹主颜色uScanColorDark: '#0088ff' // 波纹暗部颜色}// 添加颜色控制器,允许用户修改波纹主颜色gui.addColor(params, 'uScanColor').onChange((value) => {material.uniforms.uScanColor.value.set(value)})// 添加颜色控制器,允许用户修改波纹暗部颜色gui.addColor(params, 'uScanColorDark').onChange((value) => {material.uniforms.uScanColorDark.value.set(value)})// 动画循环函数,驱动整个场景的渲染和更新animate()function animate() {// 请求下一帧动画requestAnimationFrame(animate)// 更新控制器状态controls.update()// 渲染场景renderer.render(scene, camera)// 更新时间变量,驱动波纹动画material.uniforms.uTime.value += 0.005;}</script>
</body></html>