news 2026/7/9 18:03:48

Three.js 波涛海浪教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Three.js 波涛海浪教程

波涛海浪 ·move Sea· ▶ 在线运行案例

  • 案例合集:三维可视化功能案例(threehub.cn)
  • 开源仓库github地址:https://github.com/z2586300277/three-cesium-examples
  • 400个案例代码:网盘链接

你将学到什么

  • ShaderMaterial 自定义着色器实现核心视觉效果
  • OrbitControls 相机轨道交互
  • 水面反射/镜像材质
  • 场景雾效增强纵深
  • requestAnimationFrame渲染循环与resize自适应

效果说明

本案例演示波涛海浪效果:基于 WebGL 实现「波涛海浪」可视化效果,附完整可运行源码;核心用到 ShaderMaterial、OrbitControls、水面反射/镜像材质。建议先打开文首在线案例查看动态画面,再对照下方源码逐步理解。

核心概念

  • Scene / Camera / WebGLRenderer构成最小渲染闭环;大场景可开logarithmicDepthBuffer缓解 Z-fighting。
  • ShaderMaterial通过uniforms+ 自定义 GLSL 控制逐像素/逐点效果;透明粒子常配合depthTest: false
  • OrbitControls提供轨道旋转/缩放;开启enableDamping后需在 animate 中controls.update()

