HumanBreak/src/module/weather/rain.ts

213 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
Shader,
ShaderProgram,
MotaRenderer,
Container,
IShaderUniform,
UniformType
} from '@motajs/render';
import { IWeather } from './weather';
const rainVs = /* glsl */ `
in vec2 a_rainVertex;
in vec2 a_offset; // 雨滴的中心位置
in vec4 a_data; // x: 雨滴宽度; y: 雨滴长度; z: 雨滴旋转角度0表示向下逆时针为正;
// w: 属于哪一种雨需要两种雨反复循环一种无法实现循环0表示第一种1表示第二种
uniform float u_progress; // 下雨进度从最上落到最下是0.5个进度,以保证不会出现奇怪的问题
out vec2 v_center;
out vec2 v_data; // 雨滴宽度与高度
out vec2 v_pos;
mat2 createScaleMatrix(float x, float y) {
return mat2(
x, 0,
0, y
);
}
vec2 getOffsetByProgress(vec2 offset) {
if (a_data.w == 0.0) {
if (u_progress < 0.5) {
return offset * u_progress * 2.0;
} else {
return offset * (u_progress - 1.0) * 2.0;
}
} else {
return offset * u_progress * 2.0;
}
}
void main() {
float cosTheta = cos(a_data.z);
float sinTheta = sin(a_data.z);
mat2 rotate = mat2(
cosTheta, -sinTheta,
sinTheta, cosTheta
);
vec2 offset = getOffsetByProgress(vec2(-sinTheta * 2.0, -cosTheta * 2.0));
mat2 scale = createScaleMatrix(a_data.x, a_data.y);
vec2 off = a_offset + offset;
vec2 pos = rotate * scale * a_rainVertex + off;
v_center = off;
v_pos = pos;
gl_Position = vec4(pos, 0.0, 1.0);
}
`;
const rainFs = /* glsl */ `
in vec2 v_center;
in vec2 v_data; // 雨滴的宽度与长度
in vec2 v_pos;
uniform vec4 u_color; // 雨滴的颜色
out vec4 outColor;
float random(vec2 uv) {
return fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453);
}
void main() {
float ran = random(v_pos);
vec2 pos = vec2(v_pos.x + ran * 0.01, v_pos.y);
vec2 texPos = (pos + 1.0) / 2.0;
texPos.y = 1.0 - texPos.y;
vec4 tex = texture(u_sampler, texPos);
outColor = mix(u_color, tex, 0.9);
}
`;
/** 雨滴顶点坐标 */
const vertex = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
export class RainWeather implements IWeather {
readonly shader: RainShader;
readonly program: ShaderProgram;
private progress: IShaderUniform<UniformType.Uniform1f> | null = null;
constructor(readonly level: number = 5) {
const shader = new RainShader();
const gl = shader.gl;
shader.size(480, 480);
shader.setHD(true);
shader.setZIndex(100);
const program = shader.createProgram(ShaderProgram);
program.fs(rainFs);
program.vs(rainVs);
program.requestCompile();
const pos = program.defineAttribArray('a_rainVertex');
program.defineAttribArray('a_offset');
program.defineAttribArray('a_data');
program.defineUniform('u_progress', shader.UNIFORM_1f);
program.defineUniform('u_color', shader.UNIFORM_4f);
program.mode(shader.DRAW_ARRAYS_INSTANCED);
shader.useProgram(program);
if (pos) {
pos.buffer(vertex, gl.STATIC_DRAW);
pos.pointer(2, gl.FLOAT, false, 0, 0);
pos.enable();
}
this.shader = shader;
this.program = program;
}
activate(): void {
const render = MotaRenderer.get('render-main');
const draw = render?.getElementById('map-draw') as Container;
if (!draw) return;
const shader = this.shader;
shader.append(draw);
const gl = shader.gl;
const program = this.program;
program.paramArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, 100 * this.level);
this.progress = program.getUniform<UniformType.Uniform1f>('u_progress');
shader.useProgram(program);
shader.generateRainPath(
this.level * 100,
(((Math.random() - 0.5) * Math.PI) / 30) * this.level,
(Math.PI / 180) * (12 - this.level),
program
);
}
frame(): void {
this.shader.update(this.shader);
const time = 5000 - 400 * this.level;
const progress = (Date.now() % time) / time;
this.shader.useProgram(this.program);
this.progress?.set(progress);
}
deactivate(): void {
const render = MotaRenderer.get('render-main');
const draw = render?.getElementById('map-draw') as Container;
const layer = draw.children;
if (!layer || !draw) return;
const shader = this.shader;
draw.appendChild(...layer);
shader.remove();
}
}
class RainShader extends Shader {
/**
* 生成雨滴
* @param num 雨滴数量
*/
generateRainPath(
num: number,
angle: number,
deviation: number,
program: ShaderProgram
) {
const aOffset = program.getAttribArray('a_offset');
const aData = program.getAttribArray('a_data');
const color = program.getUniform<UniformType.Uniform4f>('u_color');
const gl = this.gl;
if (!aOffset || !aData) return;
const tan = Math.tan(angle);
const offset = new Float32Array(num * 2);
const data = new Float32Array(num * 4);
const half = num / 2;
for (let i = 0; i < half; i++) {
const ox = Math.random() * 3 - 1.5;
const oy = Math.random() * 2 - 1;
const rad = angle + (Math.random() - 0.5) * Math.PI * deviation;
const length = Math.random() * 0.05 + 0.03;
const width = Math.random() * 0.002 + 0.002;
offset.set([ox, oy], i * 2);
data.set([width, length, rad, 0], i * 4);
}
for (let i = half; i < num; i++) {
const ox = Math.random() * 3 - 1.5 + tan * 2;
const oy = Math.random() * 2 + 1;
const rad = angle + (Math.random() - 0.5) * Math.PI * deviation;
const length = Math.random() * 0.1 + 0.05;
const width = Math.random() * 0.002 + 0.002;
offset.set([ox, oy], i * 2);
data.set([width, length, rad, 1], i * 4);
}
aOffset.buffer(offset, gl.STATIC_DRAW);
aData.buffer(data, gl.STATIC_DRAW);
aOffset.pointer(2, gl.FLOAT, false, 0, 0);
aOffset.divisor(1);
aOffset.enable();
aData.pointer(4, gl.FLOAT, false, 0, 0);
aData.divisor(1);
aData.enable();
program.paramArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, num);
color?.set(1, 1, 1, 0.1);
}
}