着色器特效

This commit is contained in:
unanmed 2023-08-27 17:31:55 +08:00
parent f197303a2f
commit 37a18c87c0
6 changed files with 642 additions and 44 deletions

View File

@ -399,7 +399,7 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d =
1 1
], ],
"background": "winskin2.png", "background": "winskin2.png",
"textfont": 16 "textfont": 14
} }
], ],
"shops": [ "shops": [

View File

@ -37,7 +37,7 @@ main.floors.MT0=
], ],
"9,13": [ "9,13": [
"这里会列出每一层所展示的插件名称。", "这里会列出每一层所展示的插件名称。",
"1-5点光源" "1-5点光源\n6-7碎裂特效\n8-10着色器特效"
] ]
}, },
"changeFloor": { "changeFloor": {

View File

@ -209,7 +209,7 @@ main.floors.MT6=
[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[ 1, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, 1], [ 1, 0, 0, 0, 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, 1],
[ 1, 0, 0, 0,129, 0, 0, 88, 0, 94,129, 0, 0, 0, 1], [ 1, 0, 0,10187,129, 0, 0, 88, 0, 94,129,10186, 0, 0, 1],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
], ],
"bgmap": [ "bgmap": [

View File

@ -1,45 +1,119 @@
main.floors.MT8= main.floors.MT8=
{ {
"floorId": "MT8", "floorId": "MT8",
"title": "主塔 8 层", "title": "主塔 8 层",
"name": "8", "name": "8",
"width": 15, "width": 15,
"height": 15, "height": 15,
"canFlyTo": true, "canFlyTo": true,
"canFlyFrom": true, "canFlyFrom": true,
"canUseQuickShop": true, "canUseQuickShop": true,
"cannotViewMap": false, "cannotViewMap": false,
"images": [], "images": [],
"ratio": 1, "ratio": 1,
"defaultGround": "ground", "defaultGround": "ground",
"bgm": "cave.mp3", "bgm": "cave.mp3",
"firstArrive": [], "firstArrive": [],
"eachArrive": [], "eachArrive": [],
"parallelDo": "", "parallelDo": "",
"events": {}, "events": {
"changeFloor": {}, "10,13": [
"beforeBattle": {}, "从本层开始将进入着色器特效插件的教学。",
"afterBattle": {}, "着色器特效插件是一个通用型特效插件允许你使用gpu进行特效渲染效果好同时性能表现也好。",
"afterGetItem": {}, "插件的核心是片元着色器,接下来的几层也将以片元着色器为核心进行教学。"
"afterOpenDoor": {}, ],
"autoEvent": {}, "4,13": [
"cannotMove": {}, "这里是第一个特效展示木牌。",
"cannotMoveIn": {}, "现在我要对背景层、事件层、前景层、勇士层进行特效处理。",
"map": [ " 首先我应该引入需要使用的内容,使用:\n\r[yellow]const { ShaderEffect, setTickerFor, replaceGameCanvas } = core.plugin.shaderEffect;\r",
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], " 然后我需要创建一个着色器特效实例,并指定特效画布的背景色,例如我指定背景色为全透明:\n\r[yellow]const effect = new ShaderEffect([0, 0, 0, 0]);\r\n 其中\r[gold][0, 0, 0, 0]\r便是颜色数组每一项分别表示 rgba范围均为\r[gold]0-1\r",
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], " 下面我需要指定特效的作用画布,上面说了我要对哪些画布进行特效处理,现在我们就需要获取这些画布:\n\r[yellow]const canvas = ['bg', 'bg2', 'event', 'fg', 'fg2', 'hero'];\r\n 这些便是所有画布的id下面我们将其映射为画布\n\r[yellow]const imgs = canvas.map(v => core.canvas[v].canvas);\r\n 这样,我们就获取到了所有的作用画布,下面将特效实例的作用画布设置为它们:\n\r[yellow]effect.baseImage(...imgs);\r",
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], " 设置完作用画布之后,我们就可以编写着色器脚本了。着色器脚本使用\r[gold]glsl\r语言进行编写。\n首先我们来看顶点着色器。顶点着色器用于确定绘制顶点与图片顶点等一般不需要我们编写直接使用插件内置的脚本即可\n\r[yellow]effect.vs(ShaderEffect.defaultVs);\r\n 其中\r[gold]vs\r函数便是设置顶点着色器的函数。",
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], " 下面我们主要看片元着色器。片元着色器描述了每个像素的颜色,因此它是本插件的核心。在片元着色器脚本中,必须包含一个\r[gold]main\r函数同时必须为\r[gold]gl_FragColor\r赋值其中\r[gold]gl_FragColor\r便是当前像素的颜色。\n 我们直接看一个例子,例子中将当前像素的绿色与红色值进行了互换:\n\r[yellow]void main() {\n gl_FragColor = texture2D(uSampler, vTextureCoord).grba;\n}\r\n 这段着色器脚本乍一看貌似没有一处能看懂(),不过不要慌,我来解释一下它干了什么。",
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], " 首先\r[gold]glsl\r是一个语法与\r[gold]C语言\r很像的语言同时\r[gold]js\r也是C系语言因此大部分语法使用js进行判断是没有太大问题的。下面我们就来仔细看看这段着色器代码。\n 首先是\r[yellow]void main() { }\r它的作用是声明了一个返回值为空的函数\r[gold]void\r便是返回值类型\r[gold]main\r便是函数名称它与js的函数声明基本一致。\n 下面是\r[yellow]gl_FragColor = texture2D(uSampler, vTextureCoord).grba;\r它表示将后面的值赋给\r[gold]gl_FragColor\r。下面我们看后面的值\r[gold]texture2D表示获取一个纹理的信息其中\r[gold]uSampler\r指的便是我们的作用画布\r[gold]vTextureCoord\r便是当前像素的位置这么写意思便是获取到作用画布在当前位置的颜色信息。后面的\r[gold].grba\r表示根据\r[gold]grba\r的顺序获取颜色信息。它们的位置关系为r - 0, g - 1, b - 2, a - 3也就是说我写\r[gold].grba\r就表示了按 1023 的顺序进行获取,也就是把绿色与红色进行了互换。",
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], " 这下,我们便将着色器特效最复杂的部分解决了,后面的事情就好办了,我们将片元着色器脚本传递给特效实例:\n\r[yellow]effect.fs(`\n void main() {\n gl_FragColor = texture2D(uSampler, vTextureCoord).grba;\n }\n`);\r\n 下面,我们就可以进行特效渲染了。",
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], " 我们直接进行渲染:\n\r[yellow]effect.update(true);\r\n 其中\r[gold]update\r函数便是强制重新渲染特效的函数后面的参数表示重新编译着色器脚本因为我们还没编译过因此需要传入true。一般我们更改了着色器脚本后都需要重新编译。",
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], " 目前为止,我们渲染了一个静态的特效,我们需要每帧都渲染来让特效表现为动态的,我们直接使用插件自带的函数:\n\r[yellow]const ticker = setTickerFor(effect);\r\n 这样,特效就会自动每帧渲染,保持为动态了,其返回值\r[gold]ticker\r是高级动画插件中的\r[gold]Ticker\r实例。",
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], " 下面,我们需要将特效展示在页面上。我们刚刚只对样板的系统画布进行了特效渲染,因此我们可以使用插件内置的函数进行这一操作:\n\r[yellow]const manager = replaceGameCanvas(effect, canvas);\r\n 这样,特效就会展示在页面中了。下面我们来看一下完整代码。",
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "\r[yellow]const { ShaderEffect, setTickerFor, replaceGameCanvas } = core.plugin.shaderEffect;\nconst effect = new ShaderEffect([0, 0, 0, 0]);\nconst canvas = ['bg', 'bg2', 'event', 'fg', 'fg2', 'hero'];\nconst imgs = canvas.map(v => core.canvas[v].canvas);\neffect.baseImage(imgs);\neffect.vs(ShaderEffect.defaultVs);\neffect.fs(`\n void main() {\n gl_FragColor = texture2D(uSampler, \n vTextureCoord).grba;\n}\n`);\neffect.update(true);\n\nconst ticker = setTickerFor(effect);\nconst manager = replaceGameCanvas(effect, canvas);\r",
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], {
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "type": "function",
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "function": "function(){\nflags.lastShaderSample?.end();\nflags.lastShaderSample = core.shaderSample1();\n}"
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], },
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] "现在便是最终效果。"
],
"10,9": [
"这里是第二个特效展示木牌。",
"下面我将对事件层和勇士层进行处理,任何不透明的像素都将变成完全黑色,否则完全透明。它的着色器脚本:\n\r[yellow]void main() {\n float alpha = texture2D(uSampler, vTextureCoord).a;\n gl_FragColor = vec4(0.0, 0.0, 0.0, alpha == 0 ? 0 : 1);\n}\r",
{
"type": "function",
"function": "function(){\nflags.lastShaderSample?.end();\nflags.lastShaderSample = core.shaderSample2();\n}"
}
],
"10,5": [
"这里是第三个特效展示木牌",
"下面我将对事件层和勇士层进行处理然后再rgb三个通道上加上一定量的噪声\n\r[yellow]float noise(float x) {\n float y = fract(sin(x)*100000.0);\n return y;\n}\n\nvoid main() {\n vec4 rgba = texture2D(uSampler, vTextureCoord);\n float r = rgba.r + noise(rgba.r) / 5.0;\n float g = rgba.g + noise(rgba.g) / 5.0;\n float b = rgba.b + noise(rgba.b) / 5.0;\n\n gl_FragColor = vec4(r, g, b, rgba.a);\n}\r",
{
"type": "function",
"function": "function(){\nflags.lastShaderSample?.end();\nflags.lastShaderSample = core.shaderSample3();\n}"
}
],
"10,1": [
"这里是第四个特效展示木牌,让我们来做一些复杂一点的特效",
"下面我将对事件层和勇士层进行处理,然后降低亮度,在竖直方向上添加水平偏移噪声,模拟老式电视的信号不好的效果:\n\r[yellow]float noise(float x) {\n float y = fract(sin(x) * 100000.0);\n return y;\n}\n\nvoid main() {\n float brigtness = -0.1;\n vec2 xy = vTextureCoord;\n float x = xy.x + noise(xy.y) / 100.0;\n float y = xy.y;\n vec4 color = texture2D(uSampler, vec2(x, y));\n vec4 color1 = vec4(color.rgb + vec3(brigtness), color.a);\n\n gl_FragColor = color1;\n}\r",
{
"type": "function",
"function": "function(){\nflags.lastShaderSample?.end();\nflags.lastShaderSample = core.shaderSample4();\n}"
}
],
"4,9": [
"不过我们发现到目前为止特效都不会动,只能保持为一定的状态,怎么让特效也能动呢?那就期待插件的下一次更新吧!"
]
},
"changeFloor": {
"7,13": {
"floorId": ":before",
"stair": "upFloor"
},
"7,1": {
"floorId": ":next",
"stair": "downFloor"
}
},
"beforeBattle": {},
"afterBattle": {},
"afterGetItem": {},
"afterOpenDoor": {},
"autoEvent": {},
"cannotMove": {},
"cannotMoveIn": {},
"map": [
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[ 1, 0, 0, 0, 0, 0, 0, 87, 0, 0,129,10189, 0, 0, 1],
[ 1, 27, 50,606, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[ 1, 28, 52,608, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,129,10188, 0, 0, 1],
[ 1, 29, 51,236, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[ 1, 30, 47,273, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[ 1, 0, 0,10193,129, 0, 0, 0, 0, 0,129,10187, 0, 0, 1],
[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[ 1, 0, 0,10186,129, 0, 0, 88, 0, 94,129,10185, 0, 0, 1],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
], ],
"bgmap": [
],
"fgmap": [
],
"bg2map": [
],
"fg2map": [
]
} }

View File

@ -18,6 +18,7 @@ import completion, { floors } from './plugin/completion';
import path from './plugin/fx/path'; import path from './plugin/fx/path';
import * as ani from 'mutate-animate'; import * as ani from 'mutate-animate';
import frag from './plugin/fx/frag'; import frag from './plugin/fx/frag';
import shader from './plugin/fx/shader';
function forward() { function forward() {
const toForward: any[] = [ const toForward: any[] = [
@ -40,6 +41,7 @@ function forward() {
completion(), completion(),
path(), path(),
frag(), frag(),
shader()
]; ];
// 初始化所有插件并转发到core上 // 初始化所有插件并转发到core上

522
src/plugin/fx/shader.ts Normal file
View File

@ -0,0 +1,522 @@
import { Ticker } from 'mutate-animate';
const isWebGLSupported = (() => {
return !!document.createElement('canvas').getContext('webgl');
})();
type ShaderColorArray = [number, number, number, number];
type ShaderEffectImage = Exclude<TexImageSource, VideoFrame | ImageData>;
interface ProgramInfo {
program: WebGLProgram;
attrib: Record<string, number>;
uniform: Record<string, WebGLUniformLocation>;
}
interface ShaderEffectBuffer {
position: WebGLBuffer;
texture: WebGLBuffer;
indices: WebGLBuffer;
}
interface ShaderEffectShader {
vertex: WebGLShader;
fragment: WebGLShader;
}
interface MixedImage {
canvas: HTMLCanvasElement;
update(): void;
}
export default function init() {
return {
ShaderEffect,
setTickerFor,
replaceGameCanvas,
shaderSample1: sample1,
shaderSample2: sample2,
shaderSample3: sample3,
shaderSample4: sample4
};
}
const sample1 = buildSample(
['bg', 'bg2', 'event', 'fg', 'fg2', 'hero'],
`
void main() {
gl_FragColor = texture2D(uSampler, vTextureCoord).grba;
}
`
);
const sample2 = buildSample(
['event', 'hero'],
`
void main() {
float a = texture2D(uSampler, vTextureCoord).a;
gl_FragColor = vec4(0.0, 0.0, 0.0, a == 0.0 ? 0.0 : 1.0);
}
`
);
const sample3 = buildSample(
['event', 'hero'],
`
float noise(float x) {
float y = fract(sin(x) * 100000.0);
return y;
}
void main() {
vec4 rgba = texture2D(uSampler, vTextureCoord);
float r = rgba.r + noise(rgba.r) / 5.0;
float g = rgba.g + noise(rgba.g) / 5.0;
float b = rgba.b + noise(rgba.b) / 5.0;
gl_FragColor = vec4(r, g, b, rgba.a);
}
`
);
const sample4 = buildSample(
['event', 'hero'],
`
float noise(float x) {
float y = fract(sin(x) * 100000.0);
return y;
}
void main() {
float brigtness = -0.1;
vec2 xy = vTextureCoord;
float x = xy.x + noise(xy.y) / 100.0;
float y = xy.y;
vec4 color = texture2D(uSampler, vec2(x, y));
vec4 color1 = vec4(color.rgb + vec3(brigtness), color.a);
gl_FragColor = color1;
}
`
);
function buildSample(canvas: string[], fs: string) {
return () => {
const effect = new ShaderEffect([0, 0, 0, 0]);
effect.baseImage(...canvas.map(v => core.canvas[v].canvas));
effect.vs(ShaderEffect.defaultVs);
effect.fs(fs);
effect.update(true);
const ticker = setTickerFor(effect);
const manager = replaceGameCanvas(effect, canvas);
return {
end() {
ticker.destroy();
manager.remove();
}
};
};
}
const builtinVs = `
#ifdef GL_ES
precision highp float;
#endif
attribute vec4 aVertexPosition;
attribute vec2 aTextureCoord;
varying highp vec2 vTextureCoord;
`;
const builtinFs = `
#ifdef GL_ES
precision highp float;
#endif
varying highp vec2 vTextureCoord;
uniform sampler2D uSampler;
`;
export class ShaderEffect {
canvas: HTMLCanvasElement;
gl: WebGLRenderingContext;
program: WebGLProgram | null = null;
texture: WebGLTexture | null = null;
programInfo: ProgramInfo | null = null;
buffer: ShaderEffectBuffer | null = null;
shader: ShaderEffectShader | null = null;
textureCanvas: MixedImage | null = null;
private baseImages: ShaderEffectImage[] = [];
private background: ShaderColorArray = [0, 0, 0, 0];
private _vsSource: string = '';
private _fsSource: string = '';
get vsSource() {
return builtinVs + this._vsSource;
}
get fsSource() {
return builtinFs + this._fsSource;
}
constructor(background: ShaderColorArray) {
this.canvas = document.createElement('canvas');
if (!isWebGLSupported) {
throw new Error(
`Cannot initialize ShaderEffect, since your device does not support webgl.`
);
}
this.gl = this.canvas.getContext('webgl')!;
this.gl.clearColor(...background);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.background = background;
const s = core.domStyle.scale * devicePixelRatio;
this.canvas.width = s * core._PX_;
this.canvas.height = s * core._PY_;
}
/**
* ShaderEffect.canvas
* @param img
*/
baseImage(...img: ShaderEffectImage[]) {
this.baseImages = img;
}
/**
*
* @param compile
*/
update(compile: boolean = false) {
const gl = this.gl;
if (compile) {
gl.deleteProgram(this.program);
gl.deleteTexture(this.texture);
gl.deleteBuffer(this.buffer?.position ?? null);
gl.deleteBuffer(this.buffer?.texture ?? null);
gl.deleteShader(this.shader?.vertex ?? null);
gl.deleteShader(this.shader?.fragment ?? null);
this.program = this.createProgram();
this.programInfo = this.getProgramInfo();
this.buffer = this.initBuffers();
this.texture = this.createTexture();
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
}
this.textureCanvas?.update();
this.drawScene();
}
/**
* 使 glsl api
* main gl_Position
* @param shader
*/
vs(shader: string) {
this._vsSource = shader;
}
/**
* 使 glsl api
* main gl_FragColor
* @param shader
*/
fs(shader: string) {
this._fsSource = shader;
}
/**
*
*/
drawScene() {
// 清空画布
const gl = this.gl;
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
gl.clearColor(...this.background);
gl.clearDepth(1);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 设置顶点信息
this.setPositionAttrib();
this.setTextureAttrib();
// 准备绘制
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffer!.indices);
gl.useProgram(this.program);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
this.textureCanvas!.canvas
);
gl.uniform1i(this.programInfo!.uniform.uSampler, 0);
// 绘制
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
}
private createProgram() {
const gl = this.gl;
const vs = this.loadShader(gl.VERTEX_SHADER, this.vsSource);
const fs = this.loadShader(gl.FRAGMENT_SHADER, this.fsSource);
this.shader = {
vertex: vs,
fragment: fs
};
const program = gl.createProgram()!;
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error(
`Cannot initialize shader program. Error info: ${gl.getProgramInfoLog(
program
)}`
);
}
return program;
}
private loadShader(type: number, source: string) {
const gl = this.gl;
const shader = gl.createShader(type)!;
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error(
`Cannot compile ${
type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'
} shader. Error info: ${gl.getShaderInfoLog(shader)}`
);
}
return shader;
}
private createTexture() {
const gl = this.gl;
const img = mixImage(this.baseImages);
this.textureCanvas = img;
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
img.canvas
);
gl.generateMipmap(gl.TEXTURE_2D);
return texture;
}
private initBuffers(): ShaderEffectBuffer {
const positions = new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]);
const posBuffer = this.initBuffer(positions);
const textureCoord = new Float32Array([1, 1, 0, 1, 1, 0, 0, 0]);
const textureBuffer = this.initBuffer(textureCoord);
return (this.buffer = {
position: posBuffer,
texture: textureBuffer,
indices: this.initIndexBuffer()
});
}
private initBuffer(pos: Float32Array) {
const gl = this.gl;
const posBuffer = gl.createBuffer()!;
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
gl.bufferData(gl.ARRAY_BUFFER, pos, gl.STATIC_DRAW);
return posBuffer;
}
private initIndexBuffer() {
const gl = this.gl;
const buffer = gl.createBuffer()!;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
const indices = new Uint16Array([0, 1, 2, 2, 3, 1]);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
return buffer;
}
private getProgramInfo(): ProgramInfo | null {
if (!this.program) return null;
const gl = this.gl;
const pro = this.program;
return (this.programInfo = {
program: pro,
attrib: {
vertexPosition: gl.getAttribLocation(pro, 'aVertexPosition'),
textureCoord: gl.getAttribLocation(pro, 'aTextureCoord')
},
uniform: {
uSampler: gl.getUniformLocation(pro, 'uSampler')!
}
});
}
private setTextureAttrib() {
const gl = this.gl;
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer!.texture);
gl.vertexAttribPointer(
this.programInfo!.attrib.textureCoord,
2,
gl.FLOAT,
false,
0,
0
);
gl.enableVertexAttribArray(this.programInfo!.attrib.textureCoord);
}
private setPositionAttrib() {
const gl = this.gl;
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer!.position);
gl.vertexAttribPointer(
this.programInfo!.attrib.vertexPosition,
2,
gl.FLOAT,
false,
0,
0
);
gl.enableVertexAttribArray(this.programInfo!.attrib.vertexPosition);
}
static defaultVs: string = `
void main() {
vTextureCoord = aTextureCoord;
gl_Position = aVertexPosition;
}
`;
static defaultFs: string = `
void main() {
gl_FragColor = texture2D(uSampler, vTextureCoord);
}
`;
}
function floorPower2(value: number) {
return Math.pow(2, Math.ceil(Math.log(value) / Math.LN2));
}
/**
* webgl 2
* @param img
*/
function normalizeTexture(img: ShaderEffectImage) {
const canvas = document.createElement('canvas');
canvas.width = floorPower2(img.width);
canvas.height = floorPower2(img.height);
const ctx = canvas.getContext('2d')!;
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
return canvas;
}
function mixImage(imgs: ShaderEffectImage[]): MixedImage {
// todo: 直接使用webgl纹理进行图片混合
if (imgs.length === 0) {
throw new Error(`Cannot mix images whose count is 0.`);
}
if (
imgs.some(v => v.width !== imgs[0].width || v.height !== imgs[0].height)
) {
throw new Error(`Cannot mix images with different size.`);
}
const canvas = document.createElement('canvas');
canvas.width = floorPower2(imgs[0].width);
canvas.height = floorPower2(imgs[0].height);
const ctx = canvas.getContext('2d')!;
imgs.forEach(v => {
const img = normalizeTexture(v);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
});
return {
canvas,
update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
imgs.forEach(v => {
const img = normalizeTexture(v);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
});
}
};
}
/**
* ticker
* @param effect
*/
export function setTickerFor(effect: ShaderEffect) {
const ticker = new Ticker();
ticker.add(() => {
effect.update();
});
return ticker;
}
export function replaceGameCanvas(effect: ShaderEffect, canvas: string[]) {
let zIndex = 0;
canvas.forEach(v => {
const canvas = core.canvas[v].canvas;
const style = getComputedStyle(canvas);
const z = parseInt(style.zIndex);
if (z > zIndex) zIndex = z;
canvas.style.display = 'none';
});
const gl = effect.canvas;
gl.style.left = '0';
gl.style.top = '0';
gl.style.position = 'absolute';
gl.style.zIndex = zIndex.toString();
gl.style.display = 'block';
gl.style.width = `${core._PX_ * core.domStyle.scale}px`;
gl.style.height = `${core._PY_ * core.domStyle.scale}px`;
core.dom.gameDraw.appendChild(gl);
return {
recover() {
canvas.forEach(v => {
const canvas = core.canvas[v].canvas;
canvas.style.display = 'block';
});
gl.style.display = 'none';
},
append() {
canvas.forEach(v => {
const canvas = core.canvas[v].canvas;
canvas.style.display = 'none';
});
gl.style.display = 'block';
},
remove() {
this.recover();
gl.remove();
},
update(compile?: boolean) {
effect.update(compile);
}
};
}