@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.
- package/BlurRenderer.js +136 -0
- package/ContactShadowRenderer.js +381 -0
- package/GaussianBlurRenderer.js +115 -0
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/Renderer.js +50 -0
- package/p5.filterRenderer.min.js +4 -0
- package/package.json +32 -0
package/BlurRenderer.js
ADDED
|
@@ -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
|
+

|
|
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
|
+
}
|