当前位置: 首页 > article >正文

使用 Three.js 实现雪花特效

大家好!我是 [数擎 AI],一位热爱探索新技术的前端开发者,在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情,欢迎关注我的文章,我们一起成长、进步!
开发领域:前端开发 | AI 应用 | Web3D | 元宇宙
技术栈:JavaScript、React、ThreeJs、WebGL、Go
经验经验:6 年+ 前端开发经验,专注于图形渲染和 AI 技术
演示地址:下雪特效
开源项目:智简未来、晓智元宇宙、数字孪生引擎、源码地址

演示地址:https://shader.shuqin.cc/xisbz1
源码地址:https://github.com/dezhizhang/shadertoy

一、效果展示与实现原理

本教程实现了一个基于分形算法生成的动态雪花场景,主要技术特点包括:

  • 极坐标变换:通过笛卡尔坐标系与极坐标系的相互转换

  • 分形场叠加:使用多层 cos 函数构建复杂图案

  • 时空变换:通过时间变量实现动态飘落效果

  • 多重采样:多层级叠加创造丰富细节

二、核心算法解析

2.1 坐标系转换

vec2 p_to_pc(vec2 p) {
    return vec2(atan(p.y, p.x), length(p));
}

vec2 pc_to_p(vec2 pc) {
    return vec2(pc.y * cos(pc.x), pc.y * sin(pc.x));
}

这里实现了笛卡尔坐标系与极坐标系的相互转换,是生成旋转对称图案的基础。

2.2 分形场生成

vec2 fieldB(vec2 pc, float f) {
    pc.y += 0.1 * cos(pc.y * 100.0 + 10.0);
    pc.y += 0.1 * cos(pc.y * 20.0 + f);
    pc.y += 0.04 * cos(pc.y * 10.0 + 10.0);
    return pc;
}

通过多层余弦函数叠加,创造复杂的分形图案。参数 f 可用于生成不同的雪花变体。

2.3 动态效果

p.y += 2.0 * time;
p.x += 0.01 * cos(p.y * 3.0 + p.x * 3.0 + time * PI2);

通过时间变量实现雪花的垂直运动和水平摆动效果。

三、Three.js 集成要点

3.1 着色器材质配置

const material = new THREE.ShaderMaterial({
  fragmentShader: snowflakeShader.fragmentShader,
  vertexShader: snowflakeShader.vertexShader,
  uniforms: {
    iTime: { value: 0 },
    iResolution: { value: new THREE.Vector2() },
  },
});

关键 Uniform 参数:

  • iTime:时间计数器

  • iResolution:画布分辨率

3.2 响应式处理

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

确保画面在不同分辨率设备上正确显示。

四、效果优化技巧

  • 增加雪花层级:修改 snow 函数中的重复次数
col += 0.4 * snow(uv * 3.0);
col += 0.3 * snow(uv * 6.0);

改变运动速度:调整时间系数

p.y += 1.5 * time; // 修改系数控制下落速度

颜色定制:修改基础背景色

vec4 col = vec4(0.2, 0.4, 0.7, 1.0); // RGBA颜色值

五、完整代码

import * as THREE from 'three';

const snowflakeShader = {
  uniforms: {
    iTime: { value: 0 },
    iResolution: { value: new THREE.Vector2(1, 1) },
  },

  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,

  fragmentShader: `
    precision highp float;
    varying vec2 vUv;
    uniform vec2 iResolution;
    uniform float iTime;

    #define PI2 6.28
    #define PI 3.1416

    vec2 p_to_pc(vec2 p) {
      return vec2(atan(p.y, p.x), length(p));
    }

    vec2 pc_to_p(vec2 pc) {
      return vec2(pc.y * cos(pc.x), pc.y * sin(pc.x));
    }

    vec2 fieldA(vec2 pc) {
      pc.y += 0.02 * floor(cos(pc.x * 6.0) * 5.0);
      pc.y += 0.01 * floor(10.0 * cos(pc.x * 30.0));
      pc.y += 0.5 * cos(pc.y * 10.0);
      return pc;
    }

    vec2 fieldB(vec2 pc, float f) {
      pc.y += 0.1 * cos(pc.y * 100.0 + 10.0);
      pc.y += 0.1 * cos(pc.y * 20.0 + f);
      pc.y += 0.04 * cos(pc.y * 10.0 + 10.0);
      return pc;
    }

    vec4 snow_flake(vec2 p, float f) {
      vec4 col = vec4(0.0);
      vec2 pc = p_to_pc(p * 10.0);
      pc = fieldA(fieldB(pc, f));
      p = pc_to_p(pc);
      float d = length(p);
      if(d < 0.3) {
        col.rgba += vec4(1.0);
      }
      return col;
    }

    vec4 snow(vec2 p) {
      vec4 col = vec4(0.0);
      float time = iTime / 2.0;
      p.y += 2.0 * time;
      p = fract(p + 0.5) - 0.5;
      p *= 1.0;
      p.x += 0.01 * cos(p.y * 3.0 + p.x * 3.0 + time * PI2);
      
      col += snow_flake(p, 1.0);
      col += snow_flake(p + vec2(0.2, -0.1), 4.0);
      col += snow_flake(p * 2.0 + vec2(-0.4, -0.5), 5.0);
      col += snow_flake(p * 1.0 + vec2(-0.2, 0.4), 9.0);
      col += 2.0 * snow_flake(p * 1.0 + vec2(0.4, -0.4), 5.0);
      col += snow_flake(p * 1.0 + vec2(-1.2, 1.2), 9.0);
      col += snow_flake(p * 1.0 + vec2(2.4, -1.2), 5.0);
      col += snow_flake(p * 1.0 + vec2(-1.2, 1.1), 9.0);
      
      return col;
    }

    void main() {
      vec2 uv = (vUv - 0.5) * vec2(iResolution.x / iResolution.y, 1.0) * 2.0;
      vec4 col = vec4(0.2, 0.4, 0.7, 1.0);
      
      col += 0.3 * snow(uv * 2.0);
      col += 0.2 * snow(uv * 4.0 + vec2(iTime / 2.0, 0.0));
      col += 0.1 * snow(uv * 8.0);
      
      gl_FragColor = col;
    }
  `,
};

// 使用示例
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000,
);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const geometry = new THREE.PlaneGeometry(2, 2);
const material = new THREE.ShaderMaterial({
  fragmentShader: snowflakeShader.fragmentShader,
  vertexShader: snowflakeShader.vertexShader,
  uniforms: {
    iTime: { value: 0 },
    iResolution: {
      value: new THREE.Vector2(window.innerWidth, window.innerHeight),
    },
  },
});

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

camera.position.z = 1;

function animate() {
  requestAnimationFrame(animate);
  material.uniforms.iTime.value = performance.now() / 1000;
  material.uniforms.iResolution.value.set(
    window.innerWidth,
    window.innerHeight,
  );
  renderer.render(scene, camera);
}

animate();

// 处理窗口大小变化
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
  material.uniforms.iResolution.value.set(
    window.innerWidth,
    window.innerHeight,
  );
});

六、结语

本文实现的分形雪花效果展示了 WebGL 在复杂图形渲染方面的强大能力。通过调整分形参数、颜色值和运动函数,开发者可以创造出无限多样的冬日场景效果。这种基于数学函数生成图形的方式,在粒子系统、背景特效等领域都有广泛应用前景。


http://www.kler.cn/a/535512.html

相关文章:

  • html的字符实体和颜色表示
  • 【deepseek实战】绿色好用,不断网
  • 【漫画机器学习】083.安斯库姆四重奏(Anscombe‘s Quartet)
  • C基础寒假练习(6)
  • LabVIEW2025中文版软件安装包、工具包、安装教程下载
  • 【QT笔记】使用QScrollArea实现多行文本样式显示
  • 基于 Ollama+Docker+OpenWebUI 的本地化部署deepseek流程
  • 【Elasticsearch】diversified sampler
  • Unity 加载OSGB(webgl直接加载,无需转换格式!)
  • Ansible服务介绍
  • 问卷调查系统Two-Step-Kmeans-前端后端搭建完成
  • 云计算行业分析
  • 架构师成长(四)之深入理解 JVM 虚拟机栈
  • 基于Qt开发FFMpeg遇到的编译错误问题
  • uniapp使用uv-popup弹出框隐藏底部导航tabbar方法
  • Oracle常用响应文件介绍(19c)
  • ES与数据库应用浅探究
  • Go 语言 | 入门 | 快速入门
  • 主动管理的基本概念
  • el-table中的某个字段最多显示两行,超出部分显示“...详情”,怎么办
  • Tomcat Request Cookie 丢失问题
  • [论文笔记] Deepseek-R1R1-zero技术报告阅读
  • Java全栈项目-在线实验报告系统开发实践
  • Git仓库托管基本使用_01
  • MybatisPlus较全常用复杂查询引例(limit、orderby、groupby、having、like...)
  • C++:内存泄漏