许多人喜欢采用粒子来实现下雨和下雪的效果,但粒子数量过多时会影响渲染效率,我这里采用了帧缓冲,在渲染结果前面添加一层平面蒙板,只需要一个平面就能实现雨雪雾,而且有效地减少性能消耗。
1.FramebufferTexture帧缓冲贴图
将webgl渲染的结果作为一张贴图用在另外的场景元素中。
关闭renderer自动清理
this.renderer.autoClear = false;
添加两个场景
第一个场景跟平常一样设置就行
const tex = new THREE.TextureLoader().load(`./assets/image.jpg`);
tex.wrapS = THREE.RepeatWrapping;
tex.wrapT = THREE.RepeatWrapping;
const material = new THREE.MeshStandardMaterial({
map: tex
});
this.geometries = [];
const helper = new THREE.Object3D();
//生成50个随机建筑
for (let i = 0; i < 50; i++) {
const h = Math.round(Math.random() * 3) + 1;
const x = Math.round(Math.random() * 8);
const y = Math.round(Math.random() * 8);
helper.position.set((x % 2 ? -1 : 1) * x, h * 0.5, (y % 2 ? -1 : 1) * y);
const geometry = new THREE.BoxGeometry(1, h, 1);
helper.updateWorldMatrix(true, false);
geometry.applyMatrix4(helper.matrixWorld);
this.geometries.push(geometry);
}
const mergedGeometry = BufferGeometryUtils.mergeGeometries(this.geometries, false);
const cube = new THREE.Mesh(mergedGeometry, material);
//接收投影
cube.receiveShadow = true;
//产生投影
cube.castShadow = true;
this.scene.add(cube);
为了区别第一个场景,第二个场景采用正交投影,作为接收第一个场景渲染的结果作为贴图显示。
const width = this.container.offsetWidth;
const height = this.container.offsetHeight;
const sceneOrtho = new THREE.Scene();
this.sceneOrtho = sceneOrtho;
//正交相机
const cameraOrtho = new THREE.OrthographicCamera(
-width / 2,
width / 2,
height / 2,
-height / 2,
1,
10
);
//正交相机位置
cameraOrtho.position.z = 10;
//正交相机范围
cameraOrtho.left = -width / 2;
cameraOrtho.right = width / 2;
cameraOrtho.top = height / 2;
cameraOrtho.bottom = -height / 2;
this.cameraOrtho = cameraOrtho;
//创建帧缓冲贴图 width * dpr, height * dpr 贴图大小
const dpr = window.devicePixelRatio;
this.texture = new THREE.FramebufferTexture(width * dpr, height * dpr);
const g = new THREE.PlaneGeometry(width * dpr, height * dpr);
{//添加一个平面显示帧缓冲贴图
const mesh = new THREE.Mesh(
g,
new THREE.MeshBasicMaterial({
map: this.texture,
transparent: true
})
);
mesh.scale.set(0.5, 0.5, 0.5);
mesh.position.set(-width * 0.25, height * 0.25, 0);
sceneOrtho.add(mesh);
}
渲染
animate() {
//清空画布
this.renderer.clear();
//渲染第一个场景
this.renderer.render(this.scene, this.camera);
if (this.texture) {
//清空深度缓存
this.renderer.clearDepth();
//将第一个场景的结果渲染到帧缓冲贴图里
// this.vector = new THREE.Vector2();//开始坐标
//
this.renderer.copyFramebufferToTexture(this.vector, this.texture);
//渲染第二个场景
this.renderer.render(this.sceneOrtho, this.cameraOrtho);
}
this.threeAnim = requestAnimationFrame(this.animate.bind(this));
}
.copyFramebufferToTexture ( position : Vector2, texture : FramebufferTexture, level : Number )
将当前WebGLFramebuffer中的像素复制到2D纹理中
- position:开始坐标,贴图范围会根据FramebufferTexture大小和坐标决定
- level:详细级别:级别0是基本图像级别,级别n是第n个mipmap缩减级别。
可以看到第一个场景同步渲染到第二个场景的帧缓冲贴图中。
2.叠加雨雪雾
在第二个场景中帧缓冲平面前面添加一个蒙板平面
帧缓冲平面大小填满容器,同样大小的蒙板平面在其前面
{//帧缓冲平面
const mesh = new THREE.Mesh(
g,
new THREE.MeshBasicMaterial({
map: this.texture,
transparent: true
})
);
mesh.position.set(0, 0, 0);
sceneOrtho.add(mesh);
}
{//蒙板平面
this.material = new THREE.MeshBasicMaterial({
color: 0xff0000,
transparent: 0,
opacity: 0.5
});
const mesh = new THREE.Mesh(g, this.material);
mesh.position.set(0, 0, 1);
sceneOrtho.add(mesh);
this.weatherMesh = mesh;
}
添加下雨效果
顶点着色器
varying vec2 vUv;
void main(){
vUv=uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
下雨片元着色器,
varying vec2 vUv;
uniform float iTime;//随时间变化
uniform float rainSpeed;//下雨速度
uniform float rainAngle;//下雨倾斜角度
float PI = 3.1415926;
//随机数
float random(in vec2 uv) {
return fract(sin(dot(uv.xy, vec2(12.9898, 78.233))) *
43758.5453123);
}
//噪声函数
float noise(in vec2 uv) {
vec2 i = floor(uv);
vec2 f = fract(uv);
f = f * f * (3. - 2. * f);
float lb = random(i + vec2(0., 0.));
float rb = random(i + vec2(1., 0.));
float lt = random(i + vec2(0., 1.));
float rt = random(i + vec2(1., 1.));
return mix(mix(lb, rb, f.x), mix(lt, rt, f.x), f.y);
}
//下雨效果
float rain(vec2 uv) {
float travelTime = (iTime * 0.2) + 0.1;
vec2 tiling = vec2(1., .01);
vec2 offset = vec2(travelTime * 0.5 + uv.x * 0.2, travelTime * 0.2);
vec2 st = uv * tiling + offset;
float rain = 0.1;
float f = fract(noise(st * 200.5) * noise(st * 125.5) * rainSpeed);
f = clamp(pow(abs(f), 15.0) * 1.5 * (rain * rain * 125.0), 0.0, 0.25);
return f;
}
//旋转角度
mat2 rotate2d(float angle) {
return mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
}
void main() {
mat2 a = rotate2d(rainAngle * PI);
float rain = rain(vUv * a);
if(rain <= 0.0) {
discard;
return;
}
vec3 col = vec3(1.0);
gl_FragColor = vec4(col, rain);
}
下雨材质
const rainMaterial = new THREE.ShaderMaterial({
transparent: true,
uniforms: {
rainSpeed: { value: 3.0 },//下雨速度
iTime: { value: 1.0 },//随时间变化
rainAngle: { value: -0.1 }//下雨倾斜角度
},
vertexShader: ``,
fragmentShader:``,
});
让雨水动起来
animate(){
//...
if (this.material) {
this.material.uniforms.iTime.value += 0.01;
}
//...
}
可以改变rainSpeed
可调整下雨大速度大小.
添加下雪效果
顶点着色器同上,下雪的片元着色器如下,
varying vec2 vUv;
uniform float iTime;
void main() {
float snow = 0.0;
for(int k = 0; k < 6; k++) {
for(int i = 0; i < 12; i++) {
float cellSize = 2.0 + (float(i) * 3.0);
float downSpeed = 0.3 + (sin(iTime * 0.4 + float(k + i * 20)) + 1.0) * 0.00008;
vec2 uv = vUv + vec2(0.01 * sin((iTime + float(k * 6185)) * 0.6 + float(i)) * (5.0 / float(i)), downSpeed * (iTime + float(k * 1352)) * (1.0 / float(i)));
vec2 uvStep = (ceil((uv) * cellSize - vec2(0.5, 0.5)) / cellSize);
float x = fract(sin(dot(uvStep.xy, vec2(12.9898 + float(k) * 12.0, 78.233 + float(k) * 315.156))) * 43758.5453 + float(k) * 12.0) - 0.5;
float y = fract(sin(dot(uvStep.xy, vec2(62.2364 + float(k) * 23.0, 94.674 + float(k) * 95.0))) * 62159.8432 + float(k) * 12.0) - 0.5;
float randomMagnitude1 = sin(iTime * 2.5) * 0.7 / cellSize;
float randomMagnitude2 = cos(iTime * 2.5) * 0.7 / cellSize;
float d = 5.0 * distance((uvStep.xy + vec2(x * sin(y), y) * randomMagnitude1 + vec2(y, x) * randomMagnitude2), uv.xy);
float omiVal = fract(sin(dot(uvStep.xy, vec2(32.4691, 94.615))) * 31572.1684);
if(omiVal < 0.08 ? true : false) {
float newd = (x + 1.0) * 0.4 * clamp(1.9 - d * (15.0 + (x * 6.3)) * (cellSize / 1.4), 0.0, 1.0);
snow += newd;
}
}
}
gl_FragColor = vec4(1.0, 1.0, 1.0, snow);
}
这里必须来一句“雪花飘飘北风萧萧天地一片苍茫~”应应景
添加云雾的效果
顶点着色器同上,云雾的片元着色器如下,
float cloudDensity = 1.0; // overall density [0,1]
float noisiness = 0.35; // overall strength of the noise effect [0,1]
float speed = 0.1; // controls the animation speed [0, 0.1 ish)
float cloudHeight = 0.5; // (inverse) height of the input gradient [0,...)
uniform float iTime;
uniform float fogSpeed;
uniform float fogOpacity;
varying vec2 vUv;
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 snoise(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);
// First corner
vec3 i = floor(v + dot(v, C.yyy) );
vec3 x0 = v - i + dot(i, C.xxx) ;
// Other corners
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 );
// x0 = x0 - 0.0 + 0.0 * C.xxx;
// x1 = x0 - i1 + 1.0 * C.xxx;
// x2 = x0 - i2 + 2.0 * C.xxx;
// x3 = x0 - 1.0 + 3.0 * C.xxx;
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
// Permutations
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 ));
// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
float n_ = 0.142857142857; // 1.0/7.0
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)
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 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
//vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
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);
//Normalise gradients
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;
// Mix final noise value
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) ) );
}
/// Cloud stuff:
const float maximum = 1.0/1.0 + 1.0/2.0 + 1.0/3.0 + 1.0/4.0 + 1.0/5.0 + 1.0/6.0 + 1.0/7.0 + 1.0/8.0;
// Fractal Brownian motion, or something that passes for it anyway: range [-1, 1]
float fBm(vec3 uv)
{
float sum = 0.0;
for (int i = 0; i < 8; ++i) {
float f = float(i+1);
sum += snoise(uv*f) / f;
}
return sum / maximum;
}
// Simple vertical gradient:
float gradient(vec2 uv) {
return (1.0 - uv.y * uv.y * cloudHeight);
}
void main() {
vec2 uv = vUv;
vec3 p = vec3(uv, iTime * speed);
vec3 offset = vec3(sin(iTime * fogSpeed*3.0), sin(iTime * fogSpeed), sin(iTime * fogSpeed*2.0));
vec2 duv = vec2(fBm(p), fBm(p + offset)) * noisiness;
float q = gradient(uv + duv) * cloudDensity;
if(q <= 0.0) {
discard;
return;
}
gl_FragColor = vec4(vec3(1.0), q * fogOpacity);
}
刮风起雾材质
const fogMaterial = new THREE.ShaderMaterial({
transparent: true,
uniforms: {
iTime: { value: 1.0 },//随时间变化
fogOpacity: { value: 1.0 },//雾气透明度
fogSpeed: { value: 1.0 }//雾运动的速度
},
vertexShader: ``,
fragmentShader:``,
});
可以通过fogOpacity
调整雾气透明度和fogSpeed
调整雾气运动速度。
Github地址
https://github.com/xiaolidan00/my-earth