@davepagurek/p5.filterrenderer 0.0.13 → 0.0.15
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/ContactShadowRenderer.js +8 -2
- package/README.md +5 -2
- package/p5.filterRenderer.min.js +1 -1
- package/package.json +1 -1
package/ContactShadowRenderer.js
CHANGED
|
@@ -13,6 +13,7 @@ class ContactShadowRenderer extends Renderer {
|
|
|
13
13
|
this.bias = 0.1
|
|
14
14
|
this.searchRadius = 100
|
|
15
15
|
this.blurRadius = 50
|
|
16
|
+
this.tintColor = this.target.color(0)
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
prefix() {
|
|
@@ -39,6 +40,9 @@ class ContactShadowRenderer extends Renderer {
|
|
|
39
40
|
return this.prefix() + ContactShadowRenderer.blurFrag
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
setTintColor(c) {
|
|
44
|
+
this.tintColor = this.target.color(c)
|
|
45
|
+
}
|
|
42
46
|
setIntensity(intensity) {
|
|
43
47
|
this.intensity = intensity
|
|
44
48
|
}
|
|
@@ -95,6 +99,7 @@ class ContactShadowRenderer extends Renderer {
|
|
|
95
99
|
uFar: this.target._renderer._curCamera.cameraFar,
|
|
96
100
|
uNumSamples: this.numBlurSamples,
|
|
97
101
|
uBlurRadius: this.blurRadius,
|
|
102
|
+
occlusionColor: this.tintColor._array.slice(0, 3),
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
|
|
@@ -119,7 +124,7 @@ class ContactShadowRenderer extends Renderer {
|
|
|
119
124
|
for (const key in shadowUniforms) {
|
|
120
125
|
this.shader.setUniform(key, shadowUniforms[key])
|
|
121
126
|
}
|
|
122
|
-
this.target.rect(0, 0, this.target.width,
|
|
127
|
+
this.target.rect(0, 0, this.target.width, this.target.height)
|
|
123
128
|
this.target.pop()
|
|
124
129
|
})
|
|
125
130
|
|
|
@@ -326,6 +331,7 @@ uniform float uFar;
|
|
|
326
331
|
uniform float uIntensity;
|
|
327
332
|
uniform int uNumSamples;
|
|
328
333
|
uniform float uBlurRadius;
|
|
334
|
+
uniform vec3 occlusionColor;
|
|
329
335
|
|
|
330
336
|
#ifdef IS_WEBGL2
|
|
331
337
|
#define texFn texture
|
|
@@ -370,7 +376,7 @@ void main() {
|
|
|
370
376
|
total += weight;
|
|
371
377
|
}
|
|
372
378
|
occlusion /= total;
|
|
373
|
-
vec4 mixedColor = vec4(color.rgb
|
|
379
|
+
vec4 mixedColor = vec4(mix(color.rgb, occlusionColor, (1. - occlusion) * uIntensity), color.a);
|
|
374
380
|
#ifdef IS_WEBGL2
|
|
375
381
|
outColor = mixedColor;
|
|
376
382
|
#else
|
package/README.md
CHANGED
|
@@ -13,12 +13,12 @@ Add the library to your source code, *after* loading p5 but *before* loading you
|
|
|
13
13
|
|
|
14
14
|
### Via CDN
|
|
15
15
|
```html
|
|
16
|
-
<script src="https://cdn.jsdelivr.net/npm/@davepagurek/p5.filterrenderer@0.0.
|
|
16
|
+
<script src="https://cdn.jsdelivr.net/npm/@davepagurek/p5.filterrenderer@0.0.15/p5.filterRenderer.min.js"></script>
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
On OpenProcessing, paste this link into a new library slot:
|
|
20
20
|
```
|
|
21
|
-
https://cdn.jsdelivr.net/npm/@davepagurek/p5.filterrenderer@0.0.
|
|
21
|
+
https://cdn.jsdelivr.net/npm/@davepagurek/p5.filterrenderer@0.0.15/p5.filterRenderer.min.js
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
### Self-hosted
|
|
@@ -240,6 +240,9 @@ Methods on `ContactShadowRenderer`:
|
|
|
240
240
|
- Control how close together objects need to be for them to cast shadows
|
|
241
241
|
- This is defined in *world space,* meaning all transformations are applied when checking distances
|
|
242
242
|
- Defaults to 100
|
|
243
|
+
- `ContactShadowRenderer.prototype.setTintColor(color)`
|
|
244
|
+
- This is the color that the original render will be mixed with when there are shadows
|
|
245
|
+
- Defaults to black
|
|
243
246
|
|
|
244
247
|
A live example: https://davepagurek.github.io/p5.Framebuffer/examples/shadows
|
|
245
248
|
|
package/p5.filterRenderer.min.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
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.shader(this.shader);for(const t in e)this.shader.setUniform(t,e[t]);this.target.plane(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
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
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.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.plane(this.target.width,this.target.height),this.target.pop()})),this.target.noStroke(),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.plane(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.shader(this.blurShader);for(const e in n)this.blurShader.setUniform(e,n[e]);this.target.plane(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`;
|
|
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,this.tintColor=this.target.color(0)}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}setTintColor(e){this.tintColor=this.target.color(e)}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,occlusionColor:this.tintColor._array.slice(0,3)}}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.shader(this.blurShader);for(const e in n)this.blurShader.setUniform(e,n[e]);this.target.plane(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;\nuniform vec3 occlusionColor;\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(mix(color.rgb, occlusionColor, (1. - occlusion) * uIntensity), color.a);\n#ifdef IS_WEBGL2\n outColor = mixedColor;\n#else\n gl_FragColor = mixedColor;\n#endif\n}\n`;
|