littlejsengine 1.18.28 → 1.19.3

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.
@@ -1,166 +1,241 @@
1
- /**
2
- * LittleJS Post Processing Plugin
3
- * - Supports shadertoy style post processing shaders
4
- * - call new PostProcessPlugin() to setup post processing
5
- * - can be enabled to pass other canvases through a final shader
6
- * @namespace PostProcess
7
- */
8
-
9
- 'use strict';
10
-
11
- ///////////////////////////////////////////////////////////////////////////////
12
-
13
- /** Global Post Process plugin object
14
- * @type {PostProcessPlugin}
15
- * @memberof PostProcess */
16
- let postProcess;
17
-
18
- /////////////////////////////////////////////////////////////////////////
19
- /**
20
- * Post Process Plugin - Applies a full screen shader to the rendered output
21
- * @memberof PostProcess
22
- */
23
- class PostProcessPlugin
24
- {
25
- /** Create global post processing shader
26
- * @param {string} shaderCode
27
- * @param {boolean} [includeMainCanvas] - combine mainCanvas onto glCanvas
28
- * @param {boolean} [feedbackTexture] - use glCanvas from previous frame as the texture
29
- * @example
30
- * // create the post process plugin object
31
- * new PostProcessPlugin(shaderCode);
32
- */
33
- constructor(shaderCode, includeMainCanvas=false, feedbackTexture=false)
34
- {
35
- ASSERT(!postProcess, 'Post process already initialized');
36
- ASSERT(!(includeMainCanvas && feedbackTexture), 'Post process cannot both include main canvas and use feedback texture');
37
- postProcess = this;
38
-
39
- if (!shaderCode) // default shader pass through
40
- shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
41
-
42
- /** @property {WebGLProgram} - Shader for post processing */
43
- this.shader = undefined;
44
- /** @property {WebGLTexture} - Texture for post processing */
45
- this.texture = undefined;
46
- /** @property {WebGLVertexArrayObject} - Vertex array object */
47
- this.vao = undefined;
48
-
49
- // setup the post processing plugin
50
- initPostProcess();
51
- engineAddPlugin(undefined, postProcessRender, postProcessContextLost, postProcessContextRestored);
52
-
53
- function initPostProcess()
54
- {
55
- if (headlessMode) return;
56
- if (!glEnable)
57
- {
58
- console.warn('PostProcessPlugin: WebGL not enabled!');
59
- return;
60
- }
61
-
62
- // create resources
63
- postProcess.texture = glCreateTexture();
64
- postProcess.shader = glCreateProgram(
65
- '#version 300 es\n' + // specify GLSL ES version
66
- 'precision highp float;'+ // use highp for accuracy
67
- 'in vec2 p;'+ // position
68
- 'void main(){'+ // shader entry point
69
- 'gl_Position=vec4(p+p-1.,1,1);'+ // set position
70
- '}' // end of shader
71
- ,
72
- '#version 300 es\n' + // specify GLSL ES version
73
- 'precision highp float;'+ // use highp for accuracy
74
- 'uniform sampler2D iChannel0;'+ // input texture
75
- 'uniform vec3 iResolution;'+ // size of output texture
76
- 'uniform float iTime;'+ // time
77
- 'out vec4 c;'+ // out color
78
- '\n' + shaderCode + '\n'+ // insert custom shader code
79
- 'void main(){'+ // shader entry point
80
- 'mainImage(c,gl_FragCoord.xy);'+ // call post process function
81
- 'c.a=1.;'+ // always use full alpha
82
- '}' // end of shader
83
- );
84
-
85
- // setup VAO for post processing
86
- postProcess.vao = glContext.createVertexArray();
87
- glContext.bindVertexArray(postProcess.vao);
88
- glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
89
-
90
- // configure vertex attributes
91
- const vertexByteStride = 8;
92
- const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
93
- glContext.enableVertexAttribArray(pLocation);
94
- glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
95
- }
96
- function postProcessContextLost()
97
- {
98
- postProcess.shader = undefined;
99
- postProcess.texture = undefined;
100
- LOG('PostProcessPlugin: WebGL context lost');
101
- }
102
- function postProcessContextRestored()
103
- {
104
- initPostProcess();
105
- LOG('PostProcessPlugin: WebGL context restored');
106
- }
107
- function postProcessRender()
108
- {
109
- if (headlessMode || !glEnable) return;
110
-
111
- // clear out the buffer
112
- glFlush();
113
-
114
- // ensure we render to the default framebuffer (in case any earlier
115
- // caller this frame left a render target bound)
116
- glContext.bindFramebuffer(glContext.FRAMEBUFFER, null);
117
-
118
- // setup shader program to draw a quad
119
- glContext.useProgram(postProcess.shader);
120
- glContext.bindVertexArray(postProcess.vao);
121
- glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, true);
122
- glContext.disable(glContext.BLEND);
123
-
124
- // setup texture
125
- glContext.activeTexture(glContext.TEXTURE0);
126
- glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
127
- if (includeMainCanvas)
128
- {
129
- // copy main canvas to work canvas
130
- workCanvas.width = mainCanvasSize.x;
131
- workCanvas.height = mainCanvasSize.y;
132
- glCopyToContext(workContext);
133
- workContext.drawImage(mainCanvas, 0, 0);
134
- mainCanvas.width |= 0; // setting size clears the main canvas
135
-
136
-
137
- // copy work canvas to texture
138
- glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
139
- }
140
- else if (!feedbackTexture)
141
- {
142
- // copy glCanvas to texture
143
- glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
144
- }
145
-
146
- // set uniforms and draw
147
- const uniformLocation = (name)=>glContext.getUniformLocation(postProcess.shader, name);
148
- glContext.uniform1i(uniformLocation('iChannel0'), 0);
149
- glContext.uniform1f(uniformLocation('iTime'), time);
150
- glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
151
- glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
152
-
153
- if (feedbackTexture)
154
- {
155
- // pass glCanvas back to overlay texture
156
- glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
157
- }
158
-
159
- // restore default so subsequent dynamic texture uploads aren't flipped
160
- glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, false);
161
-
162
- // force it to set instanced mode
163
- glSetInstancedMode(true);
164
- }
165
- }
166
- }
1
+ /**
2
+ * LittleJS Post Processing Plugin
3
+ * - Supports shadertoy style post processing shaders
4
+ * - call new PostProcessPlugin() to setup post processing
5
+ * - can be enabled to pass other canvases through a final shader
6
+ * - iResolution is the canvas backing store, so it grows with canvasPixelRatio
7
+ * like shadertoy does. Effects that use it only for uv (p/iResolution.xy) are
8
+ * unaffected, but ones that set a feature size from it, like scan lines, get
9
+ * finer as the ratio rises. Divide by getCanvasPixelRatio() to pin them.
10
+ * @namespace PostProcess
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ ///////////////////////////////////////////////////////////////////////////////
16
+
17
+ /** Global Post Process plugin object
18
+ * @type {PostProcessPlugin}
19
+ * @memberof PostProcess */
20
+ let postProcess;
21
+
22
+ /////////////////////////////////////////////////////////////////////////
23
+ /**
24
+ * Post Process Plugin - Applies a full screen shader to the rendered output
25
+ * - Create it after any plugin that draws, since plugins render in the order they are made
26
+ * and this one shades what is on the canvas when its turn comes
27
+ * @memberof PostProcess
28
+ */
29
+ class PostProcessPlugin
30
+ {
31
+ /** Create global post processing shader
32
+ * @param {string} shaderCode
33
+ * @param {boolean} [includeMainCanvas] - combine mainCanvas onto glCanvas
34
+ * @param {boolean} [feedbackTexture] - use glCanvas from previous frame as the texture
35
+ * @example
36
+ * // create the post process plugin object
37
+ * new PostProcessPlugin(shaderCode);
38
+ */
39
+ constructor(shaderCode, includeMainCanvas=false, feedbackTexture=false)
40
+ {
41
+ ASSERT(!postProcess, 'Post process already initialized');
42
+ ASSERT(!(includeMainCanvas && feedbackTexture), 'Post process cannot both include main canvas and use feedback texture');
43
+ postProcess = this;
44
+
45
+ if (!shaderCode) // default shader pass through
46
+ shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
47
+
48
+ /** @property {WebGLProgram|undefined} - Shader for post processing
49
+ * @type {WebGLProgram|undefined} */
50
+ this.shader = undefined;
51
+ /** @property {WebGLTexture|undefined} - Texture for post processing
52
+ * @type {WebGLTexture|undefined} */
53
+ this.texture = undefined;
54
+ /** @property {WebGLVertexArrayObject|undefined} - Vertex array object
55
+ * @type {WebGLVertexArrayObject|undefined} */
56
+ this.vao = undefined;
57
+
58
+ // setup the post processing plugin
59
+ initPostProcess();
60
+ engineAddPlugin(undefined, postProcessRender, postProcessContextLost, postProcessContextRestored);
61
+
62
+ function initPostProcess()
63
+ {
64
+ if (headlessMode) return;
65
+ if (!glEnable)
66
+ {
67
+ console.warn('PostProcessPlugin: WebGL not enabled!');
68
+ return;
69
+ }
70
+
71
+ // create resources
72
+ postProcess.texture = glCreateTexture();
73
+ postProcess.shader = glCreateProgram(
74
+ '#version 300 es\n' + // specify GLSL ES version
75
+ 'precision highp float;'+ // use highp for accuracy
76
+ 'in vec2 p;'+ // position
77
+ 'void main(){'+ // shader entry point
78
+ 'gl_Position=vec4(p+p-1.,1,1);'+ // set position
79
+ '}' // end of shader
80
+ ,
81
+ '#version 300 es\n' + // specify GLSL ES version
82
+ 'precision highp float;'+ // use highp for accuracy
83
+ 'uniform sampler2D iChannel0;'+ // input texture
84
+ 'uniform vec3 iResolution;'+ // size of output texture
85
+ 'uniform float iTime;'+ // time
86
+ 'out vec4 c;'+ // out color
87
+ '\n' + shaderCode + '\n'+ // insert custom shader code
88
+ 'void main(){'+ // shader entry point
89
+ 'mainImage(c,gl_FragCoord.xy);'+ // call post process function
90
+ 'c.a=1.;'+ // always use full alpha
91
+ '}' // end of shader
92
+ );
93
+
94
+ // setup VAO for post processing
95
+ postProcess.vao = glContext.createVertexArray();
96
+ glContext.bindVertexArray(postProcess.vao);
97
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
98
+
99
+ // configure vertex attributes
100
+ const vertexByteStride = 8;
101
+ const pLocation = glContext.getAttribLocation(postProcess.shader, 'p');
102
+ glContext.enableVertexAttribArray(pLocation);
103
+ glContext.vertexAttribPointer(pLocation, 2, glContext.FLOAT, false, vertexByteStride, 0);
104
+ }
105
+ function postProcessContextLost()
106
+ {
107
+ postProcess.shader = undefined;
108
+ postProcess.texture = undefined;
109
+ LOG('PostProcessPlugin: WebGL context lost');
110
+ }
111
+ function postProcessContextRestored()
112
+ {
113
+ initPostProcess();
114
+ LOG('PostProcessPlugin: WebGL context restored');
115
+ }
116
+ function postProcessRender()
117
+ {
118
+ if (headlessMode || !glEnable) return;
119
+
120
+ // clear out the buffer
121
+ glFlush();
122
+
123
+ // ensure we render to the default framebuffer (in case any earlier
124
+ // caller this frame left a render target bound)
125
+ glContext.bindFramebuffer(glContext.FRAMEBUFFER, null);
126
+
127
+ // setup shader program to draw a quad
128
+ glContext.useProgram(postProcess.shader);
129
+ glContext.bindVertexArray(postProcess.vao);
130
+ glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, true);
131
+ glContext.disable(glContext.BLEND);
132
+
133
+ // setup texture
134
+ glContext.activeTexture(glContext.TEXTURE0);
135
+ glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
136
+ if (includeMainCanvas)
137
+ {
138
+ // copy main canvas to work canvas at the backing store size,
139
+ // mainCanvasSize is css pixels so it would lose resolution
140
+ workCanvas.width = mainCanvas.width;
141
+ workCanvas.height = mainCanvas.height;
142
+ glCopyToContext(workContext);
143
+ workContext.drawImage(mainCanvas, 0, 0);
144
+ mainCanvas.width |= 0; // setting size clears the main canvas
145
+
146
+ // that also reset the transform, restore it so anything drawn
147
+ // later this frame is still in css pixels
148
+ const dpr = getCanvasPixelRatio();
149
+ mainContext.setTransform(dpr, 0, 0, dpr, 0, 0);
150
+
151
+ // copy work canvas to texture
152
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, workCanvas);
153
+ }
154
+ else if (!feedbackTexture)
155
+ {
156
+ // copy glCanvas to texture
157
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
158
+ }
159
+
160
+ // set uniforms and draw
161
+ const uniformLocation = (name)=>glContext.getUniformLocation(postProcess.shader, name);
162
+ glContext.uniform1i(uniformLocation('iChannel0'), 0);
163
+ glContext.uniform1f(uniformLocation('iTime'), time);
164
+ glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
165
+ glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, 4);
166
+
167
+ if (feedbackTexture)
168
+ {
169
+ // pass glCanvas back to overlay texture
170
+ glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, glCanvas);
171
+ }
172
+
173
+ // restore default so subsequent dynamic texture uploads aren't flipped
174
+ glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, false);
175
+
176
+ // force it to set instanced mode
177
+ glSetInstancedMode(true);
178
+ }
179
+ }
180
+ }
181
+
182
+ ///////////////////////////////////////////////////////////////////////////////
183
+
184
+ /**
185
+ * Shader code for a bloom effect, the bright parts of the image blurred back over it
186
+ * - Pass it to PostProcessPlugin, or edit the string to build an effect on top of it
187
+ * @param {number} [threshold] - Brightness where the glow starts, 0 is everything and 1 is only pure white
188
+ * @param {number} [strength] - How much glow to add
189
+ * @param {number} [size] - How far the glow spreads in pixels, which also sets how many samples it takes
190
+ * @return {string}
191
+ * @memberof PostProcess
192
+ */
193
+ function postProcessBloomShader(threshold=.6, strength=1, size=6)
194
+ {
195
+ ASSERT(isNumber(threshold) && isNumber(strength) && isNumber(size), 'bloom settings must be numbers');
196
+ ASSERT(size > 0, 'bloom size must be above zero');
197
+ ASSERT(size <= 32, 'a bloom this wide takes a sample every few pixels of every ring, which is hundreds of samples a pixel', size);
198
+
199
+ // Taps on three rings over a disc of the given size, one every three pixels or so of each ring
200
+ // so there is no gap wide enough to show. The count follows the ring all the way out: hold it
201
+ // still and a wider glow only spreads the same taps further apart, until they show up as the
202
+ // ring of evenly spaced copies a single ring of eight leaves around anything bright.
203
+ // Each ring has its own count, odd and unequal, and its own turn off the last, so the little
204
+ // the taps do miss comes out as fine ripple instead of a shape of its own.
205
+ const rings = 3;
206
+ let code = '', taps = 0;
207
+ for (let j = 0; j < rings; ++j)
208
+ {
209
+ const radius = ((j + .5) / rings) ** .5 * size; // equal area per ring
210
+ const count = max(5 + 2 * j, round(2 * radius)) | 1;
211
+ taps += count;
212
+ code += `
213
+ for (int k = 0; k < ${count}; ++k)
214
+ {
215
+ float a = float(k) * ${(2 * PI / count).toFixed(7)}${j ? ' + ' + (j * 2.3999632).toFixed(7) : ''};
216
+ glow += max(vec3(0), texture(iChannel0, uv + vec2(cos(a), sin(a)) * ${radius.toFixed(4)} / iResolution.xy).rgb - ${threshold.toFixed(4)});
217
+ }`;
218
+ }
219
+ return `
220
+ void mainImage(out vec4 color, vec2 pixel)
221
+ {
222
+ vec2 uv = pixel / iResolution.xy;
223
+ color = texture(iChannel0, uv);
224
+ vec3 glow = vec3(0);${code}
225
+ color.rgb += glow * ${(strength / taps).toFixed(6)};
226
+ }`;
227
+ }
228
+
229
+ /**
230
+ * Set up post processing with a bloom effect, so bright colors and lights glow
231
+ * @param {number} [threshold] - Brightness where the glow starts, 0 is everything and 1 is only pure white
232
+ * @param {number} [strength] - How much glow to add
233
+ * @param {number} [size] - How far the glow spreads in pixels
234
+ * @param {boolean} [includeMainCanvas] - Glow the 2D canvas too, off by default so HUD text stays crisp
235
+ * @return {PostProcessPlugin}
236
+ * @memberof PostProcess
237
+ * @example
238
+ * postProcessBloom(); // in gameInit, after any Render3DPlugin
239
+ */
240
+ function postProcessBloom(threshold=.6, strength=1, size=6, includeMainCanvas=false)
241
+ { return new PostProcessPlugin(postProcessBloomShader(threshold, strength, size), includeMainCanvas); }