предисловие
Всем привет, это волшебник CSS и WebGL - alphardex.
В этой статье мы будем использовать three.js для реализации эффекта грозового облака.
Давайте начнем!
Предварительные знания
Чтобы добиться этого эффекта, давайте сначала кратко рассмотрим FBM.
FBM означает фрактальное броуновское движение на китайском языке, а другое название — фрактальный шум (что указывает на то, что это также тип шума). Его часто используют для изображения различных форм природы (гор, облаков, рек и т. д.). Идея состоит в том, чтобы накладывать шум несколько раз (часто 6 раз, что эквивалентно октаве) в цикле for и увеличивать частоту и уменьшать амплитуду при наложении. Ниже приводитсяПростая реализация FBM шаблона шума
#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: Нажмите вилку в правом нижнем углу, чтобы скопировать копию.
Чтобы модульизовать шейдер, нам нужно использоватьglslify
Вам также необходимо установить следующие пакеты npm:glsl-noise
положительный
Построение сцены
Создайте плоскую поверхность, которая заполняет экран как холст
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;
}
}
}
Вы можете напрямую использовать вершинный шейдер по умолчанию.
фрагментный шейдер
По идее тоже базовый метод записи фбм, но он наносится 16 раз подряд на внешний слой (при этом сжигается видеокарта в частности, но эффект очень классный, и это все делается), а ось абсцисс добавляется смещение во времени.
#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.);
}
Окончательный эффект выглядит следующим образом