Three.js 着色器打造烟雾水云效果

136 阅读3分钟

概述

本文将详细介绍如何使用 Three.js 和自定义着色器来创建逼真的烟雾、水和云朵效果。我们将通过编写顶点着色器和片元着色器来实现动态变化的视觉效果。

screenshot_2026-01-23_11-40-16.gif

准备工作

首先,我们需要引入必要的 Three.js 库和相关工具:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import gsap from "gsap";
import * as dat from "dat.gui";
import vertexShader from "../shaders/water/vertex.glsl";
import fragmentShader from "../shaders/water/fragment.glsl";

场景初始化

首先,我们需要创建一个基本的 Three.js 场景:

// 初始化场景
const scene = new THREE.Scene();

// 创建透视相机
const camera = new THREE.PerspectiveCamera(
  90,
  window.innerHeight / window.innerHeight,
  0.1,
  1000
);

// 设置相机位置
camera.position.set(0, 0, 2);
scene.add(camera);

// 加入辅助轴,帮助我们查看3维坐标轴
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

参数配置

为了方便调试和调整效果,我们使用 dat.GUI 创建参数控制面板:

const params = {
  uWaresFrequency: 14,          // 波纹频率
  uScale: 0.03,                 // 整体缩放
  uXzScale: 1.5,                // XZ平面缩放
  uNoiseFrequency: 10,          // 噪声频率
  uNoiseScale: 1.5,             // 噪声缩放
  uLowColor: "#ff0000",         // 低值颜色
  uHighColor: "#ffff00",        // 高值颜色
  uXspeed: 1,                   // X方向速度
  uZspeed: 1,                   // Z方向速度
  uNoiseSpeed: 1,               // 噪声速度
  uOpacity: 1                   // 透明度
};

着色器材质创建

接下来,我们创建一个使用自定义着色器的材质:

const shaderMaterial = new THREE.ShaderMaterial({
  vertexShader: vertexShader,
  fragmentShader: fragmentShader,
  side: THREE.DoubleSide,
  uniforms: {
    uWaresFrequency: {
      value: params.uWaresFrequency,
    },
    uScale: {
      value: params.uScale,
    },
    uNoiseFrequency: {
      value: params.uNoiseFrequency,
    },
    uNoiseScale: {
      value: params.uNoiseScale,
    },
    uXzScale: {
      value: params.uXzScale,
    },
    uTime: {
      value: 0,  // 时间变量,用于动画效果
    },
    uLowColor: {
      value: new THREE.Color(params.uLowColor),
    },
    uHighColor: {
      value: new THREE.Color(params.uHighColor),
    },
    uXspeed: {
      value: params.uXspeed,
    },
    uZspeed: {
      value: params.uZspeed,
    },
    uNoiseSpeed: {
      value: params.uNoiseSpeed,
    },
    uOpacity: {
      value: params.uOpacity,
    },
  },
  transparent: true,
});

控制面板设置

通过 dat.GUI 可以实时调整参数,观察效果变化:

gui
  .add(params, "uWaresFrequency")
  .min(1)
  .max(100)
  .step(0.1)
  .onChange((value) => {
    shaderMaterial.uniforms.uWaresFrequency.value = value;
  });

gui
  .add(params, "uScale")
  .min(0)
  .max(0.2)
  .step(0.001)
  .onChange((value) => {
    shaderMaterial.uniforms.uScale.value = value;
  });

// 更多参数控制...

几何体创建

创建一个平面几何体作为基础形状:

const plane = new THREE.Mesh(
  new THREE.PlaneBufferGeometry(1, 1, 1024, 1024),
  shaderMaterial
);
plane.rotation.x = -Math.PI / 2;

scene.add(plane);

渲染循环

最后,我们需要在渲染循环中更新时间参数,以实现动画效果:

const clock = new THREE.Clock();

function animate(t) {
  const elapsedTime = clock.getElapsedTime();
  shaderMaterial.uniforms.uTime.value = elapsedTime;
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

animate();

着色器详解

顶点着色器 (Vertex Shader)

顶点着色器负责计算每个顶点的位置变化,创建波浪效果:

precision lowp float;
uniform float uWaresFrequency;    // 波纹频率
uniform float uScale;             // 缩放系数
uniform float uNoiseFrequency;    // 噪声频率
uniform float uNoiseScale;        // 噪声缩放
uniform float uXzScale;           // XZ平面缩放
uniform float uTime;              // 时间变量
uniform float uXspeed;            // X方向速度
uniform float uZspeed;            // Z方向速度
uniform float uNoiseSpeed;        // 噪声速度

// 计算出的高度传递给片元着色器
varying float vElevation;

// 随机函数
float random (vec2 st) {
    return fract(sin(dot(st.xy,vec2(12.9898,78.233)))*43758.5453123);
}

// 噪声函数
float noise (in vec2 _st) {
    vec2 i = floor(_st);
    vec2 f = fract(_st);

    float a = random(i);
    float b = random(i + vec2(1.0, 0.0));
    float c = random(i + vec2(0.0, 1.0));
    float d = random(i + vec2(1.0, 1.0));

    vec2 u = f * f * (3.0 - 2.0 * f);

    return mix(a, b, u.x) +
            (c - a)* u.y * (1.0 - u.x) +
            (d - b) * u.x * u.y;
}

void main(){
    vec4 modelPosition = modelMatrix * vec4(position,1.0);

    // 计算波浪高度
    float elevation = sin(modelPosition.x*uWaresFrequency+uTime*uXspeed)*sin(modelPosition.z*uWaresFrequency*uXzScale+uTime*uZspeed);

    // 添加噪声效果
    elevation += -abs(cnoise(vec2(modelPosition.xz*uNoiseFrequency+uTime*uNoiseSpeed))) *uNoiseScale;
    
    vElevation = elevation;
    
    elevation *= uScale;

    modelPosition.y += elevation;

    gl_Position = projectionMatrix * viewMatrix *modelPosition;
}

片元着色器 (Fragment Shader)

片元着色器负责计算每个像素的颜色:

precision lowp float;

uniform vec3 uHighColor;    // 高值颜色
uniform vec3 uLowColor;     // 低值颜色
varying float vElevation;   // 从顶点着色器传入的高度值
uniform float uOpacity;     // 透明度

void main(){
    // 根据高度值计算颜色插值因子
    float a = (vElevation+1.0)/2.0;
    vec3 color = mix(uLowColor,uHighColor,a);
    gl_FragColor = vec4(color,uOpacity);
}

总结

通过使用自定义着色器,我们可以创建非常复杂的视觉效果,如烟雾、水波和云朵等。关键在于理解顶点着色器如何改变几何体的形状,以及片元着色器如何控制最终的颜色输出。配合参数控制面板,我们可以实时调整各种参数,获得不同的视觉效果。

这种技术在游戏开发、模拟仿真和艺术创作等领域有着广泛的应用。掌握着色器编程是实现高级视觉效果的重要技能。