emeraldengine 2.2.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2359 -968
- package/dist/types/index.d.ts +72 -32
- package/dist/types/src/Animator.d.ts +50 -0
- package/dist/types/{BitmapText.d.ts → src/BitmapText.d.ts} +19 -21
- package/dist/types/src/Camera.d.ts +122 -0
- package/dist/types/src/CameraController.d.ts +107 -0
- package/dist/types/src/CanvasText.d.ts +91 -0
- package/dist/types/src/CollisionLayers.d.ts +58 -0
- package/dist/types/{Color.d.ts → src/Color.d.ts} +5 -6
- package/dist/types/src/Coroutine.d.ts +111 -0
- package/dist/types/src/DebugOverlay.d.ts +86 -0
- package/dist/types/src/Drawable.d.ts +271 -0
- package/dist/types/src/Easing.d.ts +22 -0
- package/dist/types/src/Emerald.d.ts +420 -0
- package/dist/types/src/EmeraldDB.d.ts +159 -0
- package/dist/types/{FPSCounter.d.ts → src/FPSCounter.d.ts} +1 -3
- package/dist/types/src/GLUtils.d.ts +4 -0
- package/dist/types/{Instance.d.ts → src/Instance.d.ts} +37 -15
- package/dist/types/{InstancedTexture.d.ts → src/InstancedTexture.d.ts} +60 -23
- package/dist/types/src/Interpolator.d.ts +66 -0
- package/dist/types/src/Material.d.ts +87 -0
- package/dist/types/src/MathUtils.d.ts +80 -0
- package/dist/types/src/ParticleEmitter.d.ts +131 -0
- package/dist/types/{Physics.d.ts → src/Physics.d.ts} +88 -20
- package/dist/types/src/Pool.d.ts +53 -0
- package/dist/types/src/PostEffects.d.ts +68 -0
- package/dist/types/src/PostProcessor.d.ts +124 -0
- package/dist/types/src/RenderTarget.d.ts +56 -0
- package/dist/types/src/Scene.d.ts +62 -0
- package/dist/types/src/ScreenEffects.d.ts +111 -0
- package/dist/types/src/Serializer.d.ts +86 -0
- package/dist/types/src/Shaders.d.ts +2 -0
- package/dist/types/{Shapes.d.ts → src/Shapes.d.ts} +4 -6
- package/dist/types/src/SpatialGrid.d.ts +50 -0
- package/dist/types/src/SpriteBatch.d.ts +89 -0
- package/dist/types/src/StateMachine.d.ts +59 -0
- package/dist/types/src/Storage.d.ts +89 -0
- package/dist/types/{Texture.d.ts → src/Texture.d.ts} +3 -4
- package/dist/types/src/TextureAtlas.d.ts +55 -0
- package/dist/types/src/Tilemap.d.ts +91 -0
- package/dist/types/src/Time.d.ts +53 -0
- package/dist/types/src/Timer.d.ts +54 -0
- package/dist/types/src/Transform.d.ts +94 -0
- package/dist/types/src/Tween.d.ts +81 -0
- package/dist/types/src/UI.d.ts +121 -0
- package/dist/types/src/components/Behaviour.d.ts +71 -0
- package/dist/types/{components → src/components}/BoxCollider.d.ts +14 -14
- package/dist/types/src/components/BoxColliderDebug.d.ts +15 -0
- package/dist/types/{components → src/components}/CircleCollider.d.ts +14 -12
- package/dist/types/src/components/CircleColliderDebug.d.ts +15 -0
- package/dist/types/src/components/Collider.d.ts +96 -0
- package/dist/types/src/components/GameObject.d.ts +135 -0
- package/dist/types/src/components/RigidBody.d.ts +183 -0
- package/dist/types/src/importers/Aseprite.d.ts +79 -0
- package/dist/types/src/importers/TiledMap.d.ts +62 -0
- package/dist/types/{lights → src/lights}/DirectionalLight.d.ts +7 -9
- package/dist/types/{lights → src/lights}/PointLight.d.ts +6 -9
- package/dist/types/src/managers/AssetManager.d.ts +116 -0
- package/dist/types/src/managers/AudioManager.d.ts +259 -0
- package/dist/types/{managers → src/managers}/CameraManager.d.ts +8 -8
- package/dist/types/{managers → src/managers}/EventManager.d.ts +46 -32
- package/dist/types/src/managers/GLManager.d.ts +84 -0
- package/dist/types/src/managers/GLState.d.ts +37 -0
- package/dist/types/src/managers/IDManager.d.ts +31 -0
- package/dist/types/src/managers/InputManager.d.ts +290 -0
- package/dist/types/src/managers/NetworkManager.d.ts +93 -0
- package/dist/types/src/managers/RenderStats.d.ts +34 -0
- package/dist/types/src/managers/SceneManager.d.ts +44 -0
- package/dist/types/src/managers/ShaderManager.d.ts +55 -0
- package/dist/types/src/managers/TextureManager.d.ts +102 -0
- package/dist/types/src/particlesystem/Particle.d.ts +64 -0
- package/dist/types/{particlesystem → src/particlesystem}/ParticleSettings.d.ts +32 -24
- package/dist/types/{particlesystem → src/particlesystem}/Particles.d.ts +20 -12
- package/index.js +72 -0
- package/package.json +74 -60
- package/src/Animator.js +95 -0
- package/src/BitmapText.js +6 -5
- package/src/Camera.js +183 -0
- package/src/CameraController.js +192 -0
- package/src/CanvasText.js +281 -0
- package/src/CollisionLayers.js +86 -0
- package/src/Color.js +18 -18
- package/src/Coroutine.js +259 -0
- package/src/DebugOverlay.js +246 -0
- package/src/Drawable.js +842 -582
- package/src/Easing.js +57 -0
- package/src/Emerald.js +1150 -459
- package/src/EmeraldDB.js +328 -0
- package/src/FPSCounter.js +43 -43
- package/src/GLUtils.js +60 -67
- package/src/Instance.js +41 -5
- package/src/InstancedTexture.js +251 -118
- package/src/Interpolator.js +124 -0
- package/src/Material.js +202 -0
- package/src/MathUtils.js +133 -0
- package/src/ParticleEmitter.js +284 -0
- package/src/Physics.js +186 -5
- package/src/Pool.js +85 -0
- package/src/PostEffects.js +296 -0
- package/src/PostProcessor.js +304 -0
- package/src/RenderTarget.js +134 -0
- package/src/Scene.js +115 -83
- package/src/ScreenEffects.js +266 -0
- package/src/Serializer.js +131 -0
- package/src/Shaders.js +150 -165
- package/src/Shapes.js +118 -129
- package/src/SpatialGrid.js +111 -0
- package/src/SpriteBatch.js +299 -0
- package/src/StateMachine.js +82 -0
- package/src/Storage.js +175 -47
- package/src/Texture.js +58 -67
- package/src/TextureAtlas.js +96 -0
- package/src/Tilemap.js +274 -0
- package/src/Time.js +51 -6
- package/src/Timer.js +99 -0
- package/src/Transform.js +100 -7
- package/src/Tween.js +160 -0
- package/src/UI.js +394 -0
- package/src/components/Behaviour.js +90 -0
- package/src/components/BoxCollider.js +22 -3
- package/src/components/CircleCollider.js +19 -3
- package/src/components/CircleColliderDebug.js +24 -24
- package/src/components/Collider.js +140 -34
- package/src/components/GameObject.js +130 -21
- package/src/components/RigidBody.js +123 -2
- package/src/importers/Aseprite.js +142 -0
- package/src/importers/TiledMap.js +158 -0
- package/src/lights/DirectionalLight.js +6 -15
- package/src/lights/PointLight.js +4 -4
- package/src/managers/AssetManager.js +239 -0
- package/src/managers/AudioManager.js +565 -146
- package/src/managers/EventManager.js +488 -477
- package/src/managers/GLManager.js +57 -0
- package/src/managers/GLState.js +70 -0
- package/src/managers/IDManager.js +24 -2
- package/src/managers/InputManager.js +779 -0
- package/src/managers/NetworkManager.js +178 -0
- package/src/managers/RenderStats.js +34 -0
- package/src/managers/SceneManager.js +30 -0
- package/src/managers/ShaderManager.js +0 -2
- package/src/managers/TextureManager.js +218 -0
- package/src/particlesystem/Particle.js +82 -7
- package/src/particlesystem/ParticleSettings.js +21 -3
- package/src/particlesystem/Particles.js +80 -31
- package/dist/types/Drawable.d.ts +0 -157
- package/dist/types/Emerald.d.ts +0 -73
- package/dist/types/GLUtils.d.ts +0 -6
- package/dist/types/Scene.d.ts +0 -39
- package/dist/types/Shaders.d.ts +0 -4
- package/dist/types/Storage.d.ts +0 -46
- package/dist/types/Time.d.ts +0 -22
- package/dist/types/Transform.d.ts +0 -41
- package/dist/types/components/BoxColliderDebug.d.ts +0 -19
- package/dist/types/components/CircleColliderDebug.d.ts +0 -19
- package/dist/types/components/Collider.d.ts +0 -53
- package/dist/types/components/GameObject.d.ts +0 -72
- package/dist/types/components/RigidBody.d.ts +0 -104
- package/dist/types/managers/AudioManager.d.ts +0 -60
- package/dist/types/managers/GLManager.d.ts +0 -47
- package/dist/types/managers/IDManager.d.ts +0 -21
- package/dist/types/managers/SceneManager.d.ts +0 -22
- package/dist/types/particlesystem/Particle.d.ts +0 -42
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { PostEffect } from "./PostProcessor.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @class BloomEffect
|
|
5
|
+
* @description Multi-pass bloom: extract bright pixels above a threshold, blur
|
|
6
|
+
* them with a separable Gaussian, then add the result back over the scene. Built
|
|
7
|
+
* via {@link PostEffects.bloom}.
|
|
8
|
+
*/
|
|
9
|
+
class BloomEffect extends PostEffect {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
super("bloom", `void main(){ gl_FragColor = texture2D(uScene, vUV); }`);
|
|
12
|
+
this.threshold = options.threshold ?? 0.7;
|
|
13
|
+
this.intensity = options.intensity ?? 1.0;
|
|
14
|
+
this.spread = options.spread ?? 1.0;
|
|
15
|
+
|
|
16
|
+
/** @private */
|
|
17
|
+
this._threshold = new PostEffect(
|
|
18
|
+
"bloom:threshold",
|
|
19
|
+
`
|
|
20
|
+
uniform float uThreshold;
|
|
21
|
+
void main() {
|
|
22
|
+
vec4 c = texture2D(uScene, vUV);
|
|
23
|
+
float l = dot(c.rgb, vec3(0.2126, 0.7152, 0.0722));
|
|
24
|
+
gl_FragColor = l > uThreshold ? c : vec4(0.0, 0.0, 0.0, 1.0);
|
|
25
|
+
}`,
|
|
26
|
+
{
|
|
27
|
+
setUniforms: (gl, loc) =>
|
|
28
|
+
gl.uniform1f(loc("uThreshold"), this.threshold),
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const blur = (horizontal) => `
|
|
33
|
+
uniform float uSpread;
|
|
34
|
+
void main() {
|
|
35
|
+
vec2 texel = (${horizontal ? "vec2(1.0, 0.0)" : "vec2(0.0, 1.0)"}) / uResolution * uSpread;
|
|
36
|
+
float w[5];
|
|
37
|
+
w[0] = 0.227027; w[1] = 0.1945946; w[2] = 0.1216216; w[3] = 0.054054; w[4] = 0.016216;
|
|
38
|
+
vec3 result = texture2D(uScene, vUV).rgb * w[0];
|
|
39
|
+
for (int i = 1; i < 5; i++) {
|
|
40
|
+
result += texture2D(uScene, vUV + texel * float(i)).rgb * w[i];
|
|
41
|
+
result += texture2D(uScene, vUV - texel * float(i)).rgb * w[i];
|
|
42
|
+
}
|
|
43
|
+
gl_FragColor = vec4(result, 1.0);
|
|
44
|
+
}`;
|
|
45
|
+
const blurUniforms = {
|
|
46
|
+
setUniforms: (gl, loc) => gl.uniform1f(loc("uSpread"), this.spread),
|
|
47
|
+
};
|
|
48
|
+
/** @private */
|
|
49
|
+
this._blurH = new PostEffect("bloom:blurH", blur(true), blurUniforms);
|
|
50
|
+
/** @private */
|
|
51
|
+
this._blurV = new PostEffect("bloom:blurV", blur(false), blurUniforms);
|
|
52
|
+
|
|
53
|
+
/** @private */
|
|
54
|
+
this._composite = new PostEffect(
|
|
55
|
+
"bloom:composite",
|
|
56
|
+
`
|
|
57
|
+
uniform sampler2D uBloom;
|
|
58
|
+
uniform float uIntensity;
|
|
59
|
+
void main() {
|
|
60
|
+
vec4 scene = texture2D(uScene, vUV);
|
|
61
|
+
vec3 bloom = texture2D(uBloom, vUV).rgb;
|
|
62
|
+
gl_FragColor = vec4(scene.rgb + bloom * uIntensity, scene.a);
|
|
63
|
+
}`,
|
|
64
|
+
{
|
|
65
|
+
setUniforms: (gl, loc) => {
|
|
66
|
+
gl.uniform1f(loc("uIntensity"), this.intensity);
|
|
67
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
68
|
+
gl.bindTexture(gl.TEXTURE_2D, this._bloomTex);
|
|
69
|
+
gl.uniform1i(loc("uBloom"), 1);
|
|
70
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @private */
|
|
77
|
+
_compile(gl) {
|
|
78
|
+
super._compile(gl);
|
|
79
|
+
this._threshold._compile(gl);
|
|
80
|
+
this._blurH._compile(gl);
|
|
81
|
+
this._blurV._compile(gl);
|
|
82
|
+
this._composite._compile(gl);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
render(ctx) {
|
|
86
|
+
const temp0 = ctx.acquireTemp(0);
|
|
87
|
+
const temp1 = ctx.acquireTemp(1);
|
|
88
|
+
ctx.blit(this._threshold, ctx.input, temp0);
|
|
89
|
+
ctx.blit(this._blurH, temp0.texture, temp1);
|
|
90
|
+
ctx.blit(this._blurV, temp1.texture, temp0);
|
|
91
|
+
/** @private */
|
|
92
|
+
this._bloomTex = temp0.texture;
|
|
93
|
+
ctx.blit(this._composite, ctx.input, ctx.output);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @namespace PostEffects
|
|
99
|
+
* @description Factory functions for the built-in post-processing effects. Pass
|
|
100
|
+
* the returned PostEffect to `emerald.addPostEffect(...)`.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* emerald.enablePostProcessing();
|
|
104
|
+
* emerald.addPostEffect(PostEffects.bloom({ threshold: 0.6, intensity: 1.2 }));
|
|
105
|
+
* emerald.addPostEffect(PostEffects.vignette({ intensity: 0.5 }));
|
|
106
|
+
* emerald.addPostEffect(PostEffects.crt());
|
|
107
|
+
*/
|
|
108
|
+
const PostEffects = {
|
|
109
|
+
/**
|
|
110
|
+
* @method grayscale
|
|
111
|
+
* @description Desaturates the image.
|
|
112
|
+
*/
|
|
113
|
+
grayscale() {
|
|
114
|
+
return new PostEffect(
|
|
115
|
+
"grayscale",
|
|
116
|
+
`void main() {
|
|
117
|
+
vec4 c = texture2D(uScene, vUV);
|
|
118
|
+
float l = dot(c.rgb, vec3(0.299, 0.587, 0.114));
|
|
119
|
+
gl_FragColor = vec4(vec3(l), c.a);
|
|
120
|
+
}`
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @method vignette
|
|
126
|
+
* @description Darkens the edges of the screen.
|
|
127
|
+
* @param {Object} [options] - { intensity = 0.5, radius = 0.75, softness = 0.45 }
|
|
128
|
+
*/
|
|
129
|
+
vignette(options = {}) {
|
|
130
|
+
const intensity = options.intensity ?? 0.5;
|
|
131
|
+
const radius = options.radius ?? 0.75;
|
|
132
|
+
const softness = options.softness ?? 0.45;
|
|
133
|
+
return new PostEffect(
|
|
134
|
+
"vignette",
|
|
135
|
+
`
|
|
136
|
+
uniform float uIntensity;
|
|
137
|
+
uniform float uRadius;
|
|
138
|
+
uniform float uSoftness;
|
|
139
|
+
void main() {
|
|
140
|
+
vec4 c = texture2D(uScene, vUV);
|
|
141
|
+
float d = distance(vUV, vec2(0.5));
|
|
142
|
+
float v = smoothstep(uRadius, uRadius - uSoftness, d);
|
|
143
|
+
c.rgb *= mix(1.0 - uIntensity, 1.0, v);
|
|
144
|
+
gl_FragColor = c;
|
|
145
|
+
}`,
|
|
146
|
+
{
|
|
147
|
+
setUniforms: (gl, loc) => {
|
|
148
|
+
gl.uniform1f(loc("uIntensity"), intensity);
|
|
149
|
+
gl.uniform1f(loc("uRadius"), radius);
|
|
150
|
+
gl.uniform1f(loc("uSoftness"), softness);
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @method colorGrade
|
|
158
|
+
* @description Adjusts brightness, contrast and saturation.
|
|
159
|
+
* @param {Object} [options] - { brightness = 0, contrast = 1, saturation = 1 }
|
|
160
|
+
*/
|
|
161
|
+
colorGrade(options = {}) {
|
|
162
|
+
const brightness = options.brightness ?? 0;
|
|
163
|
+
const contrast = options.contrast ?? 1;
|
|
164
|
+
const saturation = options.saturation ?? 1;
|
|
165
|
+
return new PostEffect(
|
|
166
|
+
"colorGrade",
|
|
167
|
+
`
|
|
168
|
+
uniform float uBrightness;
|
|
169
|
+
uniform float uContrast;
|
|
170
|
+
uniform float uSaturation;
|
|
171
|
+
void main() {
|
|
172
|
+
vec4 c = texture2D(uScene, vUV);
|
|
173
|
+
vec3 col = c.rgb + uBrightness;
|
|
174
|
+
col = (col - 0.5) * uContrast + 0.5;
|
|
175
|
+
float l = dot(col, vec3(0.299, 0.587, 0.114));
|
|
176
|
+
col = mix(vec3(l), col, uSaturation);
|
|
177
|
+
gl_FragColor = vec4(clamp(col, 0.0, 1.0), c.a);
|
|
178
|
+
}`,
|
|
179
|
+
{
|
|
180
|
+
setUniforms: (gl, loc) => {
|
|
181
|
+
gl.uniform1f(loc("uBrightness"), brightness);
|
|
182
|
+
gl.uniform1f(loc("uContrast"), contrast);
|
|
183
|
+
gl.uniform1f(loc("uSaturation"), saturation);
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @method chromaticAberration
|
|
191
|
+
* @description Splits the RGB channels toward the screen edges.
|
|
192
|
+
* @param {Object} [options] - { amount = 0.003 }
|
|
193
|
+
*/
|
|
194
|
+
chromaticAberration(options = {}) {
|
|
195
|
+
const amount = options.amount ?? 0.003;
|
|
196
|
+
return new PostEffect(
|
|
197
|
+
"chromaticAberration",
|
|
198
|
+
`
|
|
199
|
+
uniform float uAmount;
|
|
200
|
+
void main() {
|
|
201
|
+
vec2 dir = vUV - 0.5;
|
|
202
|
+
vec2 off = dir * uAmount;
|
|
203
|
+
float r = texture2D(uScene, vUV + off).r;
|
|
204
|
+
float g = texture2D(uScene, vUV).g;
|
|
205
|
+
float b = texture2D(uScene, vUV - off).b;
|
|
206
|
+
float a = texture2D(uScene, vUV).a;
|
|
207
|
+
gl_FragColor = vec4(r, g, b, a);
|
|
208
|
+
}`,
|
|
209
|
+
{ setUniforms: (gl, loc) => gl.uniform1f(loc("uAmount"), amount) }
|
|
210
|
+
);
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @method scanlines
|
|
215
|
+
* @description Overlays horizontal scanlines.
|
|
216
|
+
* @param {Object} [options] - { intensity = 0.15, count = 480 }
|
|
217
|
+
*/
|
|
218
|
+
scanlines(options = {}) {
|
|
219
|
+
const intensity = options.intensity ?? 0.15;
|
|
220
|
+
const count = options.count ?? 480;
|
|
221
|
+
return new PostEffect(
|
|
222
|
+
"scanlines",
|
|
223
|
+
`
|
|
224
|
+
uniform float uIntensity;
|
|
225
|
+
uniform float uCount;
|
|
226
|
+
void main() {
|
|
227
|
+
vec4 c = texture2D(uScene, vUV);
|
|
228
|
+
float s = sin(vUV.y * uCount * 3.14159265);
|
|
229
|
+
c.rgb *= 1.0 - uIntensity * (0.5 + 0.5 * s) ;
|
|
230
|
+
gl_FragColor = c;
|
|
231
|
+
}`,
|
|
232
|
+
{
|
|
233
|
+
setUniforms: (gl, loc) => {
|
|
234
|
+
gl.uniform1f(loc("uIntensity"), intensity);
|
|
235
|
+
gl.uniform1f(loc("uCount"), count);
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @method crt
|
|
243
|
+
* @description Retro CRT look: barrel distortion, scanlines and edge vignette.
|
|
244
|
+
* @param {Object} [options] - { curvature = 4.0, scanlineIntensity = 0.2, vignette = 0.3 }
|
|
245
|
+
*/
|
|
246
|
+
crt(options = {}) {
|
|
247
|
+
const curvature = options.curvature ?? 4.0;
|
|
248
|
+
const scanlineIntensity = options.scanlineIntensity ?? 0.2;
|
|
249
|
+
const vignette = options.vignette ?? 0.3;
|
|
250
|
+
return new PostEffect(
|
|
251
|
+
"crt",
|
|
252
|
+
`
|
|
253
|
+
uniform float uCurvature;
|
|
254
|
+
uniform float uScanline;
|
|
255
|
+
uniform float uVignette;
|
|
256
|
+
vec2 curve(vec2 uv) {
|
|
257
|
+
uv = uv * 2.0 - 1.0;
|
|
258
|
+
vec2 offset = abs(uv.yx) / uCurvature;
|
|
259
|
+
uv = uv + uv * offset * offset;
|
|
260
|
+
return uv * 0.5 + 0.5;
|
|
261
|
+
}
|
|
262
|
+
void main() {
|
|
263
|
+
vec2 uv = curve(vUV);
|
|
264
|
+
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
|
|
265
|
+
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
vec4 c = texture2D(uScene, uv);
|
|
269
|
+
float scan = sin(uv.y * uResolution.y * 3.14159265);
|
|
270
|
+
c.rgb *= 1.0 - uScanline * (0.5 + 0.5 * scan);
|
|
271
|
+
float d = distance(uv, vec2(0.5));
|
|
272
|
+
c.rgb *= 1.0 - uVignette * d * d * 2.0;
|
|
273
|
+
gl_FragColor = c;
|
|
274
|
+
}`,
|
|
275
|
+
{
|
|
276
|
+
setUniforms: (gl, loc) => {
|
|
277
|
+
gl.uniform1f(loc("uCurvature"), curvature);
|
|
278
|
+
gl.uniform1f(loc("uScanline"), scanlineIntensity);
|
|
279
|
+
gl.uniform1f(loc("uVignette"), vignette);
|
|
280
|
+
},
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @method bloom
|
|
287
|
+
* @description Glow around bright areas (multi-pass).
|
|
288
|
+
* @param {Object} [options] - { threshold = 0.7, intensity = 1.0, spread = 1.0 }
|
|
289
|
+
*/
|
|
290
|
+
bloom(options = {}) {
|
|
291
|
+
return new BloomEffect(options);
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export default PostEffects;
|
|
296
|
+
export { BloomEffect };
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { initShaderProgram } from "./GLUtils.js";
|
|
2
|
+
import GLManager from "./managers/GLManager.js";
|
|
3
|
+
import RenderStats from "./managers/RenderStats.js";
|
|
4
|
+
import RenderTarget from "./RenderTarget.js";
|
|
5
|
+
|
|
6
|
+
const FULLSCREEN_VERTEX_SHADER = `
|
|
7
|
+
attribute vec2 aPos;
|
|
8
|
+
varying vec2 vUV;
|
|
9
|
+
void main() {
|
|
10
|
+
vUV = aPos * 0.5 + 0.5;
|
|
11
|
+
gl_Position = vec4(aPos, 0.0, 1.0);
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
const FRAGMENT_HEADER = `
|
|
16
|
+
#ifdef GL_ES
|
|
17
|
+
precision highp float;
|
|
18
|
+
#endif
|
|
19
|
+
varying vec2 vUV;
|
|
20
|
+
uniform sampler2D uScene;
|
|
21
|
+
uniform vec2 uResolution;
|
|
22
|
+
uniform float uTime;
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @class PostEffect
|
|
27
|
+
* @description A single full-screen post-processing pass: a fragment shader that
|
|
28
|
+
* samples the previous pass (`uScene`) and writes the next image. The shader
|
|
29
|
+
* automatically has `vUV`, `uScene`, `uResolution`, and `uTime` available; add
|
|
30
|
+
* your own uniforms and set them with the `setUniforms` callback.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* const tint = new PostEffect("tint", `
|
|
34
|
+
* uniform vec3 uTint;
|
|
35
|
+
* void main() { gl_FragColor = texture2D(uScene, vUV) * vec4(uTint, 1.0); }
|
|
36
|
+
* `, { setUniforms: (gl, loc) => gl.uniform3f(loc("uTint"), 1.0, 0.8, 0.8) });
|
|
37
|
+
*/
|
|
38
|
+
class PostEffect {
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} name - A label (for debugging)
|
|
41
|
+
* @param {string} fragmentSource - Fragment shader body (declares void main)
|
|
42
|
+
* @param {Object} [options] - { setUniforms(gl, loc, ctx), enabled = true }
|
|
43
|
+
*/
|
|
44
|
+
constructor(name, fragmentSource, options = {}) {
|
|
45
|
+
this.name = name;
|
|
46
|
+
this.fragmentSource = fragmentSource;
|
|
47
|
+
this.setUniforms = options.setUniforms || null;
|
|
48
|
+
this.enabled = options.enabled !== false;
|
|
49
|
+
this.program = null;
|
|
50
|
+
/** @private */
|
|
51
|
+
this._locCache = new Map();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** @private */
|
|
55
|
+
_compile(gl) {
|
|
56
|
+
if (this.program) return;
|
|
57
|
+
this.program = initShaderProgram(
|
|
58
|
+
gl,
|
|
59
|
+
FULLSCREEN_VERTEX_SHADER,
|
|
60
|
+
FRAGMENT_HEADER + this.fragmentSource
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @method _restoreGL
|
|
66
|
+
* @description Drops the dead program/locations after a context loss so the
|
|
67
|
+
* next _compile builds fresh ones.
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
_restoreGL() {
|
|
71
|
+
this.program = null;
|
|
72
|
+
this._locCache.clear();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
loc(gl, name) {
|
|
76
|
+
if (!this._locCache.has(name)) {
|
|
77
|
+
this._locCache.set(name, gl.getUniformLocation(this.program, name));
|
|
78
|
+
}
|
|
79
|
+
return this._locCache.get(name);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @method render
|
|
84
|
+
* @description Renders this effect from ctx.input into ctx.output. Override for
|
|
85
|
+
* multi-pass effects.
|
|
86
|
+
* @param {Object} ctx - Provided by the PostProcessor
|
|
87
|
+
*/
|
|
88
|
+
render(ctx) {
|
|
89
|
+
ctx.blit(this, ctx.input, ctx.output);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @class PostProcessor
|
|
95
|
+
* @description Drives a chain of full-screen {@link PostEffect}s. The scene is
|
|
96
|
+
* rendered into a texture, then each effect is applied in sequence (ping-ponging
|
|
97
|
+
* between two render targets), and the final result is drawn to the screen.
|
|
98
|
+
*
|
|
99
|
+
* Created and managed by Emerald when you call `emerald.enablePostProcessing()`;
|
|
100
|
+
* you usually just add effects via `emerald.addPostEffect(PostEffects.bloom())`.
|
|
101
|
+
*/
|
|
102
|
+
class PostProcessor {
|
|
103
|
+
constructor() {
|
|
104
|
+
this.gl = GLManager.getGL();
|
|
105
|
+
this.effects = [];
|
|
106
|
+
this.enabled = true;
|
|
107
|
+
|
|
108
|
+
const gl = this.gl;
|
|
109
|
+
/** @private */
|
|
110
|
+
this._quad = gl.createBuffer();
|
|
111
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._quad);
|
|
112
|
+
gl.bufferData(
|
|
113
|
+
gl.ARRAY_BUFFER,
|
|
114
|
+
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
|
|
115
|
+
gl.STATIC_DRAW
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
/** @private */
|
|
119
|
+
this._rtA = new RenderTarget(2, 2);
|
|
120
|
+
/** @private */
|
|
121
|
+
this._rtB = new RenderTarget(2, 2);
|
|
122
|
+
/** @private */
|
|
123
|
+
this._temps = [];
|
|
124
|
+
/** @private */
|
|
125
|
+
this._width = 2;
|
|
126
|
+
/** @private */
|
|
127
|
+
this._height = 2;
|
|
128
|
+
|
|
129
|
+
GLManager.registerRestorable(this);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @method _restoreGL
|
|
134
|
+
* @description Rebuilds the fullscreen quad, ping-pong render targets, and
|
|
135
|
+
* every effect's program after a WebGL context loss.
|
|
136
|
+
* @private
|
|
137
|
+
*/
|
|
138
|
+
_restoreGL() {
|
|
139
|
+
const gl = this.gl;
|
|
140
|
+
if (!gl) return;
|
|
141
|
+
this._quad = gl.createBuffer();
|
|
142
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._quad);
|
|
143
|
+
gl.bufferData(
|
|
144
|
+
gl.ARRAY_BUFFER,
|
|
145
|
+
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
|
|
146
|
+
gl.STATIC_DRAW
|
|
147
|
+
);
|
|
148
|
+
this._rtA = new RenderTarget(2, 2);
|
|
149
|
+
this._rtB = new RenderTarget(2, 2);
|
|
150
|
+
this._temps = [];
|
|
151
|
+
this._width = 2;
|
|
152
|
+
this._height = 2;
|
|
153
|
+
for (const effect of this.effects) {
|
|
154
|
+
effect._restoreGL();
|
|
155
|
+
effect._compile(gl);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @method addEffect
|
|
161
|
+
* @description Appends an effect to the chain.
|
|
162
|
+
* @param {PostEffect} effect
|
|
163
|
+
* @returns {PostProcessor} - this
|
|
164
|
+
*/
|
|
165
|
+
addEffect(effect) {
|
|
166
|
+
effect._compile(this.gl);
|
|
167
|
+
this.effects.push(effect);
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @method removeEffect
|
|
173
|
+
* @description Removes an effect from the chain.
|
|
174
|
+
*/
|
|
175
|
+
removeEffect(effect) {
|
|
176
|
+
const i = this.effects.indexOf(effect);
|
|
177
|
+
if (i !== -1) this.effects.splice(i, 1);
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @method clear
|
|
183
|
+
* @description Removes all effects.
|
|
184
|
+
*/
|
|
185
|
+
clear() {
|
|
186
|
+
this.effects.length = 0;
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @method hasEffects
|
|
192
|
+
* @returns {boolean} - Whether any enabled effects exist.
|
|
193
|
+
*/
|
|
194
|
+
hasEffects() {
|
|
195
|
+
return this.enabled && this.effects.some((e) => e.enabled);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** @private */
|
|
199
|
+
_acquireTemp(index) {
|
|
200
|
+
while (this._temps.length <= index) {
|
|
201
|
+
this._temps.push(new RenderTarget(this._width, this._height));
|
|
202
|
+
}
|
|
203
|
+
const t = this._temps[index];
|
|
204
|
+
t.resize(this._width, this._height);
|
|
205
|
+
return t;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @method process
|
|
210
|
+
* @description Applies the effect chain, drawing the final image to the canvas.
|
|
211
|
+
* @param {WebGLTexture} sceneTexture - The rendered scene
|
|
212
|
+
* @param {number} width - Canvas width in pixels
|
|
213
|
+
* @param {number} height - Canvas height in pixels
|
|
214
|
+
* @param {number} time - Seconds (for time-based effects)
|
|
215
|
+
*/
|
|
216
|
+
process(sceneTexture, width, height, time) {
|
|
217
|
+
const gl = this.gl;
|
|
218
|
+
this._width = width;
|
|
219
|
+
this._height = height;
|
|
220
|
+
this._rtA.resize(width, height);
|
|
221
|
+
this._rtB.resize(width, height);
|
|
222
|
+
|
|
223
|
+
const active = this.effects.filter((e) => e.enabled);
|
|
224
|
+
|
|
225
|
+
gl.disable(gl.DEPTH_TEST);
|
|
226
|
+
gl.disable(gl.BLEND);
|
|
227
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
228
|
+
|
|
229
|
+
const ctx = {
|
|
230
|
+
gl,
|
|
231
|
+
time,
|
|
232
|
+
width,
|
|
233
|
+
height,
|
|
234
|
+
processor: this,
|
|
235
|
+
input: sceneTexture,
|
|
236
|
+
output: null,
|
|
237
|
+
blit: (effect, input, output) => this._blit(effect, input, output, ctx),
|
|
238
|
+
acquireTemp: (i) => this._acquireTemp(i),
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
let input = sceneTexture;
|
|
242
|
+
for (let i = 0; i < active.length; i++) {
|
|
243
|
+
const isLast = i === active.length - 1;
|
|
244
|
+
const output = isLast ? null : i % 2 === 0 ? this._rtA : this._rtB;
|
|
245
|
+
ctx.input = input;
|
|
246
|
+
ctx.output = output;
|
|
247
|
+
active[i].render(ctx);
|
|
248
|
+
input = output ? output.texture : null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @method _blit
|
|
254
|
+
* @description Renders one effect program from `input` into `output` (null =
|
|
255
|
+
* screen), setting the standard uniforms.
|
|
256
|
+
* @private
|
|
257
|
+
*/
|
|
258
|
+
_blit(effect, input, output, ctx) {
|
|
259
|
+
const gl = this.gl;
|
|
260
|
+
if (output) {
|
|
261
|
+
output.bind();
|
|
262
|
+
} else {
|
|
263
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
264
|
+
gl.viewport(0, 0, this._width, this._height);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
gl.useProgram(effect.program);
|
|
268
|
+
|
|
269
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
270
|
+
gl.bindTexture(gl.TEXTURE_2D, input);
|
|
271
|
+
RenderStats.textureBinds++;
|
|
272
|
+
gl.uniform1i(effect.loc(gl, "uScene"), 0);
|
|
273
|
+
|
|
274
|
+
const resLoc = effect.loc(gl, "uResolution");
|
|
275
|
+
if (resLoc) gl.uniform2f(resLoc, this._width, this._height);
|
|
276
|
+
const timeLoc = effect.loc(gl, "uTime");
|
|
277
|
+
if (timeLoc) gl.uniform1f(timeLoc, ctx.time);
|
|
278
|
+
|
|
279
|
+
if (effect.setUniforms) {
|
|
280
|
+
effect.setUniforms(gl, (n) => effect.loc(gl, n), ctx);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._quad);
|
|
284
|
+
const posLoc = gl.getAttribLocation(effect.program, "aPos");
|
|
285
|
+
gl.enableVertexAttribArray(posLoc);
|
|
286
|
+
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
|
|
287
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
288
|
+
RenderStats.drawCalls++;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* @method dispose
|
|
293
|
+
* @description Frees the render targets owned by this processor.
|
|
294
|
+
*/
|
|
295
|
+
dispose() {
|
|
296
|
+
this._rtA.dispose();
|
|
297
|
+
this._rtB.dispose();
|
|
298
|
+
for (const t of this._temps) t.dispose();
|
|
299
|
+
this._temps.length = 0;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export default PostProcessor;
|
|
304
|
+
export { PostEffect };
|