feat: 下雨天气

This commit is contained in:
unanmed 2024-11-03 11:28:03 +08:00
parent b599f9f146
commit 5918bfcf61
3 changed files with 365 additions and 24 deletions

View File

@ -12,7 +12,7 @@ precision highp float;
in vec4 a_position; in vec4 a_position;
in vec2 a_texCoord; in vec2 a_texCoord;
out highp vec2 v_texCoord; out vec2 v_texCoord;
`; `;
const SHADER_VERTEX_PREFIX_100 = /* glsl */ ` const SHADER_VERTEX_PREFIX_100 = /* glsl */ `
precision highp float; precision highp float;
@ -20,20 +20,20 @@ precision highp float;
attribute vec4 a_position; attribute vec4 a_position;
attribute vec2 a_texCoord; attribute vec2 a_texCoord;
varying highp vec2 v_texCoord; varying vec2 v_texCoord;
`; `;
const SHADER_FRAGMENT_PREFIX_300 = /* glsl */ `#version 300 es const SHADER_FRAGMENT_PREFIX_300 = /* glsl */ `#version 300 es
precision highp float; precision highp float;
in highp vec2 v_texCoord; in vec2 v_texCoord;
uniform sampler2D u_sampler; uniform sampler2D u_sampler;
`; `;
const SHADER_FRAGMENT_PREFIX_100 = /* glsl */ ` const SHADER_FRAGMENT_PREFIX_100 = /* glsl */ `
precision highp float; precision highp float;
varying highp vec2 v_texCoord; varying vec2 v_texCoord;
uniform sampler2D u_sampler; uniform sampler2D u_sampler;
`; `;
@ -268,9 +268,7 @@ export class Shader extends Container<EShaderEvent> {
} }
update(item?: RenderItem<any>): void { update(item?: RenderItem<any>): void {
const isSelf = item === this && !this.cacheDirty;
super.update(item); super.update(item);
if (isSelf) this.cacheDirty = false;
this.shaderRenderDirty = true; this.shaderRenderDirty = true;
} }
@ -283,7 +281,6 @@ export class Shader extends Container<EShaderEvent> {
const ready = dr && program.ready(); const ready = dr && program.ready();
if (!ready) return; if (!ready) return;
const indices = program.usingIndices; const indices = program.usingIndices;
if (!indices) return;
const param = program.getDrawParams(program.renderMode); const param = program.getDrawParams(program.renderMode);
if (!param) return; if (!param) return;
@ -293,22 +290,22 @@ export class Shader extends Container<EShaderEvent> {
gl.clearDepth(1); gl.clearDepth(1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const pre = this.preDraw(); const pre = this.preDraw(gl, program, param, indices);
if (!pre) { if (!pre) {
this.postDraw(); this.postDraw(gl, program, param, indices);
return; return;
} }
this.draw(gl, program, param, indices); this.draw(gl, program, param, indices);
this.postDraw(); this.postDraw(gl, program, param, indices);
} }
private draw( draw(
gl: WebGL2RenderingContext, gl: WebGL2RenderingContext,
program: ShaderProgram, program: ShaderProgram,
param: DrawParamsMap[keyof DrawParamsMap], param: DrawParamsMap[keyof DrawParamsMap],
indices: IShaderIndices indices: IShaderIndices | null
) { ) {
switch (program.renderMode) { switch (program.renderMode) {
case RenderMode.Arrays: { case RenderMode.Arrays: {
@ -316,6 +313,7 @@ export class Shader extends Container<EShaderEvent> {
gl.drawArrays(mode, first, count); gl.drawArrays(mode, first, count);
} }
case RenderMode.Elements: { case RenderMode.Elements: {
if (!indices) return;
const { mode, count, type, offset } = const { mode, count, type, offset } =
param as DrawElementsParam; param as DrawElementsParam;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices.data); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices.data);
@ -327,6 +325,7 @@ export class Shader extends Container<EShaderEvent> {
gl.drawArraysInstanced(mode, first, count, instanceCount); gl.drawArraysInstanced(mode, first, count, instanceCount);
} }
case RenderMode.ElementsInstanced: { case RenderMode.ElementsInstanced: {
if (!indices) return;
const { const {
mode, mode,
count, count,
@ -344,7 +343,12 @@ export class Shader extends Container<EShaderEvent> {
* false {@link postDraw} * false {@link postDraw}
* *
*/ */
protected preDraw(): boolean { protected preDraw(
gl: WebGL2RenderingContext,
program: ShaderProgram,
param: DrawParamsMap[keyof DrawParamsMap],
indices: IShaderIndices | null
): boolean {
return true; return true;
} }
@ -352,9 +356,18 @@ export class Shader extends Container<EShaderEvent> {
* 使preDraw返回false * 使preDraw返回false
* *
*/ */
protected postDraw() {} protected postDraw(
gl: WebGL2RenderingContext,
program: ShaderProgram,
param: DrawParamsMap[keyof DrawParamsMap],
indices: IShaderIndices | null
) {}
private defaultReady(): boolean { /**
*
* @returns
*/
protected defaultReady(): boolean {
const program = this.program; const program = this.program;
if (!program) return false; if (!program) return false;
const tex = program.getTexture('u_sampler'); const tex = program.getTexture('u_sampler');
@ -491,6 +504,8 @@ export class Shader extends Container<EShaderEvent> {
const gl = this.gl; const gl = this.gl;
if (!gl) return; if (!gl) return;
gl.enable(gl.DEPTH_TEST); gl.enable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.depthFunc(gl.LEQUAL); gl.depthFunc(gl.LEQUAL);
} }
} }
@ -544,7 +559,7 @@ interface AttribSetFn {
[AttribType.AttribI4uiv]: _A<Uint32List>; [AttribType.AttribI4uiv]: _A<Uint32List>;
} }
interface IShaderUniform<T extends UniformType> { export interface IShaderUniform<T extends UniformType> {
/** 这个 uniform 变量的内存位置 */ /** 这个 uniform 变量的内存位置 */
readonly location: WebGLUniformLocation; readonly location: WebGLUniformLocation;
/** 这个 uniform 变量的类型 */ /** 这个 uniform 变量的类型 */
@ -559,7 +574,7 @@ interface IShaderUniform<T extends UniformType> {
set(...params: UniformSetFn[T]): void; set(...params: UniformSetFn[T]): void;
} }
interface IShaderAttrib<T extends AttribType> { export interface IShaderAttrib<T extends AttribType> {
/** 这个 attribute 常量的内存位置 */ /** 这个 attribute 常量的内存位置 */
readonly location: number; readonly location: number;
/** 这个 attribute 常量的类型 */ /** 这个 attribute 常量的类型 */
@ -575,7 +590,7 @@ interface IShaderAttrib<T extends AttribType> {
set(...params: AttribSetFn[T]): void; set(...params: AttribSetFn[T]): void;
} }
interface IShaderAttribArray { export interface IShaderAttribArray {
/** 这个 attribute 常量的内存位置 */ /** 这个 attribute 常量的内存位置 */
readonly location: number; readonly location: number;
/** 这个 attribute 所用的缓冲区信息 */ /** 这个 attribute 所用的缓冲区信息 */
@ -670,7 +685,7 @@ interface IShaderAttribArray {
disable(): void; disable(): void;
} }
interface IShaderIndices { export interface IShaderIndices {
/** 这个顶点索引所用的缓冲区信息 */ /** 这个顶点索引所用的缓冲区信息 */
readonly data: WebGLBuffer; readonly data: WebGLBuffer;
/** 这个量所处的着色器程序 */ /** 这个量所处的着色器程序 */
@ -719,7 +734,7 @@ interface IShaderIndices {
): void; ): void;
} }
interface IShaderUniformMatrix { export interface IShaderUniformMatrix {
/** 矩阵的内存位置 */ /** 矩阵的内存位置 */
readonly location: WebGLUniformLocation; readonly location: WebGLUniformLocation;
/** 矩阵类型 */ /** 矩阵类型 */
@ -741,7 +756,7 @@ interface IShaderUniformMatrix {
): void; ): void;
} }
interface IShaderUniformBlock { export interface IShaderUniformBlock {
/** 这个 uniform block 的内存地址 */ /** 这个 uniform block 的内存地址 */
readonly location: GLuint; readonly location: GLuint;
/** 与这个 uniform block 所绑定的缓冲区 */ /** 与这个 uniform block 所绑定的缓冲区 */
@ -764,7 +779,7 @@ interface IShaderUniformBlock {
set(srcData: ArrayBufferView, srcOffset: number, length?: number): void; set(srcData: ArrayBufferView, srcOffset: number, length?: number): void;
} }
interface IShaderTexture2D { export interface IShaderTexture2D {
/** 纹理对象 */ /** 纹理对象 */
readonly texture: WebGLTexture; readonly texture: WebGLTexture;
/** 宽度 */ /** 宽度 */
@ -1235,7 +1250,7 @@ interface DrawElementsInstancedParam {
instanceCount: number; instanceCount: number;
} }
interface DrawParamsMap { export interface DrawParamsMap {
[RenderMode.Arrays]: DrawArraysParam; [RenderMode.Arrays]: DrawArraysParam;
[RenderMode.ArraysInstanced]: DrawArraysInstancedParam; [RenderMode.ArraysInstanced]: DrawArraysInstancedParam;
[RenderMode.Elements]: DrawElementsParam; [RenderMode.Elements]: DrawElementsParam;
@ -1684,6 +1699,7 @@ export class ShaderProgram extends EventEmitter<ShaderProgramEvent> {
const buffer = gl.createBuffer(); const buffer = gl.createBuffer();
if (!buffer) return null; if (!buffer) return null;
const location = gl.getAttribLocation(program, name); const location = gl.getAttribLocation(program, name);
if (location === -1) return null;
const obj = new ShaderAttribArray(buffer, location, gl, this); const obj = new ShaderAttribArray(buffer, location, gl, this);
this.attribArray.set(name, obj); this.attribArray.set(name, obj);
return obj; return obj;
@ -1870,7 +1886,7 @@ export class ShaderProgram extends EventEmitter<ShaderProgramEvent> {
const sampler = this.defineTexture('u_sampler', 0); const sampler = this.defineTexture('u_sampler', 0);
const indices = this.defineIndices('defalutIndices'); const indices = this.defineIndices('defalutIndices');
if (!tex || !position || !sampler || !indices) { if (!tex || !position || !sampler || !indices) {
return false; return true;
} }
position.buffer( position.buffer(
new Float32Array([1, -1, -1, -1, 1, 1, -1, 1]), new Float32Array([1, -1, -1, -1, 1, 1, -1, 1]),

250
src/module/weather/rain.ts Normal file
View File

@ -0,0 +1,250 @@
import {
DrawParamsMap,
IShaderIndices,
IShaderUniform,
Shader,
ShaderProgram,
UniformType
} from '@/core/render/shader';
import { IWeather, WeatherController } from './weather';
import { MotaRenderer } from '@/core/render/render';
import { Container } from '@/core/render/container';
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 dis = distance(v_pos, v_center);
float alpha = 1.0;
float decay = v_data.y * 0.5 * 0.5;
if (dis > decay) {
alpha = 2.0 * (dis - decay) / decay;
}
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]);
Mota.require('var', 'loading').once('coreInit', () => {
const shader = new RainShader();
const gl = shader.gl;
shader.size(480, 480);
shader.setHD(true);
RainWeather.shader = shader;
const program = shader.createProgram();
program.setVersion(shader.VERSION_ES_300);
program.fs(rainFs);
program.vs(rainVs);
program.requestCompile();
program.useDefault(false);
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);
RainShader.rainProgram = program;
shader.useProgram(program);
if (pos) {
pos.buffer(vertex, gl.STATIC_DRAW);
pos.pointer(2, gl.FLOAT, false, 0, 0);
pos.enable();
}
const back = shader.createProgram();
back.requestCompile();
back.modified = true;
back.useDefault(false);
RainShader.backProgram = back;
shader.useProgram(back);
});
export class RainWeather implements IWeather {
static id: string = 'rain';
static shader: RainShader;
private progress: IShaderUniform<UniformType.Uniform1f> | null = null;
constructor(readonly level: number = 5) {}
activate(): void {
const render = MotaRenderer.get('render-main');
const layer = render?.getElementById('layer-main');
const draw = render?.getElementById('map-draw') as Container;
if (!layer || !draw) return;
const shader = RainWeather.shader;
layer.append(shader);
shader.append(draw);
const gl = shader.gl;
const program = RainShader.rainProgram;
program.paramArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, 100 * this.level);
this.progress = program.getUniform<UniformType.Uniform1f>('u_progress');
shader.generateRainPath(
this.level * 100,
(((Math.random() - 0.5) * Math.PI) / 30) * this.level,
(Math.PI / 180) * (12 - this.level)
);
}
frame(): void {
RainWeather.shader.update(RainWeather.shader);
const time = 5000 - 400 * this.level;
const progress = (Date.now() % time) / time;
// console.log(progress);
RainWeather.shader.useProgram(RainShader.rainProgram);
this.progress?.set(progress);
}
deactivate(): void {
const render = MotaRenderer.get('render-main');
const layer = render?.getElementById('layer-main');
const draw = render?.getElementById('map-draw') as Container;
if (!layer || !draw) return;
const shader = RainWeather.shader;
layer.append(draw);
shader.remove();
}
}
WeatherController.register(RainWeather);
class RainShader extends Shader {
static rainProgram: ShaderProgram;
static backProgram: ShaderProgram;
/**
*
* @param num
*/
generateRainPath(num: number, angle: number, deviation: number) {
const program = RainShader.rainProgram;
RainWeather.shader.useProgram(program);
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);
}
protected override preDraw(gl: WebGL2RenderingContext): boolean {
const back = RainShader.backProgram;
const rain = RainShader.rainProgram;
this.useProgram(back);
const ready = this.defaultReady();
const param = back.getDrawParams(this.DRAW_ELEMENTS);
const rainParam = rain.getDrawParams(this.DRAW_ARRAYS_INSTANCED);
if (!ready || !param || !rainParam) return false;
this.draw(gl, back, param, back.usingIndices);
this.useProgram(rain);
this.draw(gl, rain, rainParam, null);
return false;
}
}

View File

@ -0,0 +1,75 @@
import { logger } from '@/core/common/logger';
import { Ticker } from 'mutate-animate';
export interface IWeather {
/**
*
*/
activate(): void;
/**
*
*/
frame(): void;
/**
*
*/
deactivate(): void;
}
interface Weather {
id: string;
new (level?: number): IWeather;
}
export class WeatherController {
static list: Map<string, Weather> = new Map();
/** 当前的所有天气 */
active: Set<IWeather> = new Set();
ticker: Ticker = new Ticker();
private tick = () => {
this.active.forEach(v => {
v.frame();
});
};
/**
*
* @param id id
* @param level
* @returns
*/
activate(id: string, level?: number) {
const Weather = WeatherController.list.get(id);
if (!Weather) {
logger.warn(25, id);
return;
}
const weather = new Weather(level);
this.active.add(weather);
weather.activate();
if (!this.ticker.funcs.has(this.tick)) {
this.ticker.add(this.tick);
}
return weather;
}
/**
*
* @param weather
*/
deactivate(weather: IWeather) {
this.active.delete(weather);
if (this.active.size === 0) {
this.ticker.remove(this.tick);
}
weather.deactivate();
}
static register(weather: Weather) {
this.list.set(weather.id, weather);
}
}