实现步骤

  • 搭建 Scene、PerspectiveCamera、WebGLRenderer,挂载 canvas 并处理resize
  • 定义 uniforms / onBeforeCompile 或 ShaderMaterial,编写 GLSL 与材质参数
  • 创建 OrbitControls(及 Raycaster 等交互控件,若源码包含)
  • requestAnimationFrame循环中更新状态并 render(Cesium 为viewer.render或自动渲染)
  • 代码要点

    import * as THREE from "three";

    import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; import * as dat from "dat.gui";

    // refer https://codepen.io/aderaaij/pen/XWpMONO const vertexShader =#include

    uniform float uTime;

    uniform float uBigWavesElevation; uniform vec2 uBigWavesFrequency; uniform float uBigWaveSpeed;

    uniform float uSmallWavesElevation; uniform float uSmallWavesFrequency; uniform float uSmallWavesSpeed; uniform float uSmallWavesIterations;

    varying float vElevation;

    // Classic Perlin 3D Noise // by Stefan Gustavson // vec4 permute(vec4 x){return mod(((x34.0)+1.0)x, 289.0);} vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;} vec3 fade(vec3 t) {return ttt(t(t*6.0-15.0)+10.0);}

    float cnoise(vec3 P){ vec3 Pi0 = floor(P); // Integer part for indexing vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 Pi0 = mod(Pi0, 289.0); Pi1 = mod(Pi1, 289.0); vec3 Pf0 = fract(P); // Fractional part for interpolation vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); vec4 iy = vec4(Pi0.yy, Pi1.yy); vec4 iz0 = Pi0.zzzz; vec4 iz1 = Pi1.zzzz;

    vec4 ixy = permute(permute(ix) + iy); vec4 ixy0 = permute(ixy + iz0); vec4 ixy1 = permute(ixy + iz1);

    vec4 gx0 = ixy0 / 7.0; vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5; gx0 = fract(gx0); vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); vec4 sz0 = step(gz0, vec4(0.0)); gx0 -= sz0 * (step(0.0, gx0) - 0.5); gy0 -= sz0 * (step(0.0, gy0) - 0.5);

    vec4 gx1 = ixy1 / 7.0; vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5; gx1 = fract(gx1); vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); vec4 sz1 = step(gz1, vec4(0.0)); gx1 -= sz1 * (step(0.0, gx1) - 0.5); gy1 -= sz1 * (step(0.0, gy1) - 0.5);

    vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);

    vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w; vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;

    float n000 = dot(g000, Pf0); float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); float n111 = dot(g111, Pf1);

    vec3 fade_xyz = fade(Pf0); vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); return 2.2 * n_xyz; }

    void main() { #include #include #include vec4 modelPosition = modelMatrix * vec4(position, 1.0); float elevation = sin(modelPosition.xuBigWavesFrequency.x + uTimeuBigWaveSpeed)sin(modelPosition.zuBigWavesFrequency.y + uTime * uBigWaveSpeed)

    • uBigWavesElevation;
    for(float i = 1.0; i <= 10.0; i++) { elevation -= abs( cnoise( vec3(modelPosition.xzuSmallWavesFrequencyi, uTime * uSmallWavesSpeed) )
    • uSmallWavesElevation / i
    ); if(i >= uSmallWavesIterations ) { break; } } modelPosition.y += elevation; vec4 viewPosition = viewMatrix * modelPosition; vec4 projectedPosition = projectionMatrix * viewPosition; gl_Position = projectedPosition;

    vElevation = elevation; }

    const fragmentShader =#include precision mediump float; uniform vec3 uDepthColor; uniform vec3 uSurfaceColor;

    uniform float uColorOffset; uniform float uColorMultiplier;

    varying float vElevation;

    void main() { float mixStrength = (vElevation + uColorOffset) * uColorMultiplier; vec3 color = mix(uDepthColor, uSurfaceColor, mixStrength); gl_FragColor = vec4(color, 1.0); #include }

    const gui = new dat.GUI({ closed: false, width: 340 }); const bigWavesFolder = gui.addFolder("Large Waves"); const smallWavesFolder = gui.addFolder("Small Waves"); const colorFolder = gui.addFolder("Colors"); const debugObject = { waveDepthColor: "#b5e4e4", waveSurfaceColor: "#4d9aaa", fogNear: 1, fogFar: 8, fogColor: "#8e99a2" };

    const scene = new THREE.Scene(); scene.fog = new THREE.Fog( debugObject.fogColor, debugObject.fogNear, debugObject.fogFar ); scene.background = new THREE.Color(debugObject.fogColor); const waterGeometry = new THREE.PlaneGeometry(12, 12, 512, 512); const waterMaterial = new THREE.ShaderMaterial({ vertexShader, fragmentShader, transparent: true, fog: true, uniforms: { uTime: { value: 0 }, uMouse: { value: new THREE.Vector2() }, uBigWavesElevation: { value: 0.2 }, uBigWavesFrequency: { value: new THREE.Vector2(4, 2) }, uBigWaveSpeed: { value: 0.75 }, uSmallWavesElevation: { value: 0.15 }, uSmallWavesFrequency: { value: 3 }, uSmallWavesSpeed: { value: 0.2 }, uSmallWavesIterations: { value: 4 }, uDepthColor: { value: new THREE.Color(debugObject.waveDepthColor) }, uSurfaceColor: { value: new THREE.Color(debugObject.waveSurfaceColor) }, uColorOffset: { value: 0.08 }, uColorMultiplier: { value: 5 }, ...THREE.UniformsLib["fog"] } });

    const water = new THREE.Mesh(waterGeometry, waterMaterial); water.rotation.x = -Math.PI * 0.5; scene.add(water);

    const sizes = { width: window.innerWidth, height: window.innerHeight };

    window.addEventListener("resize", () => { sizes.width = window.innerWidth; sizes.height = window.innerHeight; camera.aspect = sizes.width / sizes.height; camera.updateProjectionMatrix() renderer.setSize(sizes.width, sizes.height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); });

    const camera = new THREE.PerspectiveCamera( 75, sizes.width / sizes.height, 0.1, 100 ); camera.position.set(1, 1, 1); scene.add(camera);

    const renderer = new THREE.WebGLRenderer(); renderer.setSize(sizes.width, sizes.height); renderer.outputColorSpace = THREE.SRGBColorSpace; document.body.appendChild(renderer.domElement);

    const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true;

    bigWavesFolder .add(waterMaterial.uniforms.uBigWavesElevation, "value") .min(0) .max(1) .step(0.001) .name("Elevation"); bigWavesFolder .add(waterMaterial.uniforms.uBigWavesFrequency.value, "x") .min(0) .max(10) .step(0.001) .name("Frequency X"); bigWavesFolder .add(waterMaterial.uniforms.uBigWavesFrequency.value, "y") .min(0) .max(10) .step(0.001) .name("Frequency Y"); bigWavesFolder .add(waterMaterial.uniforms.uBigWaveSpeed, "value") .min(0.25) .max(5) .step(0.001) .name("Speed");

    // Small Waves smallWavesFolder .add(waterMaterial.uniforms.uSmallWavesElevation, "value") .min(0.0) .max(0.3) .step(0.001) .name("Elevation"); smallWavesFolder .add(waterMaterial.uniforms.uSmallWavesFrequency, "value") .min(0) .max(30) .step(0.001) .name("Frequency"); smallWavesFolder .add(waterMaterial.uniforms.uSmallWavesSpeed, "value") .min(0.0) .max(1) .step(0.001) .name("Speed"); smallWavesFolder .add(waterMaterial.uniforms.uSmallWavesIterations, "value") .min(0) .max(10) .step(1) .name("Iterations");

    // Colors colorFolder .add(waterMaterial.uniforms.uColorOffset, "value") .min(0) .max(0.15) .step(0.0001) .name("Color Offset"); colorFolder .add(waterMaterial.uniforms.uColorMultiplier, "value") .min(0.0) .max(10.0) .step(0.001) .name("Color multiplier"); colorFolder .addColor(debugObject, "waveDepthColor") .name("Wave depth color") .onChange(() => { waterMaterial.uniforms.uDepthColor.value.set(debugObject.waveDepthColor); }); colorFolder .addColor(debugObject, "waveSurfaceColor") .name("Wave surface color") .onChange(() => { waterMaterial.uniforms.uSurfaceColor.value.set( debugObject.waveSurfaceColor ); }); colorFolder .addColor(debugObject, "fogColor") .name("Fog Color") .onChange(() => { waterMaterial.uniforms.fogColor.value.set(debugObject.fogColor); scene.background.set(debugObject.fogColor); scene.fog = new THREE.Fog( debugObject.fogColor, debugObject.fogNear, debugObject.fogFar ); });

    /**

    • Animate
    */ const clock = new THREE.Clock();

    const tick = () => { const elapsedTime = clock.getElapsedTime(); controls.update(); waterMaterial.uniforms.uTime.value = elapsedTime; renderer.render(scene, camera); window.requestAnimationFrame(tick); };

    tick();

    完整源码:GitHub

    小结

    • 本文提供波涛海浪完整 Three.js 源码与在线 Demo,建议先运行案例再改 uniform/参数做二次实验
    • 更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/9 18:00:43

C++原子操作与CAS原理详解:从并发基础到ABA问题实战

1. 项目概述&#xff1a;为什么原子操作是并发编程的基石在C多线程的世界里&#xff0c;数据竞争&#xff08;Data Race&#xff09;是程序员最常面对的“幽灵”。两个线程同时读写一个共享变量&#xff0c;结果变得不可预测&#xff0c;程序行为诡异且难以复现。传统的解决方案…

作者头像 李华
网站建设 2026/7/9 17:59:32

蓝牙5.4音频系统开发:STM32与LE Audio实战

1. 项目背景与核心组件选型在嵌入式音频开发领域&#xff0c;蓝牙无线传输技术正经历着从传统Bluetooth Classic到新一代LE Audio的变革。本项目采用IDC777-1蓝牙模块与STM32F446RE微控制器的组合&#xff0c;构建了一个支持Bluetooth 5.4协议的高质量无线音频传输系统。这套方…

作者头像 李华
网站建设 2026/7/9 17:58:35

Jain Fairness Index 公平性指标:Python 实现与 3 种网络带宽分配场景验证

Jain Fairness Index 公平性指标&#xff1a;Python 实现与 3 种网络带宽分配场景验证在网络资源调度和算法设计中&#xff0c;公平性是一个至关重要的考量因素。想象一下&#xff0c;当你正在参加一场在线会议&#xff0c;突然视频画面开始卡顿&#xff0c;而其他参会者的连接…

作者头像 李华
网站建设 2026/7/9 17:58:14

VARBook:解决中文程序员变量命名难题的智能助手

VARBook&#xff1a;解决中文程序员变量命名难题的智能助手 【免费下载链接】varbook 适合中文程序员的变量命名助手&#xff0c;NLP翻译&#xff0c;规范变量命名&#xff0c;定制化变量命名规则 项目地址: https://gitcode.com/gh_mirrors/va/varbook 作为一名中文程序…

作者头像 李华
网站建设 2026/7/9 17:56:37

工业工程学数据分析在2026年的应用价值

一、工业工程数据分析的核心价值 工业工程&#xff08;IE&#xff09;与数据分析的融合是智能制造落地的关键。通过实时监控生产数据、识别瓶颈环节&#xff0c;企业能实现资源利用率提升15%-30%&#xff08;如汽车装配线优化案例&#xff09;。供应链领域&#xff0c;基于历史…

作者头像 李华
网站建设 2026/7/9 17:53:47

技术人跳槽避坑指南:从5个真实离职原因到3步面试应答框架

技术人跳槽避坑指南&#xff1a;从真实离职原因到结构化应答策略在技术行业&#xff0c;跳槽早已成为职业发展的常态选择。但许多技术人在面试环节往往陷入两难——既不想违心编造离职理由&#xff0c;又担心真实原因会给HR留下负面印象。事实上&#xff0c;离职原因的回答质量…

作者头像 李华