littlejsengine 1.24.3 → 1.26.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/CHANGELOG.md +123 -0
- package/EDITOR.md +2 -2
- package/FAQ.md +55 -11
- package/README.md +6 -2
- package/REFERENCE.md +203 -57
- package/dist/littlejs.d.ts +933 -313
- package/dist/littlejs.esm.js +5143 -2419
- package/dist/littlejs.esm.min.js +38 -24
- package/dist/littlejs.js +5132 -2416
- package/dist/littlejs.min.js +37 -23
- package/dist/littlejs.release.js +4905 -2232
- package/package.json +4 -4
- package/plugins/box2d.js +141 -56
- package/plugins/drawUtilities.js +374 -380
- package/plugins/gltf.js +383 -60
- package/plugins/lightSystem.js +412 -30
- package/plugins/math3d.js +1015 -1014
- package/plugins/medalSystem.js +19 -7
- package/plugins/newgrounds.js +5 -6
- package/plugins/parallax.js +1 -0
- package/plugins/particleEffects.js +47 -14
- package/plugins/pathFinder.js +60 -38
- package/plugins/pluginExport.js +11 -0
- package/plugins/postProcess.js +200 -67
- package/plugins/render3d.js +493 -2391
- package/plugins/render3dDebug.js +5 -8
- package/plugins/render3dEditor.js +84 -9
- package/plugins/render3dExtras.js +122 -33
- package/plugins/render3dLevel.js +67 -8
- package/plugins/render3dMesh.js +1158 -0
- package/plugins/render3dObject.js +1451 -0
- package/plugins/render3dVoxels.js +15 -3
- package/plugins/textureSheet.js +9 -8
- package/plugins/threejs.js +1 -1
- package/plugins/tweenSystem.js +117 -87
- package/plugins/uiSystem.js +62 -28
- package/plugins/wavedash.js +229 -0
- package/src/engine.js +192 -40
- package/src/engineAudio.js +47 -27
- package/src/engineBuild.mjs +6 -1
- package/src/engineDebug.js +23 -162
- package/src/engineDraw.js +148 -43
- package/src/engineEditor.js +153 -40
- package/src/engineExport.js +0 -3
- package/src/engineInput.js +31 -25
- package/src/engineMath.js +38 -20
- package/src/engineObject.js +68 -50
- package/src/engineParticles.js +66 -18
- package/src/engineRelease.js +18 -15
- package/src/engineSettings.js +48 -9
- package/src/engineTileLayer.js +44 -18
- package/src/engineUtilities.js +56 -11
- package/src/engineWebGL.js +68 -22
package/plugins/postProcess.js
CHANGED
|
@@ -49,6 +49,18 @@ class PostProcessPlugin
|
|
|
49
49
|
/** @property {string} - The shadertoy style mainImage code it shades with, see setShaderCode */
|
|
50
50
|
this.shaderCode = shaderCode || postProcessEffects(); // no code passes the frame through
|
|
51
51
|
|
|
52
|
+
/** @property {Object<string, number|Array<number>>} - The game's own values for the shader, a uniform each
|
|
53
|
+
* by its name, a number a float and a list of 2 to 4 numbers a vector, set every frame as they are; an
|
|
54
|
+
* effect setting can be one of these names, so it changes every frame without making the shader again, all
|
|
55
|
+
* but glow's size, which sets how many samples it takes; adding or removing a name makes the shader again,
|
|
56
|
+
* and the shader is first made at the first render, so values set right after the plugin are in it; a name
|
|
57
|
+
* is a GLSL name not starting with an underscore, which the effects keep for their own, nor i and a capital
|
|
58
|
+
* or gl_, and not c, uv or p, the names mainImage works on
|
|
59
|
+
* @type {Object<string, number|Array<number>>} */
|
|
60
|
+
this.values = {};
|
|
61
|
+
// the names of the values the shader was made with
|
|
62
|
+
this.valueNames = '';
|
|
63
|
+
|
|
52
64
|
/** @property {WebGLProgram|undefined} - Shader for post processing
|
|
53
65
|
* @type {WebGLProgram|undefined} */
|
|
54
66
|
this.shader = undefined;
|
|
@@ -62,18 +74,14 @@ class PostProcessPlugin
|
|
|
62
74
|
* @type {WebGLVertexArrayObject|undefined} */
|
|
63
75
|
this.vao = undefined;
|
|
64
76
|
|
|
65
|
-
//
|
|
66
|
-
|
|
77
|
+
// the shader is made at the first render, so values the game sets after this, which its code may name, are
|
|
78
|
+
// declared in it
|
|
79
|
+
!headlessMode && !glEnable && console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
67
80
|
engineAddPlugin(undefined, postProcessRender, postProcessContextLost, postProcessContextRestored);
|
|
68
81
|
|
|
69
82
|
function initPostProcess()
|
|
70
83
|
{
|
|
71
|
-
if (headlessMode) return;
|
|
72
|
-
if (!glEnable)
|
|
73
|
-
{
|
|
74
|
-
console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
84
|
+
if (headlessMode || !glEnable) return;
|
|
77
85
|
|
|
78
86
|
// create resources, the feedback starting black, as if the frame before the first were empty
|
|
79
87
|
if (feedbackTexture)
|
|
@@ -93,8 +101,9 @@ class PostProcessPlugin
|
|
|
93
101
|
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
94
102
|
'}' // end of shader
|
|
95
103
|
,
|
|
96
|
-
postProcessFragmentSource(postProcess.shaderCode)
|
|
104
|
+
postProcessFragmentSource(postProcess.shaderCode, postProcess.values)
|
|
97
105
|
);
|
|
106
|
+
postProcess.valueNames = postProcessValueKey(postProcess.values);
|
|
98
107
|
|
|
99
108
|
// setup VAO for post processing
|
|
100
109
|
postProcess.vao = glContext.createVertexArray();
|
|
@@ -130,13 +139,17 @@ class PostProcessPlugin
|
|
|
130
139
|
// clear out the buffer, before anything here binds its own
|
|
131
140
|
glFlush();
|
|
132
141
|
|
|
133
|
-
// made now if WebGL was off when the plugin was made, when a lost context came back,
|
|
134
|
-
//
|
|
142
|
+
// made now if WebGL was off when the plugin was made, when a lost context came back, when setShaderCode
|
|
143
|
+
// gave it new code, or when the game added or took away a value, which the shader declares
|
|
144
|
+
if (postProcess.shader && postProcessValueKey(postProcess.values) !== postProcess.valueNames)
|
|
145
|
+
postProcess.setShaderCode(postProcess.shaderCode);
|
|
135
146
|
if (!postProcess.shader)
|
|
136
147
|
{
|
|
137
148
|
if (glContext.isContextLost()) return;
|
|
138
149
|
initPostProcess();
|
|
139
150
|
}
|
|
151
|
+
if (glFailedPrograms.has(postProcess.shader))
|
|
152
|
+
return; // a shader that did not build in a release build, the frame shows as it is
|
|
140
153
|
|
|
141
154
|
// ensure we render to the default framebuffer (in case any earlier
|
|
142
155
|
// caller this frame left a render target bound)
|
|
@@ -194,9 +207,10 @@ class PostProcessPlugin
|
|
|
194
207
|
glContext.activeTexture(glContext.TEXTURE2);
|
|
195
208
|
glContext.bindTexture(glContext.TEXTURE_2D, depth?.cameraDepthTexture || null);
|
|
196
209
|
glContext.activeTexture(glContext.TEXTURE0);
|
|
210
|
+
// all 0 without one, which an effect reads as no depth
|
|
197
211
|
const camera = depth?.camera;
|
|
198
|
-
|
|
199
|
-
camera.far == Infinity ? 0 : camera.far, camera
|
|
212
|
+
glContext.uniform3f(glUniformLocation(postProcess.shader, 'iDepthRange'), camera?.near || 0,
|
|
213
|
+
!camera || camera.far == Infinity ? 0 : camera.far, camera?.orthographic ? 1 : 0);
|
|
200
214
|
|
|
201
215
|
// set uniforms and draw
|
|
202
216
|
const uniformLocation = (name)=>glUniformLocation(postProcess.shader, name);
|
|
@@ -205,6 +219,14 @@ class PostProcessPlugin
|
|
|
205
219
|
glContext.uniform1i(uniformLocation('iChannel2'), 2);
|
|
206
220
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
207
221
|
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
222
|
+
for (const name in postProcess.values)
|
|
223
|
+
{
|
|
224
|
+
const value = postProcess.values[name], location = uniformLocation(name);
|
|
225
|
+
if (isArray(value))
|
|
226
|
+
[, , glContext.uniform2fv, glContext.uniform3fv, glContext.uniform4fv][value.length]?.call(glContext, location, value);
|
|
227
|
+
else
|
|
228
|
+
glContext.uniform1f(location, value);
|
|
229
|
+
}
|
|
208
230
|
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
|
|
209
231
|
|
|
210
232
|
if (feedbackTexture)
|
|
@@ -254,37 +276,56 @@ class PostProcessPlugin
|
|
|
254
276
|
|
|
255
277
|
///////////////////////////////////////////////////////////////////////////////
|
|
256
278
|
|
|
279
|
+
// what the shader declares of the game's values, each name and its length, so a value that changes either makes the
|
|
280
|
+
// shader again
|
|
281
|
+
const postProcessValueKey = (values)=> Object.entries(values).map(([name, v])=> name + ':' + (isArray(v) ? v.length : 1)).join();
|
|
282
|
+
|
|
257
283
|
// the fragment shader of the post process pass around its mainImage snippet: the frame on iChannel0, the last output
|
|
258
284
|
// on iChannel1 with a feedback texture, and the 3D depth on iChannel2 when render3D.depthTexture is on, read with
|
|
259
|
-
// sceneDepth(uv), the distance from the camera along its view in world units, uv 0 to 1 across the screen
|
|
260
|
-
|
|
285
|
+
// sceneDepth(uv), the distance from the camera along its view in world units, uv 0 to 1 across the screen; and a
|
|
286
|
+
// uniform for each of the game's values, a float or a vector of the list's length
|
|
287
|
+
function postProcessFragmentSource(shaderCode, values={})
|
|
261
288
|
{
|
|
289
|
+
let declared = '';
|
|
290
|
+
for (const name in values)
|
|
291
|
+
{
|
|
292
|
+
const value = values[name], length = isArray(value) ? value.length : 1;
|
|
293
|
+
ASSERT(postProcessNameCheck(name), 'a postProcess value needs a name GLSL takes that the shader does not use', name);
|
|
294
|
+
ASSERT(!isArray(value) || length >= 2 && length <= 4, 'a postProcess value is a number or 2 to 4 of them', name);
|
|
295
|
+
declared += `uniform ${length > 1 ? 'vec' + length : 'float'} ${name};`;
|
|
296
|
+
}
|
|
262
297
|
return '#version 300 es\n' + // specify GLSL ES version
|
|
263
298
|
'precision highp float;'+ // use highp for accuracy
|
|
264
299
|
'uniform sampler2D iChannel0;'+ // input texture
|
|
265
300
|
'uniform sampler2D iChannel1;'+ // the previous frame's output, when feedbackTexture is set
|
|
266
|
-
'uniform sampler2D iChannel2;'+
|
|
301
|
+
'uniform highp sampler2D iChannel2;'+ // the 3D depth, when render3D.depthTexture is on, highp or it is 11 bits
|
|
267
302
|
'uniform vec3 iResolution;'+ // size of output texture
|
|
268
303
|
'uniform float iTime;'+ // time
|
|
269
304
|
'uniform vec3 iDepthRange;'+ // the camera's near, its far or 0 for none, and 1 when orthographic
|
|
305
|
+
declared + // the game's own values
|
|
270
306
|
'out vec4 c;'+ // out color
|
|
271
307
|
// the depth texture's value back to a distance, as the camera's projection put it there
|
|
272
308
|
'float sceneDepth(vec2 uv){'+
|
|
273
309
|
'float d=texture(iChannel2,uv).r*2.-1.,n=iDepthRange.x,f=iDepthRange.y;'+
|
|
274
|
-
'return iDepthRange.z>0.?(d*(f-n)+f+n)/2.:f>0.?2.*n*f/(f+n-d*(f-n)):2.*n/(1.-d);}'+
|
|
275
|
-
|
|
310
|
+
'return iDepthRange.z>0.?(d*(f-n)+f+n)/2.:f>0.?2.*n*f/(f+n-d*(f-n)):2.*n/max(1.-d,1e-7);}'+
|
|
311
|
+
// whether there is depth to read: a range of all 0 is none, an orthographic near plane may be behind
|
|
312
|
+
'\n#define LJS_HAS_DEPTH (iDepthRange != vec3(0))\n'+ // a define needs a line of its own
|
|
313
|
+
// main first, the code's mainImage declared for it, so a define in the code can not reach main
|
|
314
|
+
'void mainImage(out vec4,vec2);'+
|
|
276
315
|
'void main(){'+ // shader entry point
|
|
277
316
|
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
278
317
|
'c.a=1.;'+ // always use full alpha
|
|
279
|
-
'}'
|
|
318
|
+
'}'+ // end of shader
|
|
319
|
+
'\n' + shaderCode + '\n'; // insert custom shader code
|
|
280
320
|
}
|
|
281
321
|
|
|
282
322
|
/**
|
|
283
323
|
* Shader code for a bloom effect, the bright parts of the image blurred back over it
|
|
284
324
|
* - Pass it to PostProcessPlugin, or edit the string to build an effect on top of it
|
|
285
|
-
* @param {number} [threshold] - Brightness where the glow starts, 0 is everything and 1 is only pure white
|
|
286
|
-
* @param {number} [strength] - How much glow to add
|
|
287
|
-
* @param {number} [size] - How far the glow spreads in pixels, which also sets how many samples it takes
|
|
325
|
+
* @param {number|string} [threshold] - Brightness where the glow starts, 0 is everything and 1 is only pure white
|
|
326
|
+
* @param {number|string} [strength] - How much glow to add
|
|
327
|
+
* @param {number} [size] - How far the glow spreads in pixels, which also sets how many samples it takes, so a
|
|
328
|
+
* number and not a value's name
|
|
288
329
|
* @return {string}
|
|
289
330
|
* @memberof PostProcess
|
|
290
331
|
*/
|
|
@@ -295,8 +336,8 @@ function postProcessBloomShader(threshold=.6, strength=1, size=6)
|
|
|
295
336
|
|
|
296
337
|
/**
|
|
297
338
|
* Set up post processing with a bloom effect, so bright colors and lights glow
|
|
298
|
-
* @param {number} [threshold] - Brightness where the glow starts, 0 is everything and 1 is only pure white
|
|
299
|
-
* @param {number} [strength] - How much glow to add
|
|
339
|
+
* @param {number|string} [threshold] - Brightness where the glow starts, 0 is everything and 1 is only pure white
|
|
340
|
+
* @param {number|string} [strength] - How much glow to add
|
|
300
341
|
* @param {number} [size] - How far the glow spreads in pixels
|
|
301
342
|
* @param {boolean} [includeMainCanvas] - Glow the 2D canvas too, off by default so HUD text stays crisp
|
|
302
343
|
* (a HUD drawn with WebGL in gameRenderPost glows either way, draw it with useWebGL=false)
|
|
@@ -314,7 +355,32 @@ function postProcessBloom(threshold=.6, strength=1, size=6, includeMainCanvas=fa
|
|
|
314
355
|
// its pixel; one that bends uv or samples the frame, the curve, chromatic and glow, goes before the ones that shade
|
|
315
356
|
|
|
316
357
|
// a number as GLSL writes it, a float with a point
|
|
317
|
-
|
|
358
|
+
// an effect setting: a number written into the code, or the name of a postProcess value, read from it each frame
|
|
359
|
+
const postProcessNumber = (n)=> typeof n === 'string' ?
|
|
360
|
+
(ASSERT(postProcessNameCheck(n), 'an effect setting is a number or the name of a postProcess value', n), n) :
|
|
361
|
+
(ASSERT(isNumber(n) && isFinite(n), 'an effect setting is a finite number or the name of a postProcess value', n),
|
|
362
|
+
n && abs(n) < .01 ? String(n) : n.toFixed(4)); // a small one keeps its digits, as 1.5e-7 or 0.00015
|
|
363
|
+
|
|
364
|
+
// a GLSL name for a value or a setting, which no part of the shader uses already: not the engine's own, i and a
|
|
365
|
+
// capital, nor those of mainImage or an effect's own locals, which start with an underscore, nor a GLSL word
|
|
366
|
+
const postProcessGLSLWords = new Set(('c uv p sceneDepth mainImage main ' +
|
|
367
|
+
// keywords and reserved words
|
|
368
|
+
'attribute const uniform varying layout centroid flat smooth noperspective patch sample break continue do for ' +
|
|
369
|
+
'while switch case default if else subroutine in out inout float double int void bool true false invariant ' +
|
|
370
|
+
'precise discard return lowp mediump highp precision struct common partition active asm class union enum ' +
|
|
371
|
+
'typedef template this resource goto inline noinline public static extern external interface long short half ' +
|
|
372
|
+
'fixed unsigned superp input output filter sizeof cast namespace using coherent volatile restrict readonly ' +
|
|
373
|
+
'writeonly atomic_uint uint mat2 mat3 mat4 vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 uvec2 uvec3 uvec4 ' +
|
|
374
|
+
// built in functions
|
|
375
|
+
'radians degrees sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh pow exp log exp2 log2 sqrt ' +
|
|
376
|
+
'inversesqrt abs sign floor trunc round roundEven ceil fract mod modf min max clamp mix step smoothstep isnan ' +
|
|
377
|
+
'isinf length distance dot cross normalize faceforward reflect refract matrixCompMult outerProduct transpose ' +
|
|
378
|
+
'determinant inverse lessThan lessThanEqual greaterThan greaterThanEqual equal notEqual any all not textureSize ' +
|
|
379
|
+
'texture textureProj textureLod textureOffset texelFetch texelFetchOffset textureProjOffset textureLodOffset ' +
|
|
380
|
+
'textureProjLod textureProjLodOffset textureGrad textureGradOffset textureProjGrad textureProjGradOffset dFdx ' +
|
|
381
|
+
'dFdy fwidth').split(' '));
|
|
382
|
+
const postProcessNameCheck = (name)=> /^[A-Za-z][A-Za-z0-9_]*$/.test(name) && !name.includes('__') &&
|
|
383
|
+
!/^(i[A-Z]|gl_|webgl_|GL_|mat[2-4]|[dfh]vec[2-4]|[iu]?(sampler|image)([123]D|Cube|Buffer|External))/.test(name) && !postProcessGLSLWords.has(name);
|
|
318
384
|
|
|
319
385
|
/**
|
|
320
386
|
* Join effects into one post process shader, in the order given, for PostProcessPlugin or setShaderCode
|
|
@@ -339,15 +405,16 @@ function postProcessEffects(...effects)
|
|
|
339
405
|
|
|
340
406
|
/**
|
|
341
407
|
* Bright parts glow, the bloom as an effect to join with others; postProcessBloom sets up bloom on its own
|
|
342
|
-
* @param {number} [threshold] - Brightness where the glow starts, 0 is everything and 1 is only pure white
|
|
343
|
-
* @param {number} [strength] - How much glow to add
|
|
344
|
-
* @param {number} [size] - How far the glow spreads in pixels, which also sets how many samples it takes
|
|
408
|
+
* @param {number|string} [threshold] - Brightness where the glow starts, 0 is everything and 1 is only pure white
|
|
409
|
+
* @param {number|string} [strength] - How much glow to add
|
|
410
|
+
* @param {number} [size] - How far the glow spreads in pixels, which also sets how many samples it takes, so a
|
|
411
|
+
* number and not a value's name
|
|
345
412
|
* @return {string}
|
|
346
413
|
* @memberof PostProcess
|
|
347
414
|
*/
|
|
348
415
|
function postProcessGlow(threshold=.6, strength=1, size=6)
|
|
349
416
|
{
|
|
350
|
-
ASSERT(isNumber(
|
|
417
|
+
ASSERT(isNumber(size), 'glow size is a number, it sets how many samples the glow takes');
|
|
351
418
|
ASSERT(size > 0, 'glow size must be above zero');
|
|
352
419
|
ASSERT(size <= 32, 'a glow this wide takes a sample every few pixels of every ring, which is hundreds of samples a pixel', size);
|
|
353
420
|
|
|
@@ -365,21 +432,22 @@ function postProcessGlow(threshold=.6, strength=1, size=6)
|
|
|
365
432
|
const count = max(5 + 2 * j, round(2 * radius)) | 1;
|
|
366
433
|
taps += count;
|
|
367
434
|
code += `
|
|
368
|
-
for (int
|
|
435
|
+
for (int _k = 0; _k < ${count}; ++_k)
|
|
369
436
|
{
|
|
370
|
-
float
|
|
371
|
-
|
|
437
|
+
float _a = float(_k) * ${(2 * PI / count).toFixed(7)}${j ? ' + ' + (j * 2.3999632).toFixed(7) : ''};
|
|
438
|
+
_glow += max(vec3(0), texture(iChannel0, uv + vec2(cos(_a), sin(_a)) * ${radius.toFixed(4)} / iResolution.xy).rgb - ${postProcessNumber(threshold)});
|
|
372
439
|
}`;
|
|
373
440
|
}
|
|
374
441
|
return ` // glow
|
|
375
|
-
vec3
|
|
376
|
-
c.rgb +=
|
|
442
|
+
vec3 _glow = vec3(0);${code}
|
|
443
|
+
c.rgb += _glow * ${typeof strength === 'string' ? postProcessNumber(strength) + ' / ' + taps + '.' :
|
|
444
|
+
(strength / taps).toFixed(6)};`; // a number as before, a value's name divided in the shader
|
|
377
445
|
}
|
|
378
446
|
|
|
379
447
|
/**
|
|
380
448
|
* Scan lines across the screen, like an old TV
|
|
381
|
-
* @param {number} [strength] - How dark the lines are, and how bright between them
|
|
382
|
-
* @param {number} [spacing] - Pixels from one line to the next
|
|
449
|
+
* @param {number|string} [strength] - How dark the lines are, and how bright between them
|
|
450
|
+
* @param {number|string} [spacing] - Pixels from one line to the next
|
|
383
451
|
* @return {string}
|
|
384
452
|
* @memberof PostProcess
|
|
385
453
|
*/
|
|
@@ -391,66 +459,130 @@ function postProcessScanlines(strength=.5, spacing=6)
|
|
|
391
459
|
|
|
392
460
|
/**
|
|
393
461
|
* Static noise over the picture, changing every frame
|
|
394
|
-
* @param {number} [strength] - How bright the static is
|
|
395
|
-
* @param {number} [size] - Size of a speck in pixels
|
|
462
|
+
* @param {number|string} [strength] - How bright the static is
|
|
463
|
+
* @param {number|string} [size] - Size of a speck in pixels
|
|
396
464
|
* @return {string}
|
|
397
465
|
* @memberof PostProcess
|
|
398
466
|
*/
|
|
399
467
|
function postProcessNoise(strength=.1, size=2)
|
|
400
468
|
{
|
|
401
469
|
return ` // noise
|
|
402
|
-
vec2
|
|
403
|
-
c.rgb += ${postProcessNumber(strength)} * fract(1. + sin(51. *
|
|
470
|
+
vec2 _q = fract((floor(p / ${postProcessNumber(size)}) + mod(iTime * 500., 1e3)) * .3197);
|
|
471
|
+
c.rgb += ${postProcessNumber(strength)} * fract(1. + sin(51. * _q.x + 73. * _q.y) * 13753.3);`;
|
|
404
472
|
}
|
|
405
473
|
|
|
406
474
|
/**
|
|
407
475
|
* Darken toward the edges and corners
|
|
408
|
-
* @param {number} [strength] - How dark the corners get, 1 is black
|
|
409
|
-
* @param {number} [falloff] - How far in it reaches, low darkens most of the screen, high only the corners
|
|
476
|
+
* @param {number|string} [strength] - How dark the corners get, 1 is black
|
|
477
|
+
* @param {number|string} [falloff] - How far in it reaches, low darkens most of the screen, high only the corners
|
|
410
478
|
* @return {string}
|
|
411
479
|
* @memberof PostProcess
|
|
412
480
|
*/
|
|
413
481
|
function postProcessVignette(strength=1, falloff=3)
|
|
414
482
|
{
|
|
415
483
|
return ` // vignette
|
|
416
|
-
vec2
|
|
417
|
-
c.rgb *= 1. - ${postProcessNumber(strength)} * min(1., pow(dot(
|
|
484
|
+
vec2 _d = uv * 2. - 1.;
|
|
485
|
+
c.rgb *= 1. - ${postProcessNumber(strength)} * min(1., pow(dot(_d, _d) / 2., ${postProcessNumber(falloff)}));`;
|
|
418
486
|
}
|
|
419
487
|
|
|
420
488
|
/**
|
|
421
489
|
* Bend the picture like the bulged glass of an old TV, black past the corners; put it first
|
|
422
|
-
* @param {number} [strength] - How much it bends
|
|
490
|
+
* @param {number|string} [strength] - How much it bends
|
|
423
491
|
* @return {string}
|
|
424
492
|
* @memberof PostProcess
|
|
425
493
|
*/
|
|
426
494
|
function postProcessCurve(strength=.1)
|
|
427
495
|
{
|
|
428
496
|
return ` // curve
|
|
429
|
-
vec2
|
|
430
|
-
|
|
431
|
-
uv =
|
|
432
|
-
c = all(lessThan(abs(
|
|
497
|
+
vec2 _d = uv * 2. - 1.;
|
|
498
|
+
_d *= 1. + ${postProcessNumber(strength)} * dot(_d, _d);
|
|
499
|
+
uv = _d * .5 + .5;
|
|
500
|
+
c = all(lessThan(abs(_d), vec2(1))) ? texture(iChannel0, uv) : vec4(0, 0, 0, 1);`;
|
|
433
501
|
}
|
|
434
502
|
|
|
435
503
|
/**
|
|
436
504
|
* Split red and blue apart toward the edges, like a cheap lens; put it before what shades the picture
|
|
437
|
-
* @param {number} [strength] - How far apart at the edge, as a part of the screen
|
|
505
|
+
* @param {number|string} [strength] - How far apart at the edge, as a part of the screen
|
|
438
506
|
* @return {string}
|
|
439
507
|
* @memberof PostProcess
|
|
440
508
|
*/
|
|
441
509
|
function postProcessChromatic(strength=.005)
|
|
442
510
|
{
|
|
443
511
|
return ` // chromatic
|
|
444
|
-
vec2
|
|
445
|
-
c.r = texture(iChannel0, uv +
|
|
446
|
-
c.b = texture(iChannel0, uv -
|
|
512
|
+
vec2 _d = (uv - .5) * ${postProcessNumber(strength)} * 2.;
|
|
513
|
+
c.r = texture(iChannel0, uv + _d).r;
|
|
514
|
+
c.b = texture(iChannel0, uv - _d).b;`;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// a blur's widest is 32 pixels, past which its 24 taps sit far enough apart to show; a value's is the game's to keep
|
|
518
|
+
const postProcessBlurCheck = (blur)=>
|
|
519
|
+
ASSERT(typeof blur === 'string' || blur <= 32, 'a blur of more than 32 pixels shows its taps as copies', blur);
|
|
520
|
+
|
|
521
|
+
// a blur over a disc of radius _r pixels around uv, of 24 taps spread evenly by the golden angle; weight, when given,
|
|
522
|
+
// is GLSL for how much a tap at _puv, _s of the radius out, counts, from 0 to 1
|
|
523
|
+
function postProcessDiscBlur(weight)
|
|
524
|
+
{
|
|
525
|
+
return `
|
|
526
|
+
vec3 _sum = vec3(0);
|
|
527
|
+
float _total = 0.;
|
|
528
|
+
for (int _k = 0; _k < 24; ++_k)
|
|
529
|
+
{
|
|
530
|
+
float _s = sqrt((float(_k) + .5) / 24.), _a = float(_k) * 2.39996;
|
|
531
|
+
vec2 _puv = uv + vec2(cos(_a), sin(_a)) * _s * _r / iResolution.xy;
|
|
532
|
+
float _w = ${weight || '1.'};
|
|
533
|
+
_sum += texture(iChannel0, _puv).rgb * _w;
|
|
534
|
+
_total += _w;
|
|
535
|
+
}
|
|
536
|
+
c.rgb = _total > 0. ? _sum / _total : c.rgb;`;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Keep a band across the screen sharp and blur the picture above and below it, as a tilt shift lens does, which
|
|
541
|
+
* makes a scene look like a small model; it reads only the screen, so it works in 2D and 3D; put it first
|
|
542
|
+
* @param {number|string} [focus] - Height of the middle of the sharp band, 0 the bottom of the screen and 1 the top
|
|
543
|
+
* @param {number|string} [size] - Height of the sharp band, as a part of the screen
|
|
544
|
+
* @param {number|string} [blur] - Widest blur in pixels, reached half the screen past the band, at most 32
|
|
545
|
+
* @return {string}
|
|
546
|
+
* @memberof PostProcess
|
|
547
|
+
* @example
|
|
548
|
+
* new PostProcessPlugin(postProcessEffects(postProcessTiltShift(.4, .2, 10), postProcessVignette(.5)));
|
|
549
|
+
*/
|
|
550
|
+
function postProcessTiltShift(focus=.5, size=.25, blur=8)
|
|
551
|
+
{
|
|
552
|
+
postProcessBlurCheck(blur);
|
|
553
|
+
const n = postProcessNumber;
|
|
554
|
+
return ` // tilt shift
|
|
555
|
+
float _r = ${n(blur)} * smoothstep(0., .5, abs(uv.y - ${n(focus)}) - ${n(size)} * .5);${postProcessDiscBlur()}`;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Keep what is a distance from the camera sharp and blur what is nearer or farther, as a camera lens does; needs
|
|
560
|
+
* render3D.depthTexture on, and blurs 3D only, 2D draws having no depth; put it first
|
|
561
|
+
* - What is in focus stays sharp at its edges: a blur in front of or behind it leaves out what is in focus
|
|
562
|
+
* @param {number|string} [focus] - Distance from the camera, along its view, that is sharpest, in world units
|
|
563
|
+
* @param {number|string} [range] - How deep the sharp part is; the blur grows over as far again past it
|
|
564
|
+
* @param {number|string} [blur] - Widest blur in pixels, at most 32
|
|
565
|
+
* @return {string}
|
|
566
|
+
* @memberof PostProcess
|
|
567
|
+
* @example
|
|
568
|
+
* render3D.depthTexture = true;
|
|
569
|
+
* new PostProcessPlugin(postProcessEffects(postProcessDepthOfField(10, 4, 8)));
|
|
570
|
+
*/
|
|
571
|
+
function postProcessDepthOfField(focus=10, range=4, blur=8)
|
|
572
|
+
{
|
|
573
|
+
postProcessBlurCheck(blur);
|
|
574
|
+
const n = postProcessNumber;
|
|
575
|
+
const amount = (depth)=> `${n(blur)} * smoothstep(0., ${n(range)}, abs(${depth} - ${n(focus)}) - ${n(range)} * .5)`;
|
|
576
|
+
// a tap counts as far as its own blur reaches back to here, so a sharp thing in front is not smeared over
|
|
577
|
+
return ` // depth of field, none where there is no depth, as with render3D.depthTexture off
|
|
578
|
+
float _r = LJS_HAS_DEPTH ? ${amount('sceneDepth(uv)')} : 0.;${postProcessDiscBlur(`clamp(${amount('sceneDepth(_puv)')} - _s * _r + 1., 0., 1.)`)}`;
|
|
447
579
|
}
|
|
448
580
|
|
|
449
581
|
/**
|
|
450
582
|
* Draw lines where the 3D depth jumps, around objects and along their creases; needs render3D.depthTexture on
|
|
451
583
|
* @param {Color} [color] - The lines' color, its alpha how strong they are
|
|
452
|
-
* @param {number} [thickness] - How wide the lines are in pixels
|
|
453
|
-
* @param {number} [threshold] - How big a jump makes a line, as a part of the distance, lower draws more
|
|
584
|
+
* @param {number|string} [thickness] - How wide the lines are in pixels
|
|
585
|
+
* @param {number|string} [threshold] - How big a jump makes a line, as a part of the distance, lower draws more
|
|
454
586
|
* @return {string}
|
|
455
587
|
* @memberof PostProcess
|
|
456
588
|
*/
|
|
@@ -459,24 +591,25 @@ function postProcessOutline(color=BLACK, thickness=1, threshold=.02)
|
|
|
459
591
|
ASSERT(isColor(color), 'outline color must be a Color');
|
|
460
592
|
const n = postProcessNumber;
|
|
461
593
|
return ` // outline
|
|
462
|
-
vec2
|
|
463
|
-
float
|
|
464
|
-
float
|
|
465
|
-
float
|
|
466
|
-
vec4
|
|
467
|
-
|
|
594
|
+
vec2 _o = ${n(thickness)} / iResolution.xy;
|
|
595
|
+
float _d = sceneDepth(uv);
|
|
596
|
+
float _dx = abs(sceneDepth(uv + vec2(_o.x, 0)) + sceneDepth(uv - vec2(_o.x, 0)) - 2. * _d);
|
|
597
|
+
float _dy = abs(sceneDepth(uv + vec2(0, _o.y)) + sceneDepth(uv - vec2(0, _o.y)) - 2. * _d);
|
|
598
|
+
vec4 _line = vec4(${n(color.r)}, ${n(color.g)}, ${n(color.b)}, ${n(color.a)});
|
|
599
|
+
if (LJS_HAS_DEPTH)
|
|
600
|
+
c.rgb = mix(c.rgb, _line.rgb, _line.a * step(${n(threshold)}, max(_dx, _dy) / max(abs(_d), 1e-6)));`;
|
|
468
601
|
}
|
|
469
602
|
|
|
470
603
|
/**
|
|
471
604
|
* The look of an old TV, as one effect to use alone or join with others: static noise, scan lines, a soft glow and
|
|
472
605
|
* a vignette, and a bulged screen when curve is set; any setting at 0 leaves that part out
|
|
473
606
|
* @param {Object} [settings]
|
|
474
|
-
* @param {number} [settings.noise] - Static noise strength
|
|
475
|
-
* @param {number} [settings.scanlines] - Scan line strength
|
|
476
|
-
* @param {number} [settings.scanlineSpacing] - Pixels from one scan line to the next
|
|
477
|
-
* @param {number} [settings.glow] - Soft glow strength
|
|
478
|
-
* @param {number} [settings.vignette] - Vignette strength
|
|
479
|
-
* @param {number} [settings.curve] - How much the screen bulges, 0 by default for flat
|
|
607
|
+
* @param {number|string} [settings.noise] - Static noise strength
|
|
608
|
+
* @param {number|string} [settings.scanlines] - Scan line strength
|
|
609
|
+
* @param {number|string} [settings.scanlineSpacing] - Pixels from one scan line to the next
|
|
610
|
+
* @param {number|string} [settings.glow] - Soft glow strength
|
|
611
|
+
* @param {number|string} [settings.vignette] - Vignette strength
|
|
612
|
+
* @param {number|string} [settings.curve] - How much the screen bulges, 0 by default for flat
|
|
480
613
|
* @return {string}
|
|
481
614
|
* @example
|
|
482
615
|
* new PostProcessPlugin(postProcessEffects(postProcessTV({scanlines: .4, curve: .1})));
|