学习threejs,使用自定义GLSL 着色器,实现水面、粒子特效

👨‍⚕️ 主页: gis分享者

👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!

👨‍⚕️ 收录于专栏:threejs gis工程师


文章目录

  • 一、🍀前言
    • [1.1 ☘️GLSL着色器](#1.1 ☘️GLSL着色器)
      • [1.1.1 ☘️着色器类型](#1.1.1 ☘️着色器类型)
      • [1.1.2 ☘️工作原理](#1.1.2 ☘️工作原理)
      • [1.1.3 ☘️核心特点](#1.1.3 ☘️核心特点)
      • [1.1.4 ☘️应用场景](#1.1.4 ☘️应用场景)
      • [1.1.5 ☘️实战示例](#1.1.5 ☘️实战示例)
  • [二、🍀使用自定义GLSL 着色器,实现水面、粒子特效](#二、🍀使用自定义GLSL 着色器,实现水面、粒子特效)
    • [1. ☘️实现思路](#1. ☘️实现思路)
    • [2. ☘️代码样例](#2. ☘️代码样例)

一、🍀前言

本文详细介绍如何基于threejs在三维场景中使用自定义GLSL 着色器,实现水面、粒子特效。亲测可用。希望能帮助到您。一起学习,加油!加油!

1.1 ☘️GLSL着色器

GLSL(OpenGL Shading Language)是OpenGL的核心编程语言,用于编写图形渲染管线中可定制的计算逻辑。其核心设计目标是通过GPU并行计算实现高效的图形处理,支持从基础几何变换到复杂物理模拟的多样化需求。

1.1.1 ☘️着色器类型

顶点着色器(Vertex Shader)

  • 功能:处理每个顶点的坐标变换(如模型视图投影矩阵变换)、法线计算及顶点颜色传递。
  • 输出:裁剪空间坐标gl_Position,供后续光栅化阶段使用。

片段着色器(Fragment Shader)

  • 功能:计算每个像素的最终颜色,支持纹理采样、光照模型(如Phong、PBR)及后处理效果(如模糊、景深)。
  • 输出:像素颜色gl_FragColor或gl_FragColor(RGBA格式)。

计算着色器(Compute Shader,高级)

  • 功能:执行通用并行计算任务(如物理模拟、图像处理),不直接绑定渲染管线。
  • 特点:通过工作组(Work Group)实现高效数据并行处理。

1.1.2 ☘️工作原理

渲染管线流程

  • 顶点处理:CPU提交顶点数据(位置、颜色、纹理坐标),GPU并行执行顶点着色器处理每个顶点。
  • 光栅化:将顶点数据转换为像素片段,生成片段着色器输入。
  • 片段处理:GPU并行执行片段着色器计算每个像素颜色。
  • 输出合并:将片段颜色与帧缓冲区混合,生成最终图像。

数据流动

  • 顶点属性:通过glVertexAttribPointer传递位置、颜色等数据,索引由layout(location=N)指定。
  • Uniform变量:CPU通过glGetUniformLocation传递常量数据(如变换矩阵、时间),在渲染循环中更新。
  • 内置变量: gl_Position(顶点着色器输出):裁剪空间坐标。 gl_FragCoord(片段着色器输入):当前像素的窗口坐标。
    gl_FrontFacing(片段着色器输入):判断像素是否属于正面三角形。

1.1.3 ☘️核心特点

语法特性

  • C语言变体:支持条件语句、循环、函数等结构,天然适配图形算法。
  • 向量/矩阵运算:内置vec2/vec3/vec4及mat2/mat3/mat4类型,支持点乘、叉乘等操作。
  • 精度限定符:如precision mediump float,控制计算精度与性能平衡。

硬件加速

  • 并行计算:GPU数千个核心并行执行着色器代码,适合处理大规模数据(如粒子系统、体素渲染)。
  • 内存模型:支持常量内存(Uniform)、纹理内存(Sampler)及共享内存(计算着色器),优化数据访问效率。

灵活性

  • 可编程管线:完全替代固定渲染管线,支持自定义光照、阴影、后处理效果。
  • 跨平台兼容性:OpenGL ES(移动端)与WebGL(Web)均支持GLSL,代码可移植性强。

1.1.4 ☘️应用场景

游戏开发

  • 实时渲染:实现PBR材质、动态阴影、屏幕空间反射。
  • 特效系统:粒子火焰、流体模拟、布料物理。
  • 性能优化:通过计算着色器加速AI计算、碰撞检测。

数据可视化

  • 科学计算:将多维数据映射为颜色/高度图(如气象数据、流场可视化)。
  • 信息图表:动态生成3D柱状图、热力图,增强数据表现力。

艺术创作

  • 程序化生成:使用噪声函数(如Perlin、Simplex)生成地形、纹理。
  • 交互式装置:结合传感器数据实时修改着色器参数,创造动态艺术作品。

教育与研究

  • 算法实验:实时调试光线追踪、路径追踪算法。
  • 教学工具:可视化线性代数运算(如矩阵变换、向量投影)。

1.1.5 ☘️实战示例

顶点着色器(传递法线与世界坐标):

javascript 复制代码
#version 330 core
layout(location=0) in vec3 aPos;
layout(location=1) in vec3 aNormal;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = mat3(transpose(inverse(model))) * aNormal; // 模型空间到世界空间的法线变换
    gl_Position = projection * view * vec4(FragPos, 1.0);
}

片段着色器(实现Blinn-Phong光照):

javascript 复制代码
#version 330 core
in vec3 FragPos;
in vec3 Normal;
out vec4 FragColor;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;
void main() {
    // 环境光
    vec3 ambient = 0.1 * lightColor;
    // 漫反射
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;
    // 镜面反射
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = 0.5 * spec * lightColor;
    // 最终颜色
    vec3 result = (ambient + diffuse + specular) * objectColor;
    FragColor = vec4(result, 1.0);
}

官方文档

二、🍀使用自定义GLSL 着色器,实现水面、粒子特效

1. ☘️实现思路

使用自定义GLSL 着色器定义THREE.ShaderMaterial材质material,定义THREE.PlaneGeometry作为水面、THREE.Points类型对象作为粒子结合UnrealBloomPass后期处理特效,实现水面、粒子效果。具体代码参考代码样例。可以直接运行。

2. ☘️代码样例

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>水面、粒子特效</title>
    <style>
        body { margin: 0; overflow: hidden; }
        canvas { display: block; }
    </style>
</head>
<body>
<script type="module">
  import * as THREE from 'https://cdn.skypack.dev/three@0.136.0/build/three.module.js';
  import { OrbitControls } from 'https://cdn.skypack.dev/three@0.136.0/examples/jsm/controls/OrbitControls.js';
  import { EffectComposer } from 'https://cdn.skypack.dev/three@0.136.0/examples/jsm/postprocessing/EffectComposer.js';
  import { RenderPass } from 'https://cdn.skypack.dev/three@0.136.0/examples/jsm/postprocessing/RenderPass.js';
  import { UnrealBloomPass } from 'https://cdn.skypack.dev/three@0.136.0/examples/jsm/postprocessing/UnrealBloomPass.js';
  import { GUI } from 'https://cdn.skypack.dev/dat.gui';

  // Scene setup
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
  camera.position.set(0, 2, 5);
  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  // Parameters
  const params = {
    alpha: 0.73,
    fresnelStrength: 1.0,
    waveAmplitude: 4.19,
    noiseScale: 0.11,
    colorDeep: '#0070ff',
    colorShallow: '#080040',
    textureInfluence: 0.5,
    bloomStrength: 0.5,
    bloomRadius: 0.4,
    bloomThreshold: 0.083,
    particleCount: 1000,
    particleSpeed: 0.5,
    particleSize: 5.0,
    particleOffsetY: 0.2,
  };

  // Post-processing
  let composer = new EffectComposer(renderer);
  const renderPass = new RenderPass(scene, camera);
  composer.addPass(renderPass);

  const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight));
  bloomPass.strength = params.bloomStrength;
  bloomPass.radius = params.bloomRadius;
  bloomPass.threshold = params.bloomThreshold;
  composer.addPass(bloomPass);

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

  // Water geometry
  const geometry = new THREE.PlaneGeometry(10, 10, 128, 128);
  geometry.rotateX(-Math.PI / 2);
  geometry.translate(0, 0.5, 0);

  // Texture
  const loader = new THREE.TextureLoader();
  const texture = loader.load('https://images.pexels.com/photos/3871773/pexels-photo-3871773.jpeg?&w=1920&h=1920');
  texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
  texture.repeat.set(2, 2);

  // Uniforms for water and particles
  const uniforms = {
    uTime: { value: 0.0 },
    uAlpha: { value: params.alpha },
    uFresnelStrength: { value: params.fresnelStrength },
    uWaveAmp: { value: params.waveAmplitude },
    uNoiseScale: { value: params.noiseScale },
    uColorDeep: { value: new THREE.Color(params.colorDeep) },
    uColorShallow: { value: new THREE.Color(params.colorShallow) },
    uResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
    uTexture: { value: texture },
    uTextureInfluence: { value: params.textureInfluence },
    uParticleSize: { value: params.particleSize }, // Ajout de uParticleSize
  };

  // Water vertex shader
  const vertexShader = `
        uniform float uTime;
        uniform float uWaveAmp;
        uniform float uNoiseScale;
        varying vec2 vUv;
        varying vec3 vPos;
        varying vec3 vNormal;
        vec3 mod289(vec3 x) {
          return x - floor(x * (1.0 / 289.0)) * 289.0;
        }
        vec4 mod289(vec4 x) {
          return x - floor(x * (1.0 / 289.0)) * 289.0;
        }
        vec4 permute(vec4 x) {
          return mod289(((x*34.0)+1.0)*x);
        }
        vec4 taylorInvSqrt(vec4 r) {
          return 1.79284291400159 - 0.85373472095314 * r;
        }
        float noise(vec3 v) {
          const vec2 C = vec2(1.0/6.0, 1.0/3.0);
          const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
          vec3 i = floor(v + dot(v, C.yyy));
          vec3 x0 = v - i + dot(i, C.xxx);
          vec3 g = step(x0.yzx, x0.xyz);
          vec3 l = 1.0 - g;
          vec3 i1 = min(g.xyz, l.zxy);
          vec3 i2 = max(g.xyz, l.zxy);
          vec3 x1 = x0 - i1 + C.xxx;
          vec3 x2 = x0 - i2 + C.yyy;
          vec3 x3 = x0 - D.yyy;
          i = mod289(i);
          vec4 p = permute(permute(permute(
                        i.z + vec4(0.0, i1.z, i2.z, 1.0))
                       + i.y + vec4(0.0, i1.y, i2.y, 1.0))
                       + i.x + vec4(0.0, i1.x, i2.x, 1.0));
          float n_ = 0.142857142857;
          vec3 ns = n_ * D.wyz - D.xzx;
          vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
          vec4 x_ = floor(j * ns.z);
          vec4 y_ = floor(j - 7.0 * x_);
          vec4 x = x_ * ns.x + ns.yyyy;
          vec4 y = y_ * ns.x + ns.yyyy;
          vec4 h = 1.0 - abs(x) - abs(y);
          vec4 b0 = vec4(x.xy, y.xy);
          vec4 b1 = vec4(x.zw, y.zw);
          vec4 s0 = floor(b0)*2.0 + 1.0;
          vec4 s1 = floor(b1)*2.0 + 1.0;
          vec4 sh = -step(h, vec4(0.0));
          vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;
          vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;
          vec3 p0 = vec3(a0.xy, h.x);
          vec3 p1 = vec3(a0.zw, h.y);
          vec3 p2 = vec3(a1.xy, h.z);
          vec3 p3 = vec3(a1.zw, h.w);
          vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
          p0 *= norm.x;
          p1 *= norm.y;
          p2 *= norm.z;
          p3 *= norm.w;
          vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
          m = m * m;
          return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
        }
        mat3 rotationMatrix(vec3 axis, float angle) {
          axis = normalize(axis);
          float s = sin(angle);
          float c = cos(angle);
          float oc = 1.0 - c;
          return mat3(
            oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s,
            oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s,
            oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c
          );
        }
        float fnoise(vec3 p) {
          mat3 rot = rotationMatrix(normalize(vec3(0.0, 0.0, 1.0)), 0.5 * uTime);
          mat3 rot2 = rotationMatrix(normalize(vec3(0.0, 0.0, 1.0)), 0.3 * uTime);
          float sum = 0.0;
          vec3 r = rot * p;
          float add = noise(r);
          float msc = add + 0.7;
          msc = clamp(msc, 0.0, 1.0);
          sum += 0.6 * add;
          p = p * 2.0;
          r = rot * p;
          add = noise(r);
          add *= msc;
          sum += 0.5 * add;
          msc *= add + 0.7;
          msc = clamp(msc, 0.0, 1.0);
          p.xy = p.xy * 2.0;
          p = rot2 * p;
          add = noise(p);
          add *= msc;
          sum += 0.25 * abs(add);
          msc *= add + 0.7;
          msc = clamp(msc, 0.0, 1.0);
          p = p * 2.0;
          add = noise(p);
          add *= msc;
          sum += 0.125 * abs(add);
          p = p * 2.0;
          add = noise(p);
          add *= msc;
          sum += 0.0625 * abs(add);
          return sum * 0.516129;
        }
        float getHeight(vec3 p) {
          return 0.3 - uWaveAmp * fnoise(vec3(uNoiseScale * (p.x + 0.0 * uTime), uNoiseScale * p.z, 0.4 * uTime));
        }
        void main() {
          vUv = uv;
          vec3 pos = position;
          pos.y = getHeight(pos);
          vPos = pos;
          vec3 pdx = pos + vec3(0.01, 0.0, 0.0);
          vec3 pdz = pos + vec3(0.0, 0.0, 0.01);
          float hdx = getHeight(pdx);
          float hdz = getHeight(pdz);
          pdx.y = hdx;
          pdz.y = hdz;
          vNormal = normalize(cross(pos - pdz, pos - pdx));
          gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
        }
      `;

  // Water fragment shader
  const fragmentShader = `
        uniform float uTime;
        uniform float uAlpha;
        uniform float uFresnelStrength;
        uniform float uWaveAmp;
        uniform float uNoiseScale;
        uniform vec3 uColorDeep;
        uniform vec3 uColorShallow;
        uniform vec2 uResolution;
        uniform sampler2D uTexture;
        uniform float uTextureInfluence;
        varying vec2 vUv;
        varying vec3 vPos;
        varying vec3 vNormal;
        vec4 getSky(vec3 rd) {
          if (rd.y > 0.3) return vec4(0.5, 0.8, 1.5, 1.0);
          if (rd.y < 0.0) return vec4(0.0, 0.2, 0.4, 1.0);
          if (rd.z > 0.9 && rd.x > 0.3) {
            if (rd.y > 0.2) return 1.5 * vec4(2.0, 1.0, 1.0, 1.0);
            return 1.5 * vec4(2.0, 1.0, 0.5, 1.0);
          }
          return vec4(0.5, 0.8, 1.5, 1.0);
        }
        vec4 shade(vec3 normal, vec3 pos, vec3 rd) {
          float fresnel = uFresnelStrength * pow(1.0 - clamp(dot(-rd, normal), 0.0, 1.0), 5.0) + (1.0 - uFresnelStrength);
          vec3 refVec = reflect(rd, normal);
          vec4 reflection = getSky(refVec);
          float deep = 1.0 + 0.5 * pos.y;
          vec4 col = fresnel * reflection;
          col += deep * 0.4 * vec4(uColorDeep, 1.0);
          col = mix(col, vec4(uColorShallow, 1.0), clamp(pos.y * 2.0 + 0.5, 0.0, 1.0));
          vec4 textureColor = texture2D(uTexture, vUv);
          col = mix(col, textureColor, uTextureInfluence);
          return clamp(col, 0.0, 1.0);
        }
        void main() {
          vec3 ro = cameraPosition;
          vec3 rd = normalize(vPos - ro);
          vec4 col = shade(vNormal, vPos, rd);
          gl_FragColor = vec4(col.rgb, uAlpha);
        }
      `;

  // Water material
  const material = new THREE.ShaderMaterial({
    vertexShader,
    fragmentShader,
    uniforms,
    transparent: true,
    side: THREE.DoubleSide,
    wireframe: false,
  });

  const waterSurface = new THREE.Mesh(geometry, material);
  scene.add(waterSurface);

  // Particle system
  const particleCount = params.particleCount;
  const particleGeometry = new THREE.BufferGeometry();
  const positions = new Float32Array(particleCount * 3);
  const uvs = new Float32Array(particleCount * 2);
  const velocities = new Float32Array(particleCount * 3);
  const lifetimes = new Float32Array(particleCount);
  const offsetYs = new Float32Array(particleCount);

  // Initialize particles
  for (let i = 0; i < particleCount; i++) {
    const i3 = i * 3;
    const i2 = i * 2;
    const x = (Math.random() - 0.5) * 10;
    const z = (Math.random() - 0.5) * 10;
    const y = 0; // Placeholder, updated in shader
    positions[i3] = x;
    positions[i3 + 1] = y;
    positions[i3 + 2] = z;
    uvs[i2] = (x + 5) / 10;
    uvs[i2 + 1] = (z + 5) / 10;
    velocities[i3] = 0;
    velocities[i3 + 1] = params.particleSpeed * (0.5 + Math.random());
    velocities[i3 + 2] = 0;
    lifetimes[i] = Math.random() * 5;
    offsetYs[i] = Math.random() * params.particleOffsetY;
  }

  particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  particleGeometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
  particleGeometry.setAttribute('velocity', new THREE.BufferAttribute(velocities, 3));
  particleGeometry.setAttribute('lifetime', new THREE.BufferAttribute(lifetimes, 1));
  particleGeometry.setAttribute('offsetY', new THREE.BufferAttribute(offsetYs, 1));

  // Particle vertex shader
  const particleVertexShader = `
        uniform float uTime;
        uniform float uWaveAmp;
        uniform float uNoiseScale;
        uniform float uParticleSize;
        attribute vec3 velocity;
        attribute float lifetime;
        attribute float offsetY;
        varying vec2 vUv;
        varying vec3 vPos;
        varying float vLifetime;
        vec3 mod289(vec3 x) {
          return x - floor(x * (1.0 / 289.0)) * 289.0;
        }
        vec4 mod289(vec4 x) {
          return x - floor(x * (1.0 / 289.0)) * 289.0;
        }
        vec4 permute(vec4 x) {
          return mod289(((x*34.0)+1.0)*x);
        }
        vec4 taylorInvSqrt(vec4 r) {
          return 1.79284291400159 - 0.85373472095314 * r;
        }
        float noise(vec3 v) {
          const vec2 C = vec2(1.0/6.0, 1.0/3.0);
          const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
          vec3 i = floor(v + dot(v, C.yyy));
          vec3 x0 = v - i + dot(i, C.xxx);
          vec3 g = step(x0.yzx, x0.xyz);
          vec3 l = 1.0 - g;
          vec3 i1 = min(g.xyz, l.zxy);
          vec3 i2 = max(g.xyz, l.zxy);
          vec3 x1 = x0 - i1 + C.xxx;
          vec3 x2 = x0 - i2 + C.yyy;
          vec3 x3 = x0 - D.yyy;
          i = mod289(i);
          vec4 p = permute(permute(permute(
                        i.z + vec4(0.0, i1.z, i2.z, 1.0))
                       + i.y + vec4(0.0, i1.y, i2.y, 1.0))
                       + i.x + vec4(0.0, i1.x, i2.x, 1.0));
          float n_ = 0.142857142857;
          vec3 ns = n_ * D.wyz - D.xzx;
          vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
          vec4 x_ = floor(j * ns.z);
          vec4 y_ = floor(j - 7.0 * x_);
          vec4 x = x_ * ns.x + ns.yyyy;
          vec4 y = y_ * ns.x + ns.yyyy;
          vec4 h = 1.0 - abs(x) - abs(y);
          vec4 b0 = vec4(x.xy, y.xy);
          vec4 b1 = vec4(x.zw, y.zw);
          vec4 s0 = floor(b0)*2.0 + 1.0;
          vec4 s1 = floor(b1)*2.0 + 1.0;
          vec4 sh = -step(h, vec4(0.0));
          vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;
          vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;
          vec3 p0 = vec3(a0.xy, h.x);
          vec3 p1 = vec3(a0.zw, h.y);
          vec3 p2 = vec3(a1.xy, h.z);
          vec3 p3 = vec3(a1.zw, h.w);
          vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
          p0 *= norm.x;
          p1 *= norm.y;
          p2 *= norm.z;
          p3 *= norm.w;
          vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
          m = m * m;
          return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
        }
        mat3 rotationMatrix(vec3 axis, float angle) {
          axis = normalize(axis);
          float s = sin(angle);
          float c = cos(angle);
          float oc = 1.0 - c;
          return mat3(
            oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s,
            oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s,
            oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c
          );
        }
        float fnoise(vec3 p) {
          mat3 rot = rotationMatrix(normalize(vec3(0.0, 0.0, 1.0)), 0.5 * uTime);
          mat3 rot2 = rotationMatrix(normalize(vec3(0.0, 0.0, 1.0)), 0.3 * uTime);
          float sum = 0.0;
          vec3 r = rot * p;
          float add = noise(r);
          float msc = add + 0.7;
          msc = clamp(msc, 0.0, 1.0);
          sum += 0.6 * add;
          p = p * 2.0;
          r = rot * p;
          add = noise(r);
          add *= msc;
          sum += 0.5 * add;
          msc *= add + 0.7;
          msc = clamp(msc, 0.0, 1.0);
          p.xy = p.xy * 2.0;
          p = rot2 * p;
          add = noise(p);
          add *= msc;
          sum += 0.25 * abs(add);
          msc *= add + 0.7;
          msc = clamp(msc, 0.0, 1.0);
          p = p * 2.0;
          add = noise(p);
          add *= msc;
          sum += 0.125 * abs(add);
          p = p * 2.0;
          add = noise(p);
          add *= msc;
          sum += 0.0625 * abs(add);
          return sum * 0.516129;
        }
        float getHeight(vec3 p) {
          return 0.3 - uWaveAmp * fnoise(vec3(uNoiseScale * (p.x + 0.0 * uTime), uNoiseScale * p.z, 0.4 * uTime));
        }
        void main() {
          vUv = uv;
          vLifetime = lifetime - uTime;
          vec3 pos = position;
          // Update y position to follow waves dynamically
          float baseHeight = getHeight(vec3(position.x, 0.0, position.z));
          pos.y = baseHeight + offsetY + velocity.y * (uTime - (lifetime - vLifetime));
          if (vLifetime < 0.0) {
            pos.y = getHeight(vec3(position.x, 0.0, position.z)) + offsetY;
            vLifetime = 5.0;
          }
          vPos = pos;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
          gl_PointSize = uParticleSize * (vLifetime / 5.0);
        }
      `;

  // Particle fragment shader
  const particleFragmentShader = `
        uniform float uTime;
        uniform float uAlpha;
        uniform float uFresnelStrength;
        uniform vec3 uColorDeep;
        uniform vec3 uColorShallow;
        uniform sampler2D uTexture;
        uniform float uTextureInfluence;
        varying vec2 vUv;
        varying vec3 vPos;
        varying float vLifetime;
        vec4 getSky(vec3 rd) {
          if (rd.y > 0.3) return vec4(0.5, 0.8, 1.5, 1.0);
          if (rd.y < 0.0) return vec4(0.0, 0.2, 0.4, 1.0);
          if (rd.z > 0.9 && rd.x > 0.3) {
            if (rd.y > 0.2) return 1.5 * vec4(2.0, 1.0, 1.0, 1.0);
            return 1.5 * vec4(2.0, 1.0, 0.5, 1.0);
          }
          return vec4(0.5, 0.8, 1.5, 1.0);
        }
        void main() {
          vec3 ro = cameraPosition;
          vec3 rd = normalize(vPos - ro);
          vec3 normal = vec3(0.0, 1.0, 0.0);
          float fresnel = uFresnelStrength * pow(1.0 - clamp(dot(-rd, normal), 0.0, 1.0), 5.0) + (1.0 - uFresnelStrength);
          vec3 refVec = reflect(rd, normal);
          vec4 reflection = getSky(refVec);
          float deep = 1.0 + 0.5 * vPos.y;
          vec4 col = fresnel * reflection;
          col += deep * 0.4 * vec4(uColorDeep, 1.0);
          col = mix(col, vec4(uColorShallow, 1.0), clamp(vPos.y * 2.0 + 0.5, 0.0, 1.0));
          vec4 textureColor = texture2D(uTexture, vUv);
          col = mix(col, textureColor, uTextureInfluence);
          col = clamp(col, 0.0, 1.0);
          float alpha = uAlpha * (vLifetime / 5.0);
          gl_FragColor = vec4(col.rgb, alpha);
        }
      `;

  const particleMaterial = new THREE.ShaderMaterial({
    vertexShader: particleVertexShader,
    fragmentShader: particleFragmentShader,
    uniforms: uniforms,
    transparent: true,
  });

  const particles = new THREE.Points(particleGeometry, particleMaterial);
  scene.add(particles);

  // Lighting
  const light = new THREE.DirectionalLight(0xffffff, 1);
  light.position.set(5, 5, 5);
  scene.add(light);

  // GUI
  const gui = new GUI();
  gui.close();
  const waterFolder = gui.addFolder('Water');
  waterFolder.add(params, 'alpha', 0.0, 1.0).onChange(v => uniforms.uAlpha.value = v);
  waterFolder.add(params, 'fresnelStrength', 0.0, 1.0).onChange(v => uniforms.uFresnelStrength.value = v);
  waterFolder.add(params, 'waveAmplitude', 0.0, 8.0).onChange(v => uniforms.uWaveAmp.value = v);
  waterFolder.add(params, 'noiseScale', 0.1, 2.0).onChange(v => uniforms.uNoiseScale.value = v);
  waterFolder.addColor(params, 'colorDeep').onChange(v => uniforms.uColorDeep.value.set(v));
  waterFolder.addColor(params, 'colorShallow').onChange(v => uniforms.uColorShallow.value.set(v));
  waterFolder.add(params, 'textureInfluence', 0.0, 1.0).onChange(v => uniforms.uTextureInfluence.value = v);
  const particleFolder = gui.addFolder('Particles');
  particleFolder.add(params, 'particleCount', 100, 50000, 1).onChange(v => {
    scene.remove(particles);
    const newParticleCount = Math.floor(v);
    const newPositions = new Float32Array(newParticleCount * 3);
    const newUvs = new Float32Array(newParticleCount * 2);
    const newVelocities = new Float32Array(newParticleCount * 3);
    const newLifetimes = new Float32Array(newParticleCount);
    const newOffsetYs = new Float32Array(newParticleCount);
    for (let i = 0; i < newParticleCount; i++) {
      const i3 = i * 3;
      const i2 = i * 2;
      const x = (Math.random() - 0.5) * 10;
      const z = (Math.random() - 0.5) * 10;
      const y = 0;
      newPositions[i3] = x;
      newPositions[i3 + 1] = y;
      newPositions[i3 + 2] = z;
      newUvs[i2] = (x + 5) / 10;
      newUvs[i2 + 1] = (z + 5) / 10;
      newVelocities[i3] = 0;
      newVelocities[i3 + 1] = params.particleSpeed * (0.5 + Math.random());
      newVelocities[i3 + 2] = 0;
      newLifetimes[i] = Math.random() * 5;
      newOffsetYs[i] = Math.random() * params.particleOffsetY;
    }
    particleGeometry.setAttribute('position', new THREE.BufferAttribute(newPositions, 3));
    particleGeometry.setAttribute('uv', new THREE.BufferAttribute(newUvs, 2));
    particleGeometry.setAttribute('velocity', new THREE.BufferAttribute(newVelocities, 3));
    particleGeometry.setAttribute('lifetime', new THREE.BufferAttribute(newLifetimes, 1));
    particleGeometry.setAttribute('offsetY', new THREE.BufferAttribute(newOffsetYs, 1));
    particles.geometry = particleGeometry;
    scene.add(particles);
  });
  particleFolder.add(params, 'particleSpeed', 0.1, 2.0).onChange(v => {
    const velocities = particleGeometry.attributes.velocity.array;
    for (let i = 0; i < particleCount; i++) {
      velocities[i * 3 + 1] = v * (0.5 + Math.random());
    }
    particleGeometry.attributes.velocity.needsUpdate = true;
  });
  particleFolder.add(params, 'particleOffsetY', 0.0, 100.0).onChange(v => {
    const offsetYs = particleGeometry.attributes.offsetY.array;
    for (let i = 0; i < particleCount; i++) {
      offsetYs[i] = Math.random() * v;
    }
    particleGeometry.attributes.offsetY.needsUpdate = true;
  });
  particleFolder.add(params, 'particleSize', 2.0, 100.0).name('Particle Size').onChange(v => {
    uniforms.uParticleSize.value = v;
  });

  const bloomFolder = gui.addFolder('Bloom');
  bloomFolder.add(params, 'bloomStrength', 0.0, 3.0).onChange(v => bloomPass.strength = v);
  bloomFolder.add(params, 'bloomRadius', 0.0, 1.0).onChange(v => bloomPass.radius = v);
  bloomFolder.add(params, 'bloomThreshold', 0.0, 1.0).onChange(v => bloomPass.threshold = v);

  // Resize handler
  window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
    uniforms.uResolution.value.set(window.innerWidth, window.innerHeight);
    composer.setSize(window.innerWidth, window.innerHeight);
  });

  // Animation loop
  function animate() {
    requestAnimationFrame(animate);
    uniforms.uTime.value = performance.now() / 1000;
    const lifetimes = particleGeometry.attributes.lifetime.array;
    for (let i = 0; i < particleCount; i++) {
      lifetimes[i] -= 1 / 60; // Assuming 60 FPS
      if (lifetimes[i] < 0) {
        lifetimes[i] = 5.0;
      }
    }
    particleGeometry.attributes.lifetime.needsUpdate = true;
    controls.update();
    composer.render();
  }
  animate();
</script>
</body>
</html>

效果如下

参考:源码

相关推荐
陶甜也7 天前
threeJS 实现开花的效果
前端·vue·blender·threejs
二川bro10 天前
第25节:VR基础与WebXR API入门
前端·3d·vr·threejs
哈哈地图13 天前
three.js手机端的4种旋转方式
threejs·手机交互
咔咔一顿操作14 天前
第五章 vue3 + Three.js 实现高级镜面反射效果案例解析
前端·javascript·vue.js·人工智能·信息可视化·threejs
咔咔一顿操作14 天前
第六章 Vue3 + Three.js 实现高质量全景图查看器:从基础到优化
开发语言·javascript·人工智能·ecmascript·threejs
浩浩乎@23 天前
【openGLES】着色器语言(GLSL)
人工智能·算法·着色器
二川bro1 个月前
第八篇:交互入门:鼠标拾取物体
前端·交互·threejs
枫景Maple1 个月前
Shader开发(六)什么是着色器
游戏引擎·着色器
救救孩子把2 个月前
Three.js 从零入门:构建你的第一个 Web 3D 世界
前端·javascript·3d·threejs