前言
大家好,这里是 CSS 兼 WebGL 魔法使——alphardex。
本文我们将用 three.js 来实现风暴云特效,以下是最终实现的效果图
让我们开始吧!
预备知识
为了实现这个特效,我们先简要了解一下 FBM 吧
FBM 中文意思是分形布朗运动,另一种称呼是分形噪声(说明它也属于噪声的一种)。它常用于描绘各种自然之中的形状(山脉、云层、河流等)。概念是在一个 for 循环内叠加几次噪声(往往是 6 次,相当于一个八度 octave),并在叠加的同时升高频率,降低振幅。以下是一个简易的 fbm 实现的噪声图案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #pragma glslify:centerUv=require(../modules/centerUv) #pragma glslify:snoise=require(glsl-noise/simplex/2d)
uniform float uTime; uniform vec2 uMouse; uniform vec2 uResolution;
varying vec2 vUv; varying vec3 vPosition;
#define OCTAVES 6
float fbm(vec2 p){ float sum=0.; float amp=.5; for(int i=0;i<OCTAVES;i++){ float noise=snoise(p)*amp; sum+=noise; p*=2.; amp*=.5; } return sum; }
void main(){ vec2 cUv=centerUv(vUv,uResolution); vec2 p=cUv*3.; float noise=fbm(p); vec3 color=vec3(noise); gl_FragColor=vec4(color,1.); }
|
准备工作
笔者的three.js 模板:点击右下角的 fork 即可复制一份
为了将着色器模块化,需要用到glslify
同时也需要安装如下的 npm 包:glsl-noise
正片
场景搭建
创建一张铺满屏幕的平面,作为画布
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| class CloudySky extends Base { clock!: THREE.Clock; cloudySkyMaterial!: THREE.ShaderMaterial; params!: any; constructor(sel: string, debug: boolean) { super(sel, debug); this.clock = new THREE.Clock(); this.cameraPosition = new THREE.Vector3(0, 0, 1); this.params = { velocity: 5, skyColor: "#324678", }; } init() { this.createScene(); this.createOrthographicCamera(); this.createRenderer(); this.createCloudySkyMaterial(); this.createPlane(); this.createLight(); this.trackMousePos(); this.addListeners(); this.setLoop(); } createCloudySkyMaterial() { const cloudySkyMaterial = new THREE.ShaderMaterial({ vertexShader: cloudySkyVertexShader, fragmentShader: cloudySkyFragmentShader, side: THREE.DoubleSide, uniforms: { uTime: { value: 0, }, uMouse: { value: new THREE.Vector2(0, 0), }, uResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight), }, uVelocity: { value: this.params.velocity, }, uSkyColor: { value: new THREE.Color(this.params.skyColor), }, }, }); this.cloudySkyMaterial = cloudySkyMaterial; this.shaderMaterial = cloudySkyMaterial; } createPlane() { const geometry = new THREE.PlaneBufferGeometry(2, 2, 100, 100); const material = this.cloudySkyMaterial; this.createMesh({ geometry, material, }); } update() { const elapsedTime = this.clock.getElapsedTime(); const mousePos = this.mousePos; if (this.cloudySkyMaterial) { this.cloudySkyMaterial.uniforms.uTime.value = elapsedTime; this.cloudySkyMaterial.uniforms.uMouse.value = mousePos; } } }
|
顶点着色器直接用默认的就可以了
片元着色器
思路也是基本的 fbm 写法,只是在外层连续应用了 16 次(这样特别烧显卡,但是实现的效果很炫酷,帅就完事了),并且加上了随着时间的 x 轴位移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #pragma glslify:centerUv=require(../modules/centerUv) #pragma glslify:snoise=require(glsl-noise/simplex/3d) #pragma glslify:invert=require(../modules/invert)
uniform float uTime; uniform vec2 uMouse; uniform vec2 uResolution; uniform float uVelocity; uniform vec3 uSkyColor;
varying vec2 vUv; varying vec3 vPosition;
#define OCTAVES 6
float fbm(vec3 p){ float sum=0.; float amp=1.; for(int i=0;i<OCTAVES;i++){ vec3 r=p/amp*.2; float noise=snoise(r)*amp; sum+=noise; amp*=.5; } return sum; }
void main(){ vec2 cUv=centerUv(vUv,uResolution); vec2 p=cUv; vec3 ray=vec3(0.); vec3 eye=normalize(vec3(p,2.)); float displacement=uTime*uVelocity; ray.x+=displacement; float cloud=0.; float sum=0.; for(int i=0;i<16;i++){ ray+=eye; sum=fbm(ray); sum=clamp(sum,0.,1.)*.1; cloud+=sum; } vec3 color=uSkyColor+cloud; gl_FragColor=vec4(color,1.); }
|
最终效果如下
项目地址
Cloudy Sky