mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-02-28 17:37:07 +08:00
feat: 下雨天气
This commit is contained in:
parent
b599f9f146
commit
5918bfcf61
@ -12,7 +12,7 @@ precision highp float;
|
||||
in vec4 a_position;
|
||||
in vec2 a_texCoord;
|
||||
|
||||
out highp vec2 v_texCoord;
|
||||
out vec2 v_texCoord;
|
||||
`;
|
||||
const SHADER_VERTEX_PREFIX_100 = /* glsl */ `
|
||||
precision highp float;
|
||||
@ -20,20 +20,20 @@ precision highp float;
|
||||
attribute vec4 a_position;
|
||||
attribute vec2 a_texCoord;
|
||||
|
||||
varying highp vec2 v_texCoord;
|
||||
varying vec2 v_texCoord;
|
||||
`;
|
||||
|
||||
const SHADER_FRAGMENT_PREFIX_300 = /* glsl */ `#version 300 es
|
||||
precision highp float;
|
||||
|
||||
in highp vec2 v_texCoord;
|
||||
in vec2 v_texCoord;
|
||||
|
||||
uniform sampler2D u_sampler;
|
||||
`;
|
||||
const SHADER_FRAGMENT_PREFIX_100 = /* glsl */ `
|
||||
precision highp float;
|
||||
|
||||
varying highp vec2 v_texCoord;
|
||||
varying vec2 v_texCoord;
|
||||
|
||||
uniform sampler2D u_sampler;
|
||||
`;
|
||||
@ -268,9 +268,7 @@ export class Shader extends Container<EShaderEvent> {
|
||||
}
|
||||
|
||||
update(item?: RenderItem<any>): void {
|
||||
const isSelf = item === this && !this.cacheDirty;
|
||||
super.update(item);
|
||||
if (isSelf) this.cacheDirty = false;
|
||||
this.shaderRenderDirty = true;
|
||||
}
|
||||
|
||||
@ -283,7 +281,6 @@ export class Shader extends Container<EShaderEvent> {
|
||||
const ready = dr && program.ready();
|
||||
if (!ready) return;
|
||||
const indices = program.usingIndices;
|
||||
if (!indices) return;
|
||||
const param = program.getDrawParams(program.renderMode);
|
||||
if (!param) return;
|
||||
|
||||
@ -293,22 +290,22 @@ export class Shader extends Container<EShaderEvent> {
|
||||
gl.clearDepth(1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
|
||||
const pre = this.preDraw();
|
||||
const pre = this.preDraw(gl, program, param, indices);
|
||||
if (!pre) {
|
||||
this.postDraw();
|
||||
this.postDraw(gl, program, param, indices);
|
||||
return;
|
||||
}
|
||||
|
||||
this.draw(gl, program, param, indices);
|
||||
|
||||
this.postDraw();
|
||||
this.postDraw(gl, program, param, indices);
|
||||
}
|
||||
|
||||
private draw(
|
||||
draw(
|
||||
gl: WebGL2RenderingContext,
|
||||
program: ShaderProgram,
|
||||
param: DrawParamsMap[keyof DrawParamsMap],
|
||||
indices: IShaderIndices
|
||||
indices: IShaderIndices | null
|
||||
) {
|
||||
switch (program.renderMode) {
|
||||
case RenderMode.Arrays: {
|
||||
@ -316,6 +313,7 @@ export class Shader extends Container<EShaderEvent> {
|
||||
gl.drawArrays(mode, first, count);
|
||||
}
|
||||
case RenderMode.Elements: {
|
||||
if (!indices) return;
|
||||
const { mode, count, type, offset } =
|
||||
param as DrawElementsParam;
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices.data);
|
||||
@ -327,6 +325,7 @@ export class Shader extends Container<EShaderEvent> {
|
||||
gl.drawArraysInstanced(mode, first, count, instanceCount);
|
||||
}
|
||||
case RenderMode.ElementsInstanced: {
|
||||
if (!indices) return;
|
||||
const {
|
||||
mode,
|
||||
count,
|
||||
@ -344,7 +343,12 @@ export class Shader extends Container<EShaderEvent> {
|
||||
* 在本着色器内部渲染之前执行的渲染,如果返回false,则表示不进行内部渲染,但依然会执行 {@link postDraw}。
|
||||
* 继承本类,并复写此方法即可实现前置渲染功能
|
||||
*/
|
||||
protected preDraw(): boolean {
|
||||
protected preDraw(
|
||||
gl: WebGL2RenderingContext,
|
||||
program: ShaderProgram,
|
||||
param: DrawParamsMap[keyof DrawParamsMap],
|
||||
indices: IShaderIndices | null
|
||||
): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -352,9 +356,18 @@ export class Shader extends Container<EShaderEvent> {
|
||||
* 在本着色器内部渲染之后执行的渲染,即使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;
|
||||
if (!program) return false;
|
||||
const tex = program.getTexture('u_sampler');
|
||||
@ -491,6 +504,8 @@ export class Shader extends Container<EShaderEvent> {
|
||||
const gl = this.gl;
|
||||
if (!gl) return;
|
||||
gl.enable(gl.DEPTH_TEST);
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||
gl.depthFunc(gl.LEQUAL);
|
||||
}
|
||||
}
|
||||
@ -544,7 +559,7 @@ interface AttribSetFn {
|
||||
[AttribType.AttribI4uiv]: _A<Uint32List>;
|
||||
}
|
||||
|
||||
interface IShaderUniform<T extends UniformType> {
|
||||
export interface IShaderUniform<T extends UniformType> {
|
||||
/** 这个 uniform 变量的内存位置 */
|
||||
readonly location: WebGLUniformLocation;
|
||||
/** 这个 uniform 变量的类型 */
|
||||
@ -559,7 +574,7 @@ interface IShaderUniform<T extends UniformType> {
|
||||
set(...params: UniformSetFn[T]): void;
|
||||
}
|
||||
|
||||
interface IShaderAttrib<T extends AttribType> {
|
||||
export interface IShaderAttrib<T extends AttribType> {
|
||||
/** 这个 attribute 常量的内存位置 */
|
||||
readonly location: number;
|
||||
/** 这个 attribute 常量的类型 */
|
||||
@ -575,7 +590,7 @@ interface IShaderAttrib<T extends AttribType> {
|
||||
set(...params: AttribSetFn[T]): void;
|
||||
}
|
||||
|
||||
interface IShaderAttribArray {
|
||||
export interface IShaderAttribArray {
|
||||
/** 这个 attribute 常量的内存位置 */
|
||||
readonly location: number;
|
||||
/** 这个 attribute 所用的缓冲区信息 */
|
||||
@ -670,7 +685,7 @@ interface IShaderAttribArray {
|
||||
disable(): void;
|
||||
}
|
||||
|
||||
interface IShaderIndices {
|
||||
export interface IShaderIndices {
|
||||
/** 这个顶点索引所用的缓冲区信息 */
|
||||
readonly data: WebGLBuffer;
|
||||
/** 这个量所处的着色器程序 */
|
||||
@ -719,7 +734,7 @@ interface IShaderIndices {
|
||||
): void;
|
||||
}
|
||||
|
||||
interface IShaderUniformMatrix {
|
||||
export interface IShaderUniformMatrix {
|
||||
/** 矩阵的内存位置 */
|
||||
readonly location: WebGLUniformLocation;
|
||||
/** 矩阵类型 */
|
||||
@ -741,7 +756,7 @@ interface IShaderUniformMatrix {
|
||||
): void;
|
||||
}
|
||||
|
||||
interface IShaderUniformBlock {
|
||||
export interface IShaderUniformBlock {
|
||||
/** 这个 uniform block 的内存地址 */
|
||||
readonly location: GLuint;
|
||||
/** 与这个 uniform block 所绑定的缓冲区 */
|
||||
@ -764,7 +779,7 @@ interface IShaderUniformBlock {
|
||||
set(srcData: ArrayBufferView, srcOffset: number, length?: number): void;
|
||||
}
|
||||
|
||||
interface IShaderTexture2D {
|
||||
export interface IShaderTexture2D {
|
||||
/** 纹理对象 */
|
||||
readonly texture: WebGLTexture;
|
||||
/** 宽度 */
|
||||
@ -1235,7 +1250,7 @@ interface DrawElementsInstancedParam {
|
||||
instanceCount: number;
|
||||
}
|
||||
|
||||
interface DrawParamsMap {
|
||||
export interface DrawParamsMap {
|
||||
[RenderMode.Arrays]: DrawArraysParam;
|
||||
[RenderMode.ArraysInstanced]: DrawArraysInstancedParam;
|
||||
[RenderMode.Elements]: DrawElementsParam;
|
||||
@ -1684,6 +1699,7 @@ export class ShaderProgram extends EventEmitter<ShaderProgramEvent> {
|
||||
const buffer = gl.createBuffer();
|
||||
if (!buffer) return null;
|
||||
const location = gl.getAttribLocation(program, name);
|
||||
if (location === -1) return null;
|
||||
const obj = new ShaderAttribArray(buffer, location, gl, this);
|
||||
this.attribArray.set(name, obj);
|
||||
return obj;
|
||||
@ -1870,7 +1886,7 @@ export class ShaderProgram extends EventEmitter<ShaderProgramEvent> {
|
||||
const sampler = this.defineTexture('u_sampler', 0);
|
||||
const indices = this.defineIndices('defalutIndices');
|
||||
if (!tex || !position || !sampler || !indices) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
position.buffer(
|
||||
new Float32Array([1, -1, -1, -1, 1, 1, -1, 1]),
|
||||
|
250
src/module/weather/rain.ts
Normal file
250
src/module/weather/rain.ts
Normal 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;
|
||||
}
|
||||
}
|
75
src/module/weather/weather.ts
Normal file
75
src/module/weather/weather.ts
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user