@davepagurek/p5.filterrenderer 0.0.12

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.
@@ -0,0 +1,136 @@
1
+ class BlurRenderer extends Renderer {
2
+ constructor(target, options) {
3
+ super(target, options)
4
+ this.focus = (target.height / 2) / tan(PI / 6)
5
+ this.intensity = 0.05
6
+ this.dof = 0
7
+ this.numSamples = 15
8
+ }
9
+
10
+ vert() {
11
+ return BlurRenderer.vert
12
+ }
13
+
14
+ frag() {
15
+ return BlurRenderer.frag
16
+ }
17
+
18
+ focusHere() {
19
+ const matrix = new DOMMatrix(this.target._renderer.uMVMatrix.mat4)
20
+ const center = new DOMPoint(0, 0, 0)
21
+ const world = center.matrixTransform(matrix)
22
+ this.focus = -world.z
23
+ }
24
+
25
+ setDof(dof) {
26
+ this.dof = dof
27
+ }
28
+
29
+ setIntensity(intensity) {
30
+ this.intensity = intensity
31
+ }
32
+
33
+ setSamples(numSamples) {
34
+ this.numSamples = numSamples
35
+ }
36
+
37
+ getUniforms() {
38
+ return {
39
+ uImg: this.fbo.color,
40
+ uDepth: this.fbo.depth,
41
+ uSize: [this.target.width, this.target.height],
42
+ uIntensity: this.intensity,
43
+ uDof: this.dof,
44
+ uNumSamples: this.numSamples,
45
+ uNear: this.target._renderer._curCamera._near,
46
+ uFar: this.target._renderer._curCamera._far,
47
+ uTargetZ: this.focus,
48
+ }
49
+ }
50
+ }
51
+
52
+ p5.prototype.createBlurRenderer = function(options) {
53
+ return new BlurRenderer(this, options)
54
+ }
55
+
56
+ BlurRenderer.vert = `
57
+ precision highp float;
58
+
59
+ attribute vec3 aPosition;
60
+ attribute vec3 aNormal;
61
+ attribute vec2 aTexCoord;
62
+
63
+ uniform mat4 uModelViewMatrix;
64
+ uniform mat4 uProjectionMatrix;
65
+ uniform mat3 uNormalMatrix;
66
+
67
+ varying highp vec2 vVertTexCoord;
68
+
69
+ void main(void) {
70
+ vec4 positionVec4 = vec4(aPosition, 1.0);
71
+ gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
72
+ vVertTexCoord = aTexCoord;
73
+ }
74
+ `
75
+
76
+ BlurRenderer.frag = `
77
+ precision highp float;
78
+ varying highp vec2 vVertTexCoord;
79
+
80
+ uniform sampler2D uImg;
81
+ uniform sampler2D uDepth;
82
+ uniform vec2 uSize;
83
+ uniform float uIntensity;
84
+ uniform float uDof;
85
+ uniform float maxBlur;
86
+ uniform int uNumSamples;
87
+ uniform float uTargetZ;
88
+ uniform float uNear;
89
+ uniform float uFar;
90
+
91
+ #define PI 3.14159265359;
92
+
93
+ const int MAX_NUM_SAMPLES = 50;
94
+
95
+ float rand(vec2 co){
96
+ return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453);
97
+ }
98
+
99
+ float depthToZ(float depth) {
100
+ float depthNormalized = 2.0 * depth - 1.0;
101
+ return 2.0 * uNear * uFar / (uFar + uNear - depthNormalized * (uFar - uNear));
102
+ }
103
+
104
+ float calcBlur(float z, float pixelScale) {
105
+ return clamp(abs(z - uTargetZ) - uDof / 2., 0.0, 0.3*pixelScale);
106
+ }
107
+
108
+ void main() {
109
+ float total = 1.0;
110
+ float origZ = depthToZ(texture2D(uDepth, vVertTexCoord).x);
111
+ vec4 color = texture2D(uImg, vVertTexCoord);
112
+
113
+ if (abs(origZ - uTargetZ) > uDof / 2.) {
114
+ float pixelScale = max(uSize.x, uSize.y);
115
+ float blurAmt = calcBlur(origZ, pixelScale);
116
+ for (int i = 0; i < MAX_NUM_SAMPLES; i++) {
117
+ if (i >= uNumSamples) break;
118
+ float t = (float(i + 1) / float(uNumSamples));
119
+ float angle = (t*12.0) * 2. * PI;
120
+ float radius = 1.0 - (t*t*t); // Sample more on the outer edge
121
+ angle += 5.*rand(gl_FragCoord.xy);
122
+ vec2 offset = (vec2(cos(angle),sin(angle)) * radius * uIntensity * blurAmt)/pixelScale;
123
+ float z = depthToZ(texture2D(uDepth, vVertTexCoord + offset).x);
124
+ float sampleBlur = calcBlur(z, pixelScale);
125
+
126
+ float weight = float((z >= origZ) || (sampleBlur >= blurAmt*radius + 5.));
127
+ vec4 sample = texture2D(uImg, vVertTexCoord + offset);
128
+ color += weight * sample;
129
+ total += weight;
130
+ }
131
+ }
132
+
133
+ color /= total;
134
+ gl_FragColor = color;
135
+ }
136
+ `
@@ -0,0 +1,381 @@
1
+ class ContactShadowRenderer extends Renderer {
2
+ constructor(target, options) {
3
+ super(target, options)
4
+ if (!this.target.webglVersion === WEBGL2) {
5
+ this.target._renderer.GL.getExtension('OES_standard_derivatives')
6
+ }
7
+ this.fbo2 = target.createFramebuffer(options)
8
+ this.blurShader = target.createShader(this.blurVert(), this.blurFrag())
9
+ this.intensity = 0.5
10
+ this.numShadowSamples = 15
11
+ this.numBlurSamples = 20
12
+ this.exponent = 250
13
+ this.bias = 0.1
14
+ this.searchRadius = 100
15
+ this.blurRadius = 50
16
+ }
17
+
18
+ prefix() {
19
+ if (this.target.webglVersion === WEBGL2) {
20
+ return '#version 300 es\n#define IS_WEBGL2\n'
21
+ } else {
22
+ return '#extension GL_OES_standard_derivatives : enable\n'
23
+ }
24
+ }
25
+
26
+ vert() {
27
+ return this.prefix() + ContactShadowRenderer.vert
28
+ }
29
+
30
+ frag() {
31
+ return this.prefix() + ContactShadowRenderer.frag
32
+ }
33
+
34
+ blurVert() {
35
+ return this.vert()
36
+ }
37
+
38
+ blurFrag() {
39
+ return this.prefix() + ContactShadowRenderer.blurFrag
40
+ }
41
+
42
+ setIntensity(intensity) {
43
+ this.intensity = intensity
44
+ }
45
+ setShadowSamples(numSamples) {
46
+ this.numShadowSamples = numSamples
47
+ }
48
+ setBlurSamples(numSamples) {
49
+ this.numBlurSamples = numSamples
50
+ }
51
+ setBlurRadius(r) {
52
+ this.blurRadius = r
53
+ }
54
+ setExponent(exponent) {
55
+ this.exponent = exponent
56
+ }
57
+ setBias(bias) {
58
+ this.bias = bias
59
+ }
60
+ setSearchRadius(radius) {
61
+ this.searchRadius = radius
62
+ }
63
+
64
+ getShadowUniforms() {
65
+ const projInfo = [
66
+ -2 / (this.target.width * this.target._renderer.uPMatrix.mat4[0]),
67
+ -2 / (this.target.height * this.target._renderer.uPMatrix.mat4[5]),
68
+ (1 - this.target._renderer.uPMatrix.mat4[2]) / this.target._renderer.uPMatrix.mat4[0],
69
+ (1 + this.target._renderer.uPMatrix.mat4[6]) / this.target._renderer.uPMatrix.mat4[5]
70
+ ]
71
+
72
+ return {
73
+ uImg: this.fbo.color,
74
+ uDepth: this.fbo.depth,
75
+ uSize: [this.target.width, this.target.height],
76
+ uIntensity: this.intensity,
77
+ uNumSamples: this.numShadowSamples,
78
+ uNear: this.target._renderer._curCamera.cameraNear,
79
+ uFar: this.target._renderer._curCamera.cameraFar,
80
+ uProjInfo: projInfo,
81
+ uExponent: this.exponent,
82
+ uBias: this.bias,
83
+ uSearchRadius: this.searchRadius,
84
+ }
85
+ }
86
+
87
+ getBlurUniforms() {
88
+ return {
89
+ uImg: this.fbo.color,
90
+ uDepth: this.fbo.depth,
91
+ uShadow: this.fbo2.color,
92
+ uSize: [this.target.width, this.target.height],
93
+ uIntensity: this.intensity,
94
+ uNear: this.target._renderer._curCamera.cameraNear,
95
+ uFar: this.target._renderer._curCamera.cameraFar,
96
+ uNumSamples: this.numBlurSamples,
97
+ uBlurRadius: this.blurRadius,
98
+ }
99
+ }
100
+
101
+ draw(cb) {
102
+ const shadowUniforms = this.getShadowUniforms()
103
+ const blurUniforms = this.getBlurUniforms()
104
+
105
+ this.fbo.draw(() => {
106
+ this.target.push()
107
+ cb()
108
+ this.target.pop()
109
+ })
110
+
111
+ this.target.push()
112
+
113
+ this.fbo2.draw(() => {
114
+ this.target.push()
115
+ this.target.clear()
116
+ this.target.noStroke()
117
+ this.target.rectMode(CENTER)
118
+ this.target.shader(this.shader)
119
+ for (const key in shadowUniforms) {
120
+ this.shader.setUniform(key, shadowUniforms[key])
121
+ }
122
+ this.target.rect(0, 0, this.target.width, -this.target.height)
123
+ this.target.pop()
124
+ })
125
+
126
+ this.target.noStroke()
127
+ this.target.rectMode(CENTER)
128
+ this.target.shader(this.blurShader)
129
+ for (const key in blurUniforms) {
130
+ this.blurShader.setUniform(key, blurUniforms[key])
131
+ }
132
+ this.target.rect(0, 0, this.target.width, -this.target.height)
133
+ this.target.pop()
134
+ }
135
+ }
136
+
137
+ p5.prototype.createContactShadowRenderer = function(options) {
138
+ return new ContactShadowRenderer(this, options)
139
+ }
140
+
141
+ ContactShadowRenderer.vert = `
142
+ #ifdef IS_WEBGL2
143
+ in vec3 aPosition;
144
+ in vec3 aNormal;
145
+ in vec2 aTexCoord;
146
+ #else
147
+ attribute vec3 aPosition;
148
+ attribute vec3 aNormal;
149
+ attribute vec2 aTexCoord;
150
+ #endif
151
+
152
+ uniform mat4 uModelViewMatrix;
153
+ uniform mat4 uProjectionMatrix;
154
+ uniform mat3 uNormalMatrix;
155
+
156
+ #ifdef IS_WEBGL2
157
+ out highp vec2 vVertTexCoord;
158
+ #else
159
+ varying highp vec2 vVertTexCoord;
160
+ #endif
161
+
162
+ void main(void) {
163
+ vec4 positionVec4 = vec4(aPosition, 1.0);
164
+ gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
165
+ vVertTexCoord = aTexCoord;
166
+ }
167
+ `
168
+
169
+ ContactShadowRenderer.frag = `
170
+ precision highp float;
171
+ #ifdef IS_WEBGL2
172
+ in highp vec2 vVertTexCoord;
173
+ out highp vec4 outColor;
174
+ #else
175
+ varying highp vec2 vVertTexCoord;
176
+ #endif
177
+
178
+ uniform sampler2D uImg;
179
+ uniform sampler2D uDepth;
180
+ uniform vec2 uSize;
181
+ uniform int uNumSamples;
182
+ uniform float uNear;
183
+ uniform float uFar;
184
+ uniform vec4 uProjInfo;
185
+ uniform float uSearchRadius;
186
+ uniform float uIntensity;
187
+ uniform float uExponent;
188
+ uniform float uBias;
189
+
190
+ const int MAX_NUM_SAMPLES = 100;
191
+
192
+ float rand(vec2 co) {
193
+ return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453);
194
+ }
195
+ float rand(vec4 co) {
196
+ return fract(rand(co.xz) + rand(co.xy) + rand(co.yw) + rand(co.zw));
197
+ }
198
+
199
+ vec3 worldFromScreen(vec2 offset) {
200
+ #ifdef IS_WEBGL2
201
+ float z = uNear * uFar / ((uNear - uFar) * texture(uDepth, vVertTexCoord + offset).x + uFar);
202
+ #else
203
+ float z = uNear * uFar / ((uNear - uFar) * texture2D(uDepth, vVertTexCoord + offset).x + uFar);
204
+ #endif
205
+ return vec3((((vVertTexCoord + offset) * uSize) * uProjInfo.xy + uProjInfo.zw) * z, z);
206
+ }
207
+
208
+ vec2 screenFromWorld(vec3 world) {
209
+ return (world.xy/world.z - uProjInfo.zw)/uProjInfo.xy;
210
+ }
211
+
212
+ const float EPSILON = 0.01;
213
+
214
+ mat4 axisAngleRotation(vec3 axis, float angle) {
215
+ axis = normalize(axis);
216
+ float s = sin(angle);
217
+ float c = cos(angle);
218
+ float oc = 1.0 - c;
219
+
220
+ return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,
221
+ oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,
222
+ oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,
223
+ 0.0, 0.0, 0.0, 1.0);
224
+ }
225
+ vec3 adjustNormal(
226
+ vec3 origNormal,
227
+ vec3 displacementNormal,
228
+ vec3 noDisplacementNormal
229
+ ) {
230
+ // Find the rotation induced by the displacement
231
+ float angle = acos(dot(displacementNormal, noDisplacementNormal));
232
+ vec3 rawAxis = cross(displacementNormal, noDisplacementNormal);
233
+ if (length(rawAxis) < 0.01) {
234
+ return origNormal;
235
+ }
236
+ vec3 axis = normalize(rawAxis);
237
+ mat4 rotation = axisAngleRotation(axis, angle);
238
+
239
+ // Apply the rotation to the original normal
240
+ vec3 normal = (rotation * vec4(origNormal, 0.)).xyz;
241
+ return normal;
242
+ }
243
+
244
+ void main() {
245
+ #ifdef IS_WEBGL2
246
+ vec4 color = texture(uImg, vVertTexCoord);
247
+ #else
248
+ vec4 color = texture2D(uImg, vVertTexCoord);
249
+ #endif
250
+ vec3 position = worldFromScreen(vec2(0., 0.));
251
+ vec3 normal = normalize(cross(dFdx(position), dFdy(position)));
252
+
253
+ float radiusSquared = uSearchRadius * uSearchRadius;
254
+
255
+ float occlusion = 0.;
256
+
257
+ for (int i = 0; i < MAX_NUM_SAMPLES; i++) {
258
+ if (i >= uNumSamples) break;
259
+ float t = (float(i + 1) / float(uNumSamples));
260
+
261
+ // Sample a sort of random ish coordinate in a half sphere pointing up
262
+ float phi = ${2 * Math.PI} * rand(vec4(gl_FragCoord.xy,t*100.,0.));
263
+ float theta = ${Math.PI / 2} * rand(vec4(gl_FragCoord.xy,t*100.,100.));
264
+ float radius = 1.0 - t*t;
265
+ vec3 localOff = vec3(
266
+ radius * cos(phi) * sin(theta),
267
+ radius * cos(theta),
268
+ radius * sin(phi) * sin(theta)
269
+ );
270
+
271
+ // Translate that to be a hemisphere oriented with the surface normal
272
+ vec3 rotatedOff = adjustNormal(localOff, normal, vec3(0., 1., 0.));
273
+ vec3 testPosition = position + rotatedOff * uSearchRadius;
274
+ vec2 screenPosition = screenFromWorld(testPosition);
275
+ vec2 offset = screenPosition / uSize - vVertTexCoord;
276
+
277
+ // At that screen space coordinate, what is the position of the object we see?
278
+ vec3 samplePos = worldFromScreen(offset);
279
+
280
+ if (samplePos.z > mix(uNear, uFar, 0.99)) continue;
281
+
282
+ // The amount of occlusion is proportional to the *cosine* of the angle between
283
+ // the line connecting the object to the surface and the surface normal. This is
284
+ // because light coming in at an angle is more spread out and thus delivers less
285
+ // energy to the surface.
286
+ //
287
+ // The dot product of originToSample and the normal is proportional to this energy
288
+ // because dot(a, b) is equivalent to length(a)*length(b)*cos(angle_between_a_and_b)
289
+ vec3 originToSample = samplePos - position;
290
+ float squaredDistanceToSample = dot(originToSample, originToSample);
291
+ float vn = dot(originToSample, normal) - uBias;
292
+
293
+ // We only let stuff start making a shadow when it's within our search radius. At
294
+ // the edge it should not occlude, and as it gets closer, it should occlude more.
295
+ // We'll give it a cubic falloff so it looks smoother.
296
+ float f = max(radiusSquared - squaredDistanceToSample, 0.0) / radiusSquared;
297
+ float sampleOcclusion = f * f * f * max(vn / (EPSILON + squaredDistanceToSample), 0.0);
298
+
299
+ occlusion += sampleOcclusion;
300
+ }
301
+ occlusion = 1.0 - (occlusion / float(uNumSamples));
302
+ occlusion = clamp(pow(occlusion, 1.0 + uExponent), 0.0, 1.0);
303
+ vec4 finalColor = vec4(occlusion, occlusion, occlusion, 1.);
304
+ #ifdef IS_WEBGL2
305
+ outColor = finalColor;
306
+ #else
307
+ gl_FragColor = finalColor;
308
+ #endif
309
+ }
310
+ `
311
+
312
+ ContactShadowRenderer.blurFrag = `
313
+ precision highp float;
314
+ #ifdef IS_WEBGL2
315
+ in highp vec2 vVertTexCoord;
316
+ out highp vec4 outColor;
317
+ #else
318
+ varying highp vec2 vVertTexCoord;
319
+ #endif
320
+
321
+ uniform sampler2D uImg;
322
+ uniform sampler2D uDepth;
323
+ uniform sampler2D uShadow;
324
+ uniform vec2 uSize;
325
+ uniform float uNear;
326
+ uniform float uFar;
327
+ uniform float uIntensity;
328
+ uniform int uNumSamples;
329
+ uniform float uBlurRadius;
330
+
331
+ #ifdef IS_WEBGL2
332
+ #define texFn texture
333
+ #else
334
+ #define texFn texture2D
335
+ #endif
336
+
337
+ float depthToZ(float depth) {
338
+ float depthNormalized = 2.0 * depth - 1.0;
339
+ return 2.0 * uNear * uFar / (uFar + uNear - depthNormalized * (uFar - uNear));
340
+ }
341
+
342
+ const int MAX_NUM_SAMPLES = 100;
343
+
344
+ float rand(vec2 co) {
345
+ return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453);
346
+ }
347
+ float rand(vec4 co) {
348
+ return fract(rand(co.xz) + rand(co.xy) + rand(co.yw) + rand(co.zw));
349
+ }
350
+
351
+ void main() {
352
+ vec4 color = texFn(uImg, vVertTexCoord);
353
+
354
+ float origZ = depthToZ(texFn(uDepth, vVertTexCoord).x);
355
+ float occlusion = texFn(uShadow, vVertTexCoord).x;
356
+ float total = 1.;
357
+
358
+ for (int i = 0; i < MAX_NUM_SAMPLES; i++) {
359
+ if (i >= uNumSamples) break;
360
+ float t = (float(i) / float(uNumSamples - 1));
361
+ float angle = (t*12.0) * ${2 * Math.PI};
362
+ float radius = 1.0 - t;
363
+ angle += 5.*rand(gl_FragCoord.xy);
364
+
365
+ vec2 offset = (vec2(cos(angle),sin(angle)) * radius * uBlurRadius)/uSize;
366
+ float z = depthToZ(texFn(uDepth, vVertTexCoord + offset).x);
367
+
368
+ float weight = float(z >= origZ);
369
+ float shadowSample = texFn(uShadow, vVertTexCoord + offset).x;
370
+ occlusion += weight * shadowSample;
371
+ total += weight;
372
+ }
373
+ occlusion /= total;
374
+ vec4 mixedColor = vec4(color.rgb * mix(1., occlusion, uIntensity), color.a);
375
+ #ifdef IS_WEBGL2
376
+ outColor = mixedColor;
377
+ #else
378
+ gl_FragColor = mixedColor;
379
+ #endif
380
+ }
381
+ `
@@ -0,0 +1,115 @@
1
+ class GaussianBlurRenderer extends BlurRenderer {
2
+ constructor(target, options) {
3
+ super(target, options)
4
+ this.fbo2 = target.createFramebuffer(options)
5
+ this.intensity = 0.1
6
+ this.numSamples = 20
7
+ }
8
+
9
+ frag() {
10
+ return GaussianBlurRenderer.frag
11
+ }
12
+
13
+ getUniforms() {
14
+ const uniforms = super.getUniforms()
15
+ delete uniforms.uImg
16
+ return uniforms
17
+ }
18
+
19
+ draw(cb) {
20
+ this.fbo.draw(() => {
21
+ this.target.push()
22
+ cb()
23
+ this.target.pop()
24
+ })
25
+
26
+ const uniforms = this.getUniforms()
27
+
28
+ this.target.push()
29
+
30
+ this.fbo2.draw(() => {
31
+ this.target.push()
32
+ this.target.clear()
33
+ this.target.noStroke()
34
+ this.target.rectMode(CENTER)
35
+ this.target.shader(this.shader)
36
+ for (const key in uniforms) {
37
+ this.shader.setUniform(key, uniforms[key])
38
+ }
39
+ this.shader.setUniform('uDirection', 0)
40
+ this.shader.setUniform('uImg', this.fbo.color)
41
+ this.target.rect(0, 0, this.target.width, -this.target.height)
42
+ this.target.pop()
43
+ })
44
+
45
+ this.target.noStroke()
46
+ this.target.rectMode(CENTER)
47
+ this.target.shader(this.shader)
48
+ for (const key in uniforms) {
49
+ this.shader.setUniform(key, uniforms[key])
50
+ }
51
+ this.shader.setUniform('uDirection', 1)
52
+ this.shader.setUniform('uImg', this.fbo2.color)
53
+ this.target.rect(0, 0, this.target.width, -this.target.height)
54
+ this.target.pop()
55
+ }
56
+
57
+ remove() {
58
+ super.remove()
59
+ this.fbo2.remove()
60
+ }
61
+ }
62
+
63
+ p5.prototype.createGaussianBlurRenderer = function(options) {
64
+ return new GaussianBlurRenderer(this, options)
65
+ }
66
+
67
+ GaussianBlurRenderer.frag = `
68
+ precision highp float;
69
+ varying highp vec2 vVertTexCoord;
70
+ uniform sampler2D uImg;
71
+ uniform sampler2D uDepth;
72
+ uniform vec2 uSize;
73
+ uniform float uIntensity;
74
+ uniform float uDof;
75
+ uniform float maxBlur;
76
+ uniform int uNumSamples;
77
+ uniform float uTargetZ;
78
+ uniform float uNear;
79
+ uniform float uFar;
80
+ uniform int uDirection;
81
+ #define s ${0.5/3}
82
+ const int MAX_NUM_SAMPLES = 50;
83
+ float depthToZ(float depth) {
84
+ float depthNormalized = 2.0 * depth - 1.0;
85
+ return 2.0 * uNear * uFar / (uFar + uNear - depthNormalized * (uFar - uNear));
86
+ }
87
+ float calcBlur(float z, float pixelScale) {
88
+ return clamp(abs(z - uTargetZ) - uDof / 2., 0.0, 0.3*pixelScale);
89
+ }
90
+ void main() {
91
+ float total = 1.0;
92
+ float origZ = depthToZ(texture2D(uDepth, vVertTexCoord).x);
93
+ vec4 color = texture2D(uImg, vVertTexCoord);
94
+ if (abs(origZ - uTargetZ) > uDof / 2.) {
95
+ float pixelScale = max(uSize.x, uSize.y);
96
+ float blurAmt = calcBlur(origZ, pixelScale);
97
+ for (int i = 0; i < MAX_NUM_SAMPLES; i++) {
98
+ if (i >= uNumSamples) break;
99
+ float t = (float(i) / float(uNumSamples - 1));
100
+ float radius = (t * 2. - 1.);
101
+ float distAway = radius * uIntensity * blurAmt;
102
+ vec2 offset = (uDirection == 0 ? vec2(1.,0.) : vec2(0.,1.)) * distAway / pixelScale;
103
+ float z = depthToZ(texture2D(uDepth, vVertTexCoord + offset).x);
104
+ float sampleBlur = calcBlur(z, pixelScale);
105
+ float t2 = distAway / (sampleBlur * uIntensity);
106
+ float weight = ${1/Math.sqrt(2*Math.PI)} / s * exp(-0.5*pow(t2/s,2.));
107
+ vec4 sample = texture2D(uImg, vVertTexCoord + offset);
108
+ color += weight * sample;
109
+ total += weight;
110
+ }
111
+ }
112
+ color /= total;
113
+ gl_FragColor = color;
114
+ }
115
+ `
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Dave Pagurek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,261 @@
1
+ # p5.filterRenderer
2
+
3
+ A library for p5.js WebGL mode to draw with depth blur and shadows.
4
+
5
+ Read more about the motivation for this and how focal blur shaders work in <a href="https://www.davepagurek.com/blog/depth-of-field/">this blog post on the subject.</a>
6
+
7
+ ![image](https://user-images.githubusercontent.com/5315059/172021218-b50f6693-40a6-49a1-99af-8dd9d73f00eb.png)
8
+ <small><em>Above: a screenshot from [a sketch](https://openprocessing.org/sketch/1590159) using blurring out-of-focus areas</em></small>
9
+
10
+ ## Get the library
11
+
12
+ Add the library to your source code, *after* loading p5 but *before* loading your own code.
13
+
14
+ ### Via CDN
15
+ ```html
16
+ <script src="https://cdn.jsdelivr.net/npm/@davepagurek/p5.filterRenderer@0.0.12/p5.Framebuffer.min.js"></script>
17
+ ```
18
+
19
+ On OpenProcessing, paste this link into a new library slot:
20
+ ```
21
+ https://cdn.jsdelivr.net/npm/@davepagurek/p5.filterRenderer@0.0.12/p5.filterRenderer.min.js
22
+ ```
23
+
24
+ ### Self-hosted
25
+ [Download the minified or unminified source code from the releases tab](https://github.com/davepagurek/p5.filterRenderer/releases/), then add it to your HTML:
26
+ ```html
27
+ <script type="text/javascript" src="p5.filterRenderer.min.js"></script>
28
+ ```
29
+
30
+
31
+ ## Usage
32
+
33
+ ### Depth of field blur
34
+
35
+ The library provides a helper that bundles a Framebuffer with a shader that applies focal blur, leaving objects at a provided distance in focus and blurring things more the farther away from that point they are.
36
+
37
+ Create a blur renderer and draw inside its `draw` callback. When you tell it to `focusHere()`, anything drawn at that transformed position will be in focus. You can use standard p5 `translate` calls to position the focal point.
38
+
39
+ #### Gaussian blur
40
+
41
+ This is likely the best-looking blur renderer, although it uses two render passes. Start by using this one, but look out the other `BlurRenderer` if it's slow.
42
+
43
+ <table>
44
+ <tr>
45
+ <td>
46
+
47
+ ```js
48
+ let blurRenderer
49
+
50
+ function setup() {
51
+ createCanvas(400, 400, WEBGL)
52
+ blurRenderer = createGaussianBlurRenderer()
53
+ blurRenderer.setIntensity(0.15)
54
+ blurRenderer.setSamples(20)
55
+ blurRenderer.setDof(50)
56
+ }
57
+
58
+ function draw() {
59
+ blurRenderer.draw(() => {
60
+ clear()
61
+ push()
62
+ background(255)
63
+ noStroke()
64
+ lights()
65
+
66
+ push()
67
+ fill('blue')
68
+ translate(-80, -80, -300)
69
+ blurRenderer.focusHere()
70
+ sphere(50)
71
+ pop()
72
+
73
+ push()
74
+ fill('red')
75
+ sphere(50)
76
+ pop()
77
+ pop()
78
+ })
79
+ }
80
+ ```
81
+
82
+ </td>
83
+ <td>
84
+ <img src="https://user-images.githubusercontent.com/5315059/201497333-92a3f46e-91b7-4d4e-a675-f958d8d9ff50.png" width="400">
85
+ </td>
86
+ </tr>
87
+ </table>
88
+
89
+ Methods on `GaussianBlurRenderer`:
90
+ - `GaussianBlurRenderer.prototype.draw(callback: () => void)`
91
+ - Draw the scene defined in the callback with blur
92
+ - `GaussianBlurRenderer.prototype.focusHere()`
93
+ - Tell the renderer what point in space should be in focus. It will move based on any calls to `translate()` or other transformations that you have applied.
94
+ - Defaults to the origin
95
+ - `GaussianBlurRenderer.prototype.setIntensity(intensity: number)`
96
+ - Control the intensity of the blur, between 0 and 1: the lower the intensity, the farther objects have to be from the focal point to be blurred
97
+ - Defaults to 0.1
98
+ - `GaussianBlurRenderer.prototype.setDof(dof: number)`
99
+ - Control the depth of field (dof), which is the distance away from the focal point that is also in focus, from 0 up
100
+ - The lower the dof, the smaller range will be that has no blur. Blur amount will start to accumulate when objects are outside of the dof range
101
+ - The focal target (set by `focusHere`) is located in the centre of the clear range. So assume the focal target's depth value is `z`, then the clear range becomes from `z - dof / 2` to `z + dof / 2`.
102
+ - Defaults to 0
103
+ - `GaussianBlurRenderer.prototype.setSamples(numSamples: number)`
104
+ - Control how many random samples to use in the blur shader. More samples will look smoother but is more computationally intensive.
105
+ - Defaults to 20
106
+
107
+ A live example: https://davepagurek.github.io/p5.Framebuffer/examples/gaussianblur
108
+
109
+
110
+ #### One-pass blur
111
+
112
+ Another implementation of blur, but using a single shader pass. This will likely produce a grainier result, but might be faster on some systems.
113
+
114
+ <table>
115
+ <tr>
116
+ <td>
117
+
118
+ ```js
119
+ let blurRenderer
120
+
121
+ function setup() {
122
+ createCanvas(400, 400, WEBGL)
123
+ blurRenderer = createBlurRenderer()
124
+ }
125
+
126
+ function draw() {
127
+ blurRenderer.draw(() => {
128
+ clear()
129
+ push()
130
+ background(255)
131
+ noStroke()
132
+ lights()
133
+
134
+ push()
135
+ fill('blue')
136
+ translate(-80, -80, -300)
137
+ blurRenderer.focusHere()
138
+ sphere(50)
139
+ pop()
140
+
141
+ push()
142
+ fill('red')
143
+ sphere(50)
144
+ pop()
145
+ pop()
146
+ })
147
+ }
148
+ ```
149
+
150
+ </td>
151
+ <td>
152
+ <img src="https://user-images.githubusercontent.com/5315059/178128839-164de943-960c-4e0a-ba6a-a7aa836ec798.png">
153
+ </td>
154
+ </tr>
155
+ </table>
156
+
157
+ Methods on `BlurRenderer`:
158
+ - `BlurRenderer.prototype.draw(callback: () => void)`
159
+ - Draw the scene defined in the callback with blur
160
+ - `BlurRenderer.prototype.focusHere()`
161
+ - Tell the renderer what point in space should be in focus. It will move based on any calls to `translate()` or other transformations that you have applied.
162
+ - Defaults to the origin
163
+ - `BlurRenderer.prototype.setIntensity(intensity: number)`
164
+ - Control the intensity of the blur, between 0 and 1: the lower the intensity, the farther objects have to be from the focal point to be blurred
165
+ - Defaults to 0.05
166
+ - `BlurRenderer.prototype.setDof(dof: number)`
167
+ - Control the depth of field (dof), which is the distance away from the focal point that is also in focus, from 0 up
168
+ - The lower the dof, the smaller range will be that has no blur. Blur amount will start to accumulate when objects are outside of the dof range
169
+ - The focal target (set by `focusHere`) is located in the centre of the clear range. So assume the focal target's depth value is `z`, then the clear range becomes from `z - dof / 2` to `z + dof / 2`.
170
+ - Defaults to 0
171
+ - `BlurRenderer.prototype.setSamples(numSamples: number)`
172
+ - Control how many random samples to use in the blur shader. More samples will look smoother but is more computationally intensive.
173
+ - Defaults to 15
174
+
175
+ A live example: https://davepagurek.github.io/p5.Framebuffer/examples/blur
176
+
177
+ ### Contact Shadows
178
+
179
+ The library provides a helper that bundles a Framebuffer with a shader that applies Ambient Occlusion shadows. This approximates the shadows one would see if there was uniform light hitting an object from all sides. In practice, it adds shadows in areas where objects get close to each other.
180
+
181
+ Create a shadow renderer and draw inside its `draw` callback. The renderer will add shadows to the result.
182
+
183
+ <table>
184
+ <tr>
185
+ <td>
186
+
187
+ ```js
188
+ let contactShadowRenderer
189
+
190
+ function setup() {
191
+ createCanvas(400, 400, WEBGL)
192
+ contactShadowRenderer = createContactShadowRenderer()
193
+ }
194
+
195
+ function draw() {
196
+ contactShadowRenderer.draw(() => {
197
+ clear()
198
+ push()
199
+ background(255)
200
+ fill(255)
201
+ noStroke()
202
+ lights()
203
+
204
+ push()
205
+ translate(50, -50, 10)
206
+ sphere(50)
207
+ pop()
208
+
209
+ push()
210
+ translate(-50, 50, -10)
211
+ sphere(90)
212
+ pop()
213
+ })
214
+ }
215
+ ```
216
+
217
+ </td>
218
+ <td>
219
+ <img src="https://user-images.githubusercontent.com/5315059/178128655-22816bcd-901d-49b5-95db-753815762805.png">
220
+ </td>
221
+ </tr>
222
+ </table>
223
+
224
+ Methods on `ContactShadowRenderer`:
225
+ - `ContactShadowRenderer.prototype.draw(callback: () => void)`
226
+ - Draw the scene defined in the callback with shadows added
227
+ - `ContactShadowRenderer.prototype.setIntensity(intensity: number)`
228
+ - Control how dark shadows are: 0 is no shadows, and 1 is full darkness
229
+ - Defaults to 0.5
230
+ - `ContactShadowRenderer.prototype.setShadowSamples(numSamples: number)`
231
+ - Control how many random samples to use in the shadow shader. More samples will be more accurate but is more computationally intensive.
232
+ - Defaults to 15
233
+ - `ContactShadowRenderer.prototype.setBlurSamples(numSamples: number)`
234
+ - Control how many random samples to use in the blur shader. More samples will be smoother but is more computationally intensive.
235
+ - Defaults to 20
236
+ - `ContactShadowRenderer.prototype.setBlurRadius(radius: number)`
237
+ - Sets how far the blur extends when blurring shadows, in pixels, ignoring the pixel density
238
+ - Defaults to 50
239
+ - `ContactShadowRenderer.prototype.setSearchRadius(radius: number)`
240
+ - Control how close together objects need to be for them to cast shadows
241
+ - This is defined in *world space,* meaning all transformations are applied when checking distances
242
+ - Defaults to 100
243
+
244
+ A live example: https://davepagurek.github.io/p5.Framebuffer/examples/shadows
245
+
246
+ ## External examples
247
+
248
+ - <a href="https://openprocessing.org/sketch/1773564">Rolling Shutter</a>
249
+ - Uses 120 framebuffers to store previous frames of video for a slit scanning effect
250
+ - <a href="https://openprocessing.org/sketch/1721124">Wizard Pondering Orb</a>
251
+ - Uses the Gaussian blur renderer
252
+ - <a href="https://openprocessing.org/sketch/1616318">3D Text</a>
253
+ - Uses two framebuffers to do a feedback effect
254
+ - <a href="https://openprocessing.org/sketch/1622863">Disassemble</a>
255
+ - Uses the contact shadow renderer
256
+ - <a href="https://openprocessing.org/sketch/1590159">Train Knots</a>
257
+ - Uses the depth buffer in a focal blur shader
258
+ - <a href="https://openprocessing.org/sketch/1460113">Modern Vampires of the City</a>
259
+ - Uses the depth buffer to create a fog effect
260
+
261
+ More coming soon!
package/Renderer.js ADDED
@@ -0,0 +1,50 @@
1
+ class Renderer {
2
+ constructor(target = window, options = {}) {
3
+ this.target = target
4
+ this.fbo = target.createFramebuffer(options)
5
+ this.shader = target.createShader(this.vert(), this.frag())
6
+ }
7
+
8
+ vert() {
9
+ throw new Error('Unimplemented')
10
+ }
11
+
12
+ frag() {
13
+ throw new Error('Unimplemented')
14
+ }
15
+
16
+ getUniforms() {
17
+ return {}
18
+ }
19
+
20
+ draw(cb) {
21
+ this.fbo.draw(() => {
22
+ this.target.push()
23
+ cb()
24
+ this.target.pop()
25
+ })
26
+
27
+ const uniforms = this.getUniforms()
28
+
29
+ this.target.push()
30
+ this.target.noStroke()
31
+ this.target.rectMode(CENTER)
32
+ this.target.shader(this.shader)
33
+ for (const key in uniforms) {
34
+ this.shader.setUniform(key, uniforms[key])
35
+ }
36
+ this.target.rect(0, 0, this.target.width, -this.target.height)
37
+ this.target.pop()
38
+ }
39
+
40
+ remove() {
41
+ this.fbo.remove()
42
+ }
43
+ }
44
+
45
+ const superPerspective = p5.Camera.prototype.perspective
46
+ p5.Camera.prototype.perspective = function(fovy, aspect, near, far) {
47
+ this._near = near === undefined ? this.defaultCameraNear : near
48
+ this._far = far === undefined ? this.defaultCameraFar : far
49
+ superPerspective.call(this, fovy, aspect, near, far)
50
+ }
@@ -0,0 +1,4 @@
1
+ class Renderer{constructor(t=window,e={}){this.target=t,this.fbo=t.createFramebuffer(e),this.shader=t.createShader(this.vert(),this.frag())}vert(){throw new Error("Unimplemented")}frag(){throw new Error("Unimplemented")}getUniforms(){return{}}draw(t){this.fbo.draw((()=>{this.target.push(),t(),this.target.pop()}));const e=this.getUniforms();this.target.push(),this.target.noStroke(),this.target.rectMode(CENTER),this.target.shader(this.shader);for(const t in e)this.shader.setUniform(t,e[t]);this.target.rect(0,0,this.target.width,-this.target.height),this.target.pop()}remove(){this.fbo.remove()}}const superPerspective=p5.Camera.prototype.perspective;p5.Camera.prototype.perspective=function(t,e,r,s){this._near=void 0===r?this.defaultCameraNear:r,this._far=void 0===s?this.defaultCameraFar:s,superPerspective.call(this,t,e,r,s)};
2
+ class BlurRenderer extends Renderer{constructor(e,t){super(e,t),this.focus=e.height/2/tan(PI/6),this.intensity=.05,this.dof=0,this.numSamples=15}vert(){return BlurRenderer.vert}frag(){return BlurRenderer.frag}focusHere(){const e=new DOMMatrix(this.target._renderer.uMVMatrix.mat4),t=new DOMPoint(0,0,0).matrixTransform(e);this.focus=-t.z}setDof(e){this.dof=e}setIntensity(e){this.intensity=e}setSamples(e){this.numSamples=e}getUniforms(){return{uImg:this.fbo.color,uDepth:this.fbo.depth,uSize:[this.target.width,this.target.height],uIntensity:this.intensity,uDof:this.dof,uNumSamples:this.numSamples,uNear:this.target._renderer._curCamera._near,uFar:this.target._renderer._curCamera._far,uTargetZ:this.focus}}}p5.prototype.createBlurRenderer=function(e){return new BlurRenderer(this,e)},BlurRenderer.vert="\nprecision highp float;\n\nattribute vec3 aPosition;\nattribute vec3 aNormal;\nattribute vec2 aTexCoord;\n\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\nuniform mat3 uNormalMatrix;\n\nvarying highp vec2 vVertTexCoord;\n\nvoid main(void) {\n vec4 positionVec4 = vec4(aPosition, 1.0);\n gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;\n vVertTexCoord = aTexCoord;\n}\n",BlurRenderer.frag="\nprecision highp float;\nvarying highp vec2 vVertTexCoord;\n\nuniform sampler2D uImg;\nuniform sampler2D uDepth;\nuniform vec2 uSize;\nuniform float uIntensity;\nuniform float uDof;\nuniform float maxBlur;\nuniform int uNumSamples;\nuniform float uTargetZ;\nuniform float uNear;\nuniform float uFar;\n\n#define PI 3.14159265359;\n\nconst int MAX_NUM_SAMPLES = 50;\n\nfloat rand(vec2 co){\n return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453);\n}\n\nfloat depthToZ(float depth) {\n float depthNormalized = 2.0 * depth - 1.0;\n return 2.0 * uNear * uFar / (uFar + uNear - depthNormalized * (uFar - uNear));\n}\n\nfloat calcBlur(float z, float pixelScale) {\n return clamp(abs(z - uTargetZ) - uDof / 2., 0.0, 0.3*pixelScale);\n}\n\nvoid main() {\n float total = 1.0;\n float origZ = depthToZ(texture2D(uDepth, vVertTexCoord).x);\n vec4 color = texture2D(uImg, vVertTexCoord);\n\n if (abs(origZ - uTargetZ) > uDof / 2.) {\n float pixelScale = max(uSize.x, uSize.y);\n float blurAmt = calcBlur(origZ, pixelScale);\n for (int i = 0; i < MAX_NUM_SAMPLES; i++) {\n if (i >= uNumSamples) break;\n float t = (float(i + 1) / float(uNumSamples));\n float angle = (t*12.0) * 2. * PI;\n float radius = 1.0 - (t*t*t); // Sample more on the outer edge\n angle += 5.*rand(gl_FragCoord.xy);\n vec2 offset = (vec2(cos(angle),sin(angle)) * radius * uIntensity * blurAmt)/pixelScale;\n float z = depthToZ(texture2D(uDepth, vVertTexCoord + offset).x);\n float sampleBlur = calcBlur(z, pixelScale);\n\n float weight = float((z >= origZ) || (sampleBlur >= blurAmt*radius + 5.));\n vec4 sample = texture2D(uImg, vVertTexCoord + offset);\n color += weight * sample;\n total += weight;\n }\n }\n\n color /= total;\n gl_FragColor = color;\n}\n";
3
+ class GaussianBlurRenderer extends BlurRenderer{constructor(t,e){super(t,e),this.fbo2=t.createFramebuffer(e),this.intensity=.1,this.numSamples=20}frag(){return GaussianBlurRenderer.frag}getUniforms(){const t=super.getUniforms();return delete t.uImg,t}draw(t){this.fbo.draw((()=>{this.target.push(),t(),this.target.pop()}));const e=this.getUniforms();this.target.push(),this.fbo2.draw((()=>{this.target.push(),this.target.clear(),this.target.noStroke(),this.target.rectMode(CENTER),this.target.shader(this.shader);for(const t in e)this.shader.setUniform(t,e[t]);this.shader.setUniform("uDirection",0),this.shader.setUniform("uImg",this.fbo.color),this.target.rect(0,0,this.target.width,-this.target.height),this.target.pop()})),this.target.noStroke(),this.target.rectMode(CENTER),this.target.shader(this.shader);for(const t in e)this.shader.setUniform(t,e[t]);this.shader.setUniform("uDirection",1),this.shader.setUniform("uImg",this.fbo2.color),this.target.rect(0,0,this.target.width,-this.target.height),this.target.pop()}remove(){super.remove(),this.fbo2.remove()}}p5.prototype.createGaussianBlurRenderer=function(t){return new GaussianBlurRenderer(this,t)},GaussianBlurRenderer.frag=`\nprecision highp float;\nvarying highp vec2 vVertTexCoord;\nuniform sampler2D uImg;\nuniform sampler2D uDepth;\nuniform vec2 uSize;\nuniform float uIntensity;\nuniform float uDof;\nuniform float maxBlur;\nuniform int uNumSamples;\nuniform float uTargetZ;\nuniform float uNear;\nuniform float uFar;\nuniform int uDirection;\n#define s ${.5/3}\nconst int MAX_NUM_SAMPLES = 50;\nfloat depthToZ(float depth) {\n float depthNormalized = 2.0 * depth - 1.0;\n return 2.0 * uNear * uFar / (uFar + uNear - depthNormalized * (uFar - uNear));\n}\nfloat calcBlur(float z, float pixelScale) {\n return clamp(abs(z - uTargetZ) - uDof / 2., 0.0, 0.3*pixelScale);\n}\nvoid main() {\n float total = 1.0;\n float origZ = depthToZ(texture2D(uDepth, vVertTexCoord).x);\n vec4 color = texture2D(uImg, vVertTexCoord);\n if (abs(origZ - uTargetZ) > uDof / 2.) {\n float pixelScale = max(uSize.x, uSize.y);\n float blurAmt = calcBlur(origZ, pixelScale);\n for (int i = 0; i < MAX_NUM_SAMPLES; i++) {\n if (i >= uNumSamples) break;\n float t = (float(i) / float(uNumSamples - 1));\n float radius = (t * 2. - 1.);\n float distAway = radius * uIntensity * blurAmt;\n vec2 offset = (uDirection == 0 ? vec2(1.,0.) : vec2(0.,1.)) * distAway / pixelScale;\n float z = depthToZ(texture2D(uDepth, vVertTexCoord + offset).x);\n float sampleBlur = calcBlur(z, pixelScale);\n float t2 = distAway / (sampleBlur * uIntensity);\n float weight = ${1/Math.sqrt(2*Math.PI)} / s * exp(-0.5*pow(t2/s,2.));\n vec4 sample = texture2D(uImg, vVertTexCoord + offset);\n color += weight * sample;\n total += weight;\n }\n }\n color /= total;\n gl_FragColor = color;\n}\n`;
4
+ class ContactShadowRenderer extends Renderer{constructor(e,t){super(e,t),!this.target.webglVersion===WEBGL2&&this.target._renderer.GL.getExtension("OES_standard_derivatives"),this.fbo2=e.createFramebuffer(t),this.blurShader=e.createShader(this.blurVert(),this.blurFrag()),this.intensity=.5,this.numShadowSamples=15,this.numBlurSamples=20,this.exponent=250,this.bias=.1,this.searchRadius=100,this.blurRadius=50}prefix(){return this.target.webglVersion===WEBGL2?"#version 300 es\n#define IS_WEBGL2\n":"#extension GL_OES_standard_derivatives : enable\n"}vert(){return this.prefix()+ContactShadowRenderer.vert}frag(){return this.prefix()+ContactShadowRenderer.frag}blurVert(){return this.vert()}blurFrag(){return this.prefix()+ContactShadowRenderer.blurFrag}setIntensity(e){this.intensity=e}setShadowSamples(e){this.numShadowSamples=e}setBlurSamples(e){this.numBlurSamples=e}setBlurRadius(e){this.blurRadius=e}setExponent(e){this.exponent=e}setBias(e){this.bias=e}setSearchRadius(e){this.searchRadius=e}getShadowUniforms(){const e=[-2/(this.target.width*this.target._renderer.uPMatrix.mat4[0]),-2/(this.target.height*this.target._renderer.uPMatrix.mat4[5]),(1-this.target._renderer.uPMatrix.mat4[2])/this.target._renderer.uPMatrix.mat4[0],(1+this.target._renderer.uPMatrix.mat4[6])/this.target._renderer.uPMatrix.mat4[5]];return{uImg:this.fbo.color,uDepth:this.fbo.depth,uSize:[this.target.width,this.target.height],uIntensity:this.intensity,uNumSamples:this.numShadowSamples,uNear:this.target._renderer._curCamera.cameraNear,uFar:this.target._renderer._curCamera.cameraFar,uProjInfo:e,uExponent:this.exponent,uBias:this.bias,uSearchRadius:this.searchRadius}}getBlurUniforms(){return{uImg:this.fbo.color,uDepth:this.fbo.depth,uShadow:this.fbo2.color,uSize:[this.target.width,this.target.height],uIntensity:this.intensity,uNear:this.target._renderer._curCamera.cameraNear,uFar:this.target._renderer._curCamera.cameraFar,uNumSamples:this.numBlurSamples,uBlurRadius:this.blurRadius}}draw(e){const t=this.getShadowUniforms(),n=this.getBlurUniforms();this.fbo.draw((()=>{this.target.push(),e(),this.target.pop()})),this.target.push(),this.fbo2.draw((()=>{this.target.push(),this.target.clear(),this.target.noStroke(),this.target.rectMode(CENTER),this.target.shader(this.shader);for(const e in t)this.shader.setUniform(e,t[e]);this.target.rect(0,0,this.target.width,-this.target.height),this.target.pop()})),this.target.noStroke(),this.target.rectMode(CENTER),this.target.shader(this.blurShader);for(const e in n)this.blurShader.setUniform(e,n[e]);this.target.rect(0,0,this.target.width,-this.target.height),this.target.pop()}}p5.prototype.createContactShadowRenderer=function(e){return new ContactShadowRenderer(this,e)},ContactShadowRenderer.vert="\n#ifdef IS_WEBGL2\nin vec3 aPosition;\nin vec3 aNormal;\nin vec2 aTexCoord;\n#else\nattribute vec3 aPosition;\nattribute vec3 aNormal;\nattribute vec2 aTexCoord;\n#endif\n\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\nuniform mat3 uNormalMatrix;\n\n#ifdef IS_WEBGL2\nout highp vec2 vVertTexCoord;\n#else\nvarying highp vec2 vVertTexCoord;\n#endif\n\nvoid main(void) {\n vec4 positionVec4 = vec4(aPosition, 1.0);\n gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;\n vVertTexCoord = aTexCoord;\n}\n",ContactShadowRenderer.frag=`\nprecision highp float;\n#ifdef IS_WEBGL2\nin highp vec2 vVertTexCoord;\nout highp vec4 outColor;\n#else\nvarying highp vec2 vVertTexCoord;\n#endif\n\nuniform sampler2D uImg;\nuniform sampler2D uDepth;\nuniform vec2 uSize;\nuniform int uNumSamples;\nuniform float uNear;\nuniform float uFar;\nuniform vec4 uProjInfo;\nuniform float uSearchRadius;\nuniform float uIntensity;\nuniform float uExponent;\nuniform float uBias;\n\nconst int MAX_NUM_SAMPLES = 100;\n\nfloat rand(vec2 co) {\n return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453);\n}\nfloat rand(vec4 co) {\n return fract(rand(co.xz) + rand(co.xy) + rand(co.yw) + rand(co.zw));\n}\n\nvec3 worldFromScreen(vec2 offset) {\n#ifdef IS_WEBGL2\n float z = uNear * uFar / ((uNear - uFar) * texture(uDepth, vVertTexCoord + offset).x + uFar);\n#else\n float z = uNear * uFar / ((uNear - uFar) * texture2D(uDepth, vVertTexCoord + offset).x + uFar);\n#endif\n return vec3((((vVertTexCoord + offset) * uSize) * uProjInfo.xy + uProjInfo.zw) * z, z);\n}\n\nvec2 screenFromWorld(vec3 world) {\n return (world.xy/world.z - uProjInfo.zw)/uProjInfo.xy;\n}\n\nconst float EPSILON = 0.01;\n\nmat4 axisAngleRotation(vec3 axis, float angle) {\n axis = normalize(axis);\n float s = sin(angle);\n float c = cos(angle);\n float oc = 1.0 - c;\n\n return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,\n oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,\n oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,\n 0.0, 0.0, 0.0, 1.0);\n}\nvec3 adjustNormal(\n vec3 origNormal,\n vec3 displacementNormal,\n vec3 noDisplacementNormal\n) {\n // Find the rotation induced by the displacement\n float angle = acos(dot(displacementNormal, noDisplacementNormal));\n vec3 rawAxis = cross(displacementNormal, noDisplacementNormal);\n if (length(rawAxis) < 0.01) {\n return origNormal;\n }\n vec3 axis = normalize(rawAxis);\n mat4 rotation = axisAngleRotation(axis, angle);\n\n // Apply the rotation to the original normal\n vec3 normal = (rotation * vec4(origNormal, 0.)).xyz;\n return normal;\n}\n\nvoid main() {\n#ifdef IS_WEBGL2\n vec4 color = texture(uImg, vVertTexCoord);\n#else\n vec4 color = texture2D(uImg, vVertTexCoord);\n#endif\n vec3 position = worldFromScreen(vec2(0., 0.));\n vec3 normal = normalize(cross(dFdx(position), dFdy(position)));\n\n float radiusSquared = uSearchRadius * uSearchRadius;\n\n float occlusion = 0.;\n\n for (int i = 0; i < MAX_NUM_SAMPLES; i++) {\n if (i >= uNumSamples) break;\n float t = (float(i + 1) / float(uNumSamples));\n\n // Sample a sort of random ish coordinate in a half sphere pointing up\n float phi = ${2*Math.PI} * rand(vec4(gl_FragCoord.xy,t*100.,0.));\n float theta = ${Math.PI/2} * rand(vec4(gl_FragCoord.xy,t*100.,100.));\n float radius = 1.0 - t*t;\n vec3 localOff = vec3(\n radius * cos(phi) * sin(theta),\n radius * cos(theta),\n radius * sin(phi) * sin(theta)\n );\n\n // Translate that to be a hemisphere oriented with the surface normal\n vec3 rotatedOff = adjustNormal(localOff, normal, vec3(0., 1., 0.));\n vec3 testPosition = position + rotatedOff * uSearchRadius;\n vec2 screenPosition = screenFromWorld(testPosition);\n vec2 offset = screenPosition / uSize - vVertTexCoord;\n \n // At that screen space coordinate, what is the position of the object we see?\n vec3 samplePos = worldFromScreen(offset);\n\n if (samplePos.z > mix(uNear, uFar, 0.99)) continue;\n\n // The amount of occlusion is proportional to the *cosine* of the angle between\n // the line connecting the object to the surface and the surface normal. This is\n // because light coming in at an angle is more spread out and thus delivers less\n // energy to the surface.\n //\n // The dot product of originToSample and the normal is proportional to this energy\n // because dot(a, b) is equivalent to length(a)*length(b)*cos(angle_between_a_and_b)\n vec3 originToSample = samplePos - position;\n float squaredDistanceToSample = dot(originToSample, originToSample);\n float vn = dot(originToSample, normal) - uBias;\n\n // We only let stuff start making a shadow when it's within our search radius. At\n // the edge it should not occlude, and as it gets closer, it should occlude more.\n // We'll give it a cubic falloff so it looks smoother.\n float f = max(radiusSquared - squaredDistanceToSample, 0.0) / radiusSquared;\n float sampleOcclusion = f * f * f * max(vn / (EPSILON + squaredDistanceToSample), 0.0);\n\n occlusion += sampleOcclusion;\n }\n occlusion = 1.0 - (occlusion / float(uNumSamples));\n occlusion = clamp(pow(occlusion, 1.0 + uExponent), 0.0, 1.0);\n vec4 finalColor = vec4(occlusion, occlusion, occlusion, 1.);\n#ifdef IS_WEBGL2\n outColor = finalColor;\n#else\n gl_FragColor = finalColor;\n#endif\n}\n`,ContactShadowRenderer.blurFrag=`\nprecision highp float;\n#ifdef IS_WEBGL2\nin highp vec2 vVertTexCoord;\nout highp vec4 outColor;\n#else\nvarying highp vec2 vVertTexCoord;\n#endif\n\nuniform sampler2D uImg;\nuniform sampler2D uDepth;\nuniform sampler2D uShadow;\nuniform vec2 uSize;\nuniform float uNear;\nuniform float uFar;\nuniform float uIntensity;\nuniform int uNumSamples;\nuniform float uBlurRadius;\n\n#ifdef IS_WEBGL2\n#define texFn texture\n#else\n#define texFn texture2D\n#endif\n\nfloat depthToZ(float depth) {\n float depthNormalized = 2.0 * depth - 1.0;\n return 2.0 * uNear * uFar / (uFar + uNear - depthNormalized * (uFar - uNear));\n}\n\nconst int MAX_NUM_SAMPLES = 100;\n\nfloat rand(vec2 co) {\n return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453);\n}\nfloat rand(vec4 co) {\n return fract(rand(co.xz) + rand(co.xy) + rand(co.yw) + rand(co.zw));\n}\n\nvoid main() {\n vec4 color = texFn(uImg, vVertTexCoord);\n\n float origZ = depthToZ(texFn(uDepth, vVertTexCoord).x);\n float occlusion = texFn(uShadow, vVertTexCoord).x;\n float total = 1.;\n\n for (int i = 0; i < MAX_NUM_SAMPLES; i++) {\n if (i >= uNumSamples) break;\n float t = (float(i) / float(uNumSamples - 1));\n float angle = (t*12.0) * ${2*Math.PI};\n float radius = 1.0 - t;\n angle += 5.*rand(gl_FragCoord.xy);\n\n vec2 offset = (vec2(cos(angle),sin(angle)) * radius * uBlurRadius)/uSize;\n float z = depthToZ(texFn(uDepth, vVertTexCoord + offset).x);\n\n float weight = float(z >= origZ);\n float shadowSample = texFn(uShadow, vVertTexCoord + offset).x;\n occlusion += weight * shadowSample;\n total += weight;\n }\n occlusion /= total;\n vec4 mixedColor = vec4(color.rgb * mix(1., occlusion, uIntensity), color.a);\n#ifdef IS_WEBGL2\n outColor = mixedColor;\n#else\n gl_FragColor = mixedColor;\n#endif\n}\n`;
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@davepagurek/p5.filterrenderer",
3
+ "version": "0.0.12",
4
+ "main": "p5.filterRenderer.min.js",
5
+ "author": "Dave Pagurek <dave@davepagurek.com>",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/davepagurek/p5.filterRenderer.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/davepagurek/p5.filterRenderer/issues"
13
+ },
14
+ "homepage": "https://github.com/davepagurek/p5.filterRenderer",
15
+ "dependencies": {},
16
+ "devDependencies": {
17
+ "minify": "^9.0.0",
18
+ "prettier": "^2.7.1"
19
+ },
20
+ "scripts": {
21
+ "build:all": "minify Renderer.js > p5.filterRenderer.min.js; minify BlurRenderer.js >> p5.filterRenderer.min.js; minify GaussianBlurRenderer.js >> p5.filterRenderer.min.js; minify ContactShadowRenderer.js >> p5.filterRenderer.min.js",
22
+ "build": "yarn build:all",
23
+ "publish": "npm publish --access public"
24
+ },
25
+ "files": [
26
+ "p5.filterRenderer.min.js",
27
+ "Renderer.js",
28
+ "BlurRenderer.js",
29
+ "GaussianBlurRenderer.js",
30
+ "ContactShadowRenderer.js"
31
+ ]
32
+ }