From 38e24dd9bc96e3fa7f977a59df8c997b12d67069 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Mon, 28 Aug 2023 16:05:52 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9D=80=E8=89=B2=E5=99=A8=E7=89=B9=E6=95=88?= =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/project/floors/MT8.js | 9 +- public/project/floors/MT9.js | 171 ++++++++++++++----- public/project/maps.js | 10 ++ src/plugin/fx/shader.ts | 323 +++++++++++++++++++++++++++++++++-- src/plugin/utils.ts | 7 + src/source/cls.d.ts | 10 ++ src/source/maps.d.ts | 20 +++ 7 files changed, 490 insertions(+), 60 deletions(-) diff --git a/public/project/floors/MT8.js b/public/project/floors/MT8.js index 917ea1e..9e8268c 100644 --- a/public/project/floors/MT8.js +++ b/public/project/floors/MT8.js @@ -44,7 +44,7 @@ main.floors.MT8= ], "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", + "下面我将对事件层和勇士层进行处理,任何不透明的像素都将变成完全黑色,否则完全透明。它的着色器脚本:\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 ? 0.0 : 1.0);\n}\r", { "type": "function", "function": "function(){\nflags.lastShaderSample?.end();\nflags.lastShaderSample = core.shaderSample2();\n}" @@ -67,7 +67,12 @@ main.floors.MT8= } ], "4,9": [ - "不过我们发现到目前为止特效都不会动,只能保持为一定的状态,怎么让特效也能动呢?那就期待插件的下一次更新吧!" + "不过我们发现到目前为止特效都不会动,只能保持为一定的状态,怎么让特效也能动呢?那就期待插件的下一次更新吧!", + "下面将清除着色器特效。", + { + "type": "function", + "function": "function(){\nflags.lastShaderSample?.end();\n}" + } ] }, "changeFloor": { diff --git a/public/project/floors/MT9.js b/public/project/floors/MT9.js index 18c192c..2c9db82 100644 --- a/public/project/floors/MT9.js +++ b/public/project/floors/MT9.js @@ -1,45 +1,134 @@ main.floors.MT9= { -"floorId": "MT9", -"title": "主塔 9 层", -"name": "9", -"width": 15, -"height": 15, -"canFlyTo": true, -"canFlyFrom": true, -"canUseQuickShop": true, -"cannotViewMap": false, -"images": [], -"ratio": 1, -"defaultGround": "ground", -"bgm": "cave.mp3", -"firstArrive": [], -"eachArrive": [], -"parallelDo": "", -"events": {}, -"changeFloor": {}, -"beforeBattle": {}, -"afterBattle": {}, -"afterGetItem": {}, -"afterOpenDoor": {}, -"autoEvent": {}, -"cannotMove": {}, -"cannotMoveIn": {}, -"map": [ - [ 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], - [ 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], - [ 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], - [ 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], - [ 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], - [ 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], - [ 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], - [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + "floorId": "MT9", + "title": "主塔 9 层", + "name": "9", + "width": 15, + "height": 15, + "canFlyTo": true, + "canFlyFrom": true, + "canUseQuickShop": true, + "cannotViewMap": false, + "images": [], + "ratio": 1, + "defaultGround": "ground", + "bgm": "cave.mp3", + "firstArrive": [], + "eachArrive": [], + "parallelDo": "", + "events": { + "5,1": [ + "本层将展示着色器特效的动画效果。", + "动画效果的核心依然是高级动画插件,使用它来让一个值动态变化,从而达到动画的效果。本层将展示若干个示例。" + ], + "3,1": [ + "这里是第一个实例。", + "本实例将做出一个画面逐渐变黑白的特效(虽然使用css更为方便,但是为了教学插件怎么用,就使用着色器插件进行制作)", + " 首先我们要知道,要让画面变黑白,应该让它的rgb变成相同的,一般来说为了让效果更真实,会有特殊的算法进行优化,不过这里方便起见直接取平均值。\n 那么要有一个动画效果,我们应当声明一个可以变化的值来表示灰度百分比,表现在着色器中就是\r[gold]uniform(全局变量)\r声明。", + " 那么片元着色器的代码就是:\n\r[yellow]uniform float uGrayscale;\n\nvoid main() {\n vec4 rgb = texture2D(uSampler, vTextureCoord);\n float avr = (rgb.r + rgb.g + rgb.b) / 3.0;\n float dr = (rgb.r - avr) * (1.0 - uGrayscale);\n float dg = (rgb.g - avr) * (1.0 - uGrayscale);\n float db = (rgb.b - avr) * (1.0 - uGrayscale);\n\n gl_FragColor = vec4(avr + dr, avr + dg, avr + db, rgb.a);\n}\r", + " 稍微解析一下这段代码。这段代码首先声明了一个浮点型的\r[yellow]uGrayscale\r全局变量,然后在\r[yellow]main\r函数里面获取了当前像素的rgba值,计算平均值和差值,然后最后的rgba值就是平均值加上差值了。", + " 那么前面的代码我们可以很轻松地写出来了,跟上一层的一样,不同的是我们还需要引入高级动画插件:\n\r[yellow]const { ShaderEffect, setTickerFor, replaceGameCanvas } = core.plugin.shaderEffect;\nconst { Animation, hyper } = core.plugin.animate;\nconst canvas = ['bg', 'bg2', 'event', 'fg', 'fg2', 'hero'];\neffect.baseImage(...canvas.map(v => core.canvas[v].canvas));\neffect.vs(ShaderEffect.defaultVs);\neffect.fs(/* 上面那一段着色器脚本 */);\r", + " 在这后面就出现与之前不一样的地方了,之前我们直接使用\r[yellow]update\r函数进行渲染,但是这里不一样,需要使用\r[yellow]compile\r函数先进行编译:\n\r[yellow]effect.compile();\r", + " 编译完成之后,我们就可以对\r[gold]uniform\r进行操作了,我们需要创建一个全局变量绑定器:\n\r[yellow]const binder = effect.createUniformBinder('uGrayscale', 1, 'f', '');\r\n 这一句话的信息量很大,我们来解析一下。\n 这个方法的第一个参数是全局变量的名称,着色器中我们使用了\r[gold]uGrayScale\r,这里也填它。\n 第二个参数是变量的元素数量,由于是一个浮点型数字,因此只有一个元素,所以填\r[gold]1\r。\n 第三个参数是元素的数值类似,是浮点数,所以填\r[gold]'f'\r,整型的话应该填\r[gold]'i'\r。\n 第四个参数是这个变量是否是向量,浮点数不是向量,因此填\r[gold]''\r,注意不能不填,应当填空字符串。", + " 下一步我们需要对这个全局变量进行初始化操作,使用\r[gold]set\r方法进行赋值即可初始化:\n\r[yellow]binder.set(0);\r", + " 下面,我们就可以使用js来操作这个\r[gold]uniform\r了,也就是说,我们可以使用高级动画插件操作这个全局变量来实现动画效果了。下面我们直接创建高级动画实例进行动画操作:\n\r[yellow]const ani = new Animation();\nani.register('grayscale', 0);\nani.time(5000).mode(hyper('sin', 'in-out')).absolute().apply('grayscale', 1);\r\n 这样,我们就创建了一个动画属性,下面我们要将它与全局变量绑定器联系起来。这里的高级动画的意思是:动画时间5000毫秒,速率曲线为hyper sin(双曲正弦)函数,in-out(慢-快-慢)变化方式,绝对变化,最终grayscale变为1", + " 联系起来的方式很简单,直接使用内置方法每帧赋值即可:\n\r[yellow]ani.ticker.add(() => binder.set(ani.value.grayscale));\r", + " 这样动画也配置完毕了,后面便可以直接渲染特效,然后在页面上展示了,唯一不同的点是不能再向\r[gold]update\r的第一个参数传入true了,这会重新编译着色器,导致绑定器失效。下面是后面的代码:\n\r[yellow]effect.update();\nconst ticker = setTickerFor(effect);\nconst manager = replaceGameCanvas(effect, canvas);\r\n 由于全部源码较长,这里就不放完整源码了。", + "下面是最终效果。", + { + "type": "function", + "function": "function(){\nflags.lastShaderSample?.end();\nflags.lastShaderSample = core.shaderSample5();\n}" + } + ], + "1,1": [ + "这里是第二个特效实例。", + "这个实例将模拟老实电视的显示不稳定", + "这个特效本质上是对颜色进行一定程度的噪声处理,因此我们只需要创建一个噪声偏移量的全局变量,然后让它一直以恒定的速率增加就行了,这甚至不需要高级动画实例的参与。", + " 它的片元着色器脚本:\n\r[yellow]uniform float uOffset;\n\nfloat 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 n = noise(rgba.r + rgba.g + rgba.b + uOffset) / 10.0;\n \n gl_FragColor = vec4(rgba.rgb + n, rgba.a);\n}\r", + "它在动画部分的脚本(注意从高级动画插件引入Ticker):\n\r[yellow]const ticker = new Ticker();\nticker.add(() => binder.set(binder.get() + 0.01));\r", + { + "type": "function", + "function": "function(){\nflags.lastShaderSample?.end();\nflags.lastShaderSample = core.shaderSample6();\n}" + } + ], + "9,1": [ + "这里是第三个特效实例。", + "这个实例将让上一层的第四个实例动起来", + "实际伤这与本层的第三实例类似,依然是加一个offset在噪声上。", + " 它的片元着色器脚本:\n\r[yellow]uniform float uOffset;\n\nfloat noise(float x) {\n float y = fract(sin(x) * 100000.0);\n return y;\n}\n\nvoid main() {\n float brigtness = -0.1 + noise(uOffset) / 50.0;\n vec2 xy = vTextureCoord;\n float x = xy.x + noise(xy.y + uOffset) / 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", + "它在动画部分的脚本(注意从高级动画插件引入Ticker):\n\r[yellow]const ticker = new Ticker();\nticker.add(() => binder.set(binder.get() + 0.01));\r", + { + "type": "function", + "function": "function(){\nflags.lastShaderSample?.end();\nflags.lastShaderSample = core.shaderSample7();\n}" + } + ], + "11,1": [ + "这里是第四个特效实例。", + "这个实例将做一个四周边缘周期性变黑的特效", + " 它需要用到当前像素的位置,因此需要从顶点着色器中用\r[gold]varying\r变量传递至片元着色器中,那么它的顶点着色器:\n\r[yellow]varying vec4 vpos;\n\nvoid main() {\n vTextureCoord = aTextureCoord;\n vpos = aVertexPosition;\n gl_Position = aVertexPosition;\n}\r", + " 它的片元着色器脚本:\n\r[yellow]uniform float uStrength;\nvarying vec4 vpos;\n\nvoid main() {\n float alpha = clamp(distance(vec2(0, 0), vpos.xy) - 0.6, 0.0, 1.0) * uStrength;\n vec4 tex = texture2D(uSampler, vTextureCoord);\n gl_FragColor = vec4(tex.rgb * (1.0 - alpha), 1.0);\n}\r", + "它在动画部分的脚本(注意从高级动画插件引入Ticker):\n\r[yellow]const ticker = new Ticker();\nticker.add(time => binder.set((Math.sin(time / 2000 - Math.PI / 2) + 1) / 2);\r", + { + "type": "function", + "function": "function(){\nflags.lastShaderSample?.end();\nflags.lastShaderSample = core.shaderSample8();\n}" + } + ], + "13,1": [ + "这里是第五个特效实例。", + "这个实例将吧前两个特效结合起来,粗略实现泰拉瑞亚中月球领主来临前的特效。", + " 顶点着色器与第四个特效相同:\n\r[yellow]varying vec4 vpos;\n\nvoid main() {\n vTextureCoord = aTextureCoord;\n vpos = aVertexPosition;\n gl_Position = aVertexPosition;\n}\r", + " 它的片元着色器脚本:\n\r[yellow]uniform float uStrength;\nvarying vec4 vpos;\n\nfloat noise(float x) {\n float y = fract(sin(x) * 100000.0);\n return y;\n}\n\nvoid main() {\n float brigtness = -uStrength / 10.0;\n vec2 xy = vTextureCoord;\n float x = xy.x + noise(xy.y + uStrength) / 300.0 * uStrength;\n float y = xy.y;\n vec4 color = texture2D(uSampler, vec2(x, y));\n vec4 color1 = vec4(color.rgb + vec3(brigtness), color.a);\n float alpha = clamp(distance(vec2(0, 0), vpos.xy) - 0.6, 0.0, 1.0) * uStrength;\n gl_FragColor = vec4(color1.rgb * (1.0 - alpha), 1.0);\n}\r", + "它在动画部分的脚本(注意从高级动画插件引入Ticker):\n\r[yellow]const ticker = new Ticker();\nticker.add(time => binder.set((Math.sin(time / 2000 - Math.PI / 2) + 1) / 2);\r", + { + "type": "function", + "function": "function(){\nflags.lastShaderSample?.end();\nflags.lastShaderSample = core.shaderSample9();\n}" + } + ] + }, + "changeFloor": { + "7,1": { + "floorId": ":before", + "stair": "upFloor" + }, + "7,13": { + "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,129, 0,129, 0,129, 0, 88, 0,129, 0,129, 0,129, 1], + [ 1,10187, 0,10186, 0,10185, 0, 0, 0,10188, 0,10189, 0,10193, 1], + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1,201, 0,202, 0,203, 0,204, 0,205, 0,206, 0,207, 1], + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1,333, 0,336, 0,340, 0,345, 0,351, 0,358, 0,366, 1], + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1,376, 0,378, 0,381, 0,385, 0, 35, 0, 37, 0, 39, 1], + [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [ 1, 31, 0, 32, 0, 33, 0, 34, 0, 36, 0, 38, 0, 40, 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, 87, 0, 0, 0, 0, 0, 0, 1], + [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] ], + "bgmap": [ + +], + "fgmap": [ + +], + "bg2map": [ + +], + "fg2map": [ + +] } \ No newline at end of file diff --git a/public/project/maps.js b/public/project/maps.js index 3cfa587..ecf0818 100644 --- a/public/project/maps.js +++ b/public/project/maps.js @@ -572,6 +572,16 @@ var maps_90f36752_8815_4be8_b32b_d7fad1d0542e = "641": {"cls":"items","id":"I641"}, "642": {"cls":"items","id":"I642"}, "643": {"cls":"enemys","id":"E643"}, + "10185": {"cls":"tileset","id":"X10185","canPass":true}, + "10186": {"cls":"tileset","id":"X10186","canPass":true}, + "10187": {"cls":"tileset","id":"X10187","canPass":true}, + "10188": {"cls":"tileset","id":"X10188","canPass":true}, + "10189": {"cls":"tileset","id":"X10189","canPass":true}, + "10193": {"cls":"tileset","id":"X10193","canPass":true}, + "10194": {"cls":"tileset","id":"X10194","canPass":true}, + "10195": {"cls":"tileset","id":"X10195","canPass":true}, + "10196": {"cls":"tileset","id":"X10196","canPass":true}, + "10197": {"cls":"tileset","id":"X10197","canPass":true}, "20032": {"cls":"tileset","id":"X20032","cannotOut":["up","left"],"cannotIn":["up","left"]}, "20033": {"cls":"tileset","id":"X20033","cannotOut":["up"],"cannotIn":["up"]}, "20034": {"cls":"tileset","id":"X20034","cannotOut":["up","right"],"cannotIn":["up","right"]}, diff --git a/src/plugin/fx/shader.ts b/src/plugin/fx/shader.ts index fbac5ef..869e2b5 100644 --- a/src/plugin/fx/shader.ts +++ b/src/plugin/fx/shader.ts @@ -1,4 +1,5 @@ -import { Ticker } from 'mutate-animate'; +import { Animation, Ticker, hyper } from 'mutate-animate'; +import { ensureArray } from '../utils'; const isWebGLSupported = (() => { return !!document.createElement('canvas').getContext('webgl'); @@ -29,6 +30,32 @@ interface MixedImage { update(): void; } +type UniformBinderNum = 1 | 2 | 3 | 4; +type UniformBinderType = 'f' | 'i'; +type UniformFunc< + N extends UniformBinderNum, + T extends UniformBinderType, + V extends 'v' | '' +> = `uniform${N}${T}${V}`; + +type UniformBinderValue = N extends 1 + ? number + : N extends 2 + ? [number, number] + : N extends 3 + ? [number, number, number] + : [number, number, number, number]; + +interface UniformBinder< + N extends UniformBinderNum, + T extends UniformBinderType, + V extends 'v' | '' +> { + value: UniformBinderValue; + set(value: UniformBinderValue): void; + get(): UniformBinderValue; +} + export default function init() { return { ShaderEffect, @@ -37,7 +64,12 @@ export default function init() { shaderSample1: sample1, shaderSample2: sample2, shaderSample3: sample3, - shaderSample4: sample4 + shaderSample4: sample4, + shaderSample5: sample5, + shaderSample6: sample6, + shaderSample7: sample7, + shaderSample8: sample8, + shaderSample9: sample9 }; } @@ -97,6 +129,170 @@ const sample4 = buildSample( ` ); +const sample5 = buildAnimtedSample( + ['bg', 'bg2', 'event', 'fg', 'fg2', 'hero'], + ` + uniform float uGrayscale; + + void main() { + vec4 rgba = texture2D(uSampler, vTextureCoord); + float avr = (rgba.r + rgba.g + rgba.b) / 3.0; + float dr = (rgba.r - avr) * (1.0 - uGrayscale); + float dg = (rgba.g - avr) * (1.0 - uGrayscale); + float db = (rgba.b - avr) * (1.0 - uGrayscale); + + gl_FragColor = vec4(avr + dr, avr + dg, avr + db, rgba.a); + }`, + effect => { + const binder = effect.createUniformBinder('uGrayscale', 1, 'f', ''); + binder.set(0); + const ani = new Animation(); + ani.register('grayscale', 0); + ani.time(5000) + .mode(hyper('sin', 'in-out')) + .absolute() + .apply('grayscale', 1); + ani.ticker.add(() => binder.set(ani.value.grayscale)); + + return [ani]; + } +); + +const sample6 = buildAnimtedSample( + ['bg', 'bg2', 'event', 'fg', 'fg2', 'hero'], + ` + uniform float uOffset; + + float noise(float x) { + float y = fract(sin(x) * 100000.0); + return y; + } + + void main() { + vec4 rgba = texture2D(uSampler, vTextureCoord); + float n = noise(rgba.r + rgba.g + rgba.b + uOffset) / 10.0; + + gl_FragColor = vec4(rgba.rgb + n, rgba.a); + } + `, + effect => { + const binder = effect.createUniformBinder('uOffset', 1, 'f', ''); + binder.set(0); + const ani = new Animation(); + ani.ticker.add(() => binder.set(binder.get() + 0.001)); + + return [ani]; + } +); + +const sample7 = buildAnimtedSample( + ['bg', 'bg2', 'event', 'fg', 'fg2', 'hero'], + ` + uniform float uOffset; + + float noise(float x) { + float y = fract(sin(x) * 100000.0); + return y; + } + + void main() { + float brigtness = -0.1 + noise(uOffset) / 50.0; + vec2 xy = vTextureCoord; + float x = xy.x + noise(xy.y + uOffset) / 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; + } + `, + effect => { + const binder = effect.createUniformBinder('uOffset', 1, 'f', ''); + binder.set(0); + const ani = new Animation(); + ani.ticker.add(() => binder.set(binder.get() + 0.001)); + + return [ani]; + } +); + +const sample8 = buildAnimtedSample( + ['bg', 'bg2', 'event', 'fg', 'fg2', 'hero'], + ` + uniform float uStrength; + varying vec4 vpos; + + void main() { + float alpha = clamp(distance(vec2(0, 0), vpos.xy) - 0.6, 0.0, 1.0) * uStrength; + vec4 tex = texture2D(uSampler, vTextureCoord); + gl_FragColor = vec4(color1.rgb * (1.0 - alpha), 1.0); + } + `, + effect => { + const binder = effect.createUniformBinder('uStrength', 1, 'f', ''); + binder.set(0); + const ani = new Animation(); + ani.ticker.add(time => { + binder.set((Math.sin(time / 2000 - Math.PI / 2) + 1) / 2); + }); + + return [ani]; + }, + ` + varying vec4 vpos; + + void main() { + vTextureCoord = aTextureCoord; + vpos = aVertexPosition; + gl_Position = aVertexPosition; + } + ` +); + +const sample9 = buildAnimtedSample( + ['bg', 'bg2', 'event', 'fg', 'fg2', 'hero'], + ` + uniform float uStrength; + varying vec4 vpos; + + float noise(float x) { + float y = fract(sin(x) * 100000.0); + return y; + } + + void main() { + float brigtness = -uStrength / 10.0; + vec2 xy = vTextureCoord; + float x = xy.x + noise(xy.y + uStrength + 1.0) / 300.0 * uStrength; + float y = xy.y; + vec4 color = texture2D(uSampler, vec2(x, y)); + vec4 color1 = vec4(color.rgb + vec3(brigtness), color.a); + + float alpha = clamp(distance(vec2(0, 0), vpos.xy) - 0.6, 0.0, 1.0) * uStrength; + gl_FragColor = vec4(color1.rgb * (1.0 - alpha), 1.0); + } + `, + effect => { + const binder = effect.createUniformBinder('uStrength', 1, 'f', ''); + binder.set(0); + const ani = new Animation(); + ani.ticker.add(time => { + binder.set((Math.sin(time / 2000 - Math.PI / 2) + 1) / 2); + }); + + return [ani]; + }, + ` + varying vec4 vpos; + + void main() { + vTextureCoord = aTextureCoord; + vpos = aVertexPosition; + gl_Position = aVertexPosition; + } + ` +); + function buildSample(canvas: string[], fs: string) { return () => { const effect = new ShaderEffect([0, 0, 0, 0]); @@ -117,6 +313,35 @@ function buildSample(canvas: string[], fs: string) { }; } +function buildAnimtedSample( + canvas: string[], + fs: string, + bind: (effect: ShaderEffect) => Animation[], + vs?: string +) { + return () => { + const effect = new ShaderEffect([0, 0, 0, 0]); + effect.baseImage(...canvas.map(v => core.canvas[v].canvas)); + effect.vs(vs ?? ShaderEffect.defaultVs); + effect.fs(fs); + effect.compile(); + + const anis = bind(effect); + + effect.update(); + const ticker = setTickerFor(effect); + const manager = replaceGameCanvas(effect, canvas); + + return { + end() { + ticker.destroy(); + manager.remove(); + anis.forEach(v => v.ticker.destroy()); + } + }; + }; +} + const builtinVs = ` #ifdef GL_ES precision highp float; @@ -189,25 +414,31 @@ export class ShaderEffect { * @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); - } + if (compile) this.compile(); this.textureCanvas?.update(); this.drawScene(); } + /** + * 仅重新编译着色器,不进行纹理创建和特效渲染 + */ + compile() { + const gl = this.gl; + 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); + gl.useProgram(this.program); + } + /** * 设置顶点着色器,使用 glsl 编写,插件提供了一些新的 api * 着色器中必须包含 main 函数,同时为 gl_Position 赋值 @@ -265,6 +496,64 @@ export class ShaderEffect { gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); } + /** + * 创建一个全局变量绑定器,用于操作全局变量 + * @param uniform 全局变量的变量名 + * @param num 变量的元素数量,float和int视为1,vec2 vec3 vec4分别视为 2 3 4 + * @param type 数据类型,可以填'f',表示浮点型,或者填'i',表示整型 + * @param vector 是否为向量,可以填'v',表示是向量,或者填'',表示不是向量 + * @returns 一个uniform绑定器,用于操作全局变量uniform + */ + createUniformBinder< + N extends UniformBinderNum, + T extends UniformBinderType, + V extends 'v' | '' + >(uniform: string, num: N, type: T, vector: V): UniformBinder { + if (!this.program) { + throw new Error( + `Uniform binder should be use when the program initialized.` + ); + } + + const suffix = `${num}${type}${vector ? 'v' : ''}`; + const func = `uniform${suffix}` as UniformFunc; + const value = ( + num === 1 ? 0 : Array(num).fill(0) + ) as UniformBinderValue; + + const loc = this.gl.getUniformLocation(this.program, uniform); + const gl = this.gl; + + return { + value, + set(value) { + this.value = value; + let v; + if (vector === 'v') { + let _v = ensureArray(value); + if (type === 'f') { + v = new Float32Array(_v); + } else { + v = new Int32Array(_v); + } + } else { + v = ensureArray(value); + } + // 对uniform赋值 + if (vector === 'v') { + // @ts-ignore + gl[func](loc, v); + } else { + // @ts-ignore + gl[func](loc, ...v); + } + }, + get() { + return this.value; + } + }; + } + private createProgram() { const gl = this.gl; const vs = this.loadShader(gl.VERTEX_SHADER, this.vsSource); diff --git a/src/plugin/utils.ts b/src/plugin/utils.ts index bc35136..a8cc217 100644 --- a/src/plugin/utils.ts +++ b/src/plugin/utils.ts @@ -228,3 +228,10 @@ export function changeLocalStorage( const to = fn(now); core.setLocalStorage(name, to); } + +export function ensureArray( + value: T +): T extends any[] ? T : T[] { + // @ts-ignore + return value instanceof Array ? value : [value]; +} diff --git a/src/source/cls.d.ts b/src/source/cls.d.ts index 237930c..78c875f 100644 --- a/src/source/cls.d.ts +++ b/src/source/cls.d.ts @@ -571,6 +571,16 @@ interface IdToCls { I641: 'items'; I642: 'items'; E643: 'enemys'; + X10185: 'tileset'; + X10186: 'tileset'; + X10187: 'tileset'; + X10188: 'tileset'; + X10189: 'tileset'; + X10193: 'tileset'; + X10194: 'tileset'; + X10195: 'tileset'; + X10196: 'tileset'; + X10197: 'tileset'; X20032: 'tileset'; X20033: 'tileset'; X20034: 'tileset'; diff --git a/src/source/maps.d.ts b/src/source/maps.d.ts index a3f03f5..e745d1e 100644 --- a/src/source/maps.d.ts +++ b/src/source/maps.d.ts @@ -571,6 +571,16 @@ interface IdToNumber { I641: 641; I642: 642; E643: 643; + X10185: 10185; + X10186: 10186; + X10187: 10187; + X10188: 10188; + X10189: 10189; + X10193: 10193; + X10194: 10194; + X10195: 10195; + X10196: 10196; + X10197: 10197; X20032: 20032; X20033: 20033; X20034: 20034; @@ -1225,6 +1235,16 @@ interface NumberToId { 641: 'I641'; 642: 'I642'; 643: 'E643'; + 10185: 'X10185'; + 10186: 'X10186'; + 10187: 'X10187'; + 10188: 'X10188'; + 10189: 'X10189'; + 10193: 'X10193'; + 10194: 'X10194'; + 10195: 'X10195'; + 10196: 'X10196'; + 10197: 'X10197'; 20032: 'X20032'; 20033: 'X20033'; 20034: 'X20034';