Skip to content

Commit 347c052

Browse files
authored
Merge pull request #12 from davepagurek/feat/renderers
Add shadow and blur render helpers
2 parents bc4a08a + bdff0a4 commit 347c052

File tree

12 files changed

+560
-72
lines changed

12 files changed

+560
-72
lines changed

examples/blur/blur.frag renamed to BlurRenderer.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,73 @@
1+
class BlurRenderer extends Renderer {
2+
constructor(target) {
3+
super(target)
4+
this.focus = (target.height / 2) / tan(PI / 6)
5+
this.intensity = 0.05
6+
this.numSamples = 15
7+
}
8+
9+
vert() {
10+
return BlurRenderer.vert
11+
}
12+
13+
frag() {
14+
return BlurRenderer.frag
15+
}
16+
17+
focusHere() {
18+
const matrix = new DOMMatrix(this.target._renderer.uMVMatrix.mat4)
19+
const center = new DOMPoint(0, 0, 0)
20+
const world = center.matrixTransform(matrix)
21+
this.focus = -world.z
22+
}
23+
24+
setIntensity(intensity) {
25+
this.intensity = intensity
26+
}
27+
28+
setSamples(numSamples) {
29+
this.numSamples = numSamples
30+
}
31+
32+
getUniforms() {
33+
return {
34+
uImg: this.fbo.color,
35+
uDepth: this.fbo.depth,
36+
uSize: [this.target.width, this.target.height],
37+
uIntensity: this.intensity,
38+
uNumSamples: this.numSamples,
39+
uNear: this.target._renderer._curCamera._near,
40+
uFar: this.target._renderer._curCamera._far,
41+
uTargetZ: this.focus,
42+
}
43+
}
44+
}
45+
46+
p5.prototype.createBlurRenderer = function() {
47+
return new BlurRenderer(this)
48+
}
49+
50+
BlurRenderer.vert = `
51+
precision highp float;
52+
53+
attribute vec3 aPosition;
54+
attribute vec3 aNormal;
55+
attribute vec2 aTexCoord;
56+
57+
uniform mat4 uModelViewMatrix;
58+
uniform mat4 uProjectionMatrix;
59+
uniform mat3 uNormalMatrix;
60+
61+
varying highp vec2 vVertTexCoord;
62+
63+
void main(void) {
64+
vec4 positionVec4 = vec4(aPosition, 1.0);
65+
gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
66+
vVertTexCoord = aTexCoord;
67+
}
68+
`
69+
70+
BlurRenderer.frag = `
171
precision highp float;
272
varying highp vec2 vVertTexCoord;
373
@@ -52,3 +122,4 @@ void main() {
52122
color /= total;
53123
gl_FragColor = color;
54124
}
125+
`

ContactShadowRenderer.js

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
class ContactShadowRenderer extends Renderer {
2+
constructor(target) {
3+
super(target)
4+
this.target._renderer.GL.getExtension('OES_standard_derivatives')
5+
this.intensity = 0.5
6+
this.numSamples = 15
7+
this.exponent = 250
8+
this.bias = 1.
9+
this.searchRadius = 100
10+
}
11+
12+
vert() {
13+
return ContactShadowRenderer.vert
14+
}
15+
16+
frag() {
17+
return ContactShadowRenderer.frag
18+
}
19+
20+
setIntensity(intensity) {
21+
this.intensity = intensity
22+
}
23+
setSamples(numSamples) {
24+
this.numSamples = numSamples
25+
}
26+
setExponent(exponent) {
27+
this.exponent = exponent
28+
}
29+
setBias(bias) {
30+
this.bias = bias
31+
}
32+
setSearchRadius(radius) {
33+
this.searchRadius = radius
34+
}
35+
36+
getUniforms() {
37+
const projInfo = [
38+
-2 / (this.target.width * this.target._renderer.uPMatrix.mat4[0]),
39+
-2 / (this.target.height * this.target._renderer.uPMatrix.mat4[5]),
40+
(1 - this.target._renderer.uPMatrix.mat4[2]) / this.target._renderer.uPMatrix.mat4[0],
41+
(1 + this.target._renderer.uPMatrix.mat4[6]) / this.target._renderer.uPMatrix.mat4[5]
42+
]
43+
44+
return {
45+
uImg: this.fbo.color,
46+
uDepth: this.fbo.depth,
47+
uSize: [this.target.width, this.target.height],
48+
uIntensity: this.intensity,
49+
uNumSamples: this.numSamples,
50+
uNear: this.target._renderer._curCamera.cameraNear,
51+
uFar: this.target._renderer._curCamera.cameraFar,
52+
uProjInfo: projInfo,
53+
uExponent: this.exponent,
54+
uBias: this.bias,
55+
uSearchRadius: this.searchRadius,
56+
}
57+
}
58+
}
59+
60+
p5.prototype.createContactShadowRenderer = function() {
61+
return new ContactShadowRenderer(this)
62+
}
63+
64+
ContactShadowRenderer.vert = `
65+
attribute vec3 aPosition;
66+
attribute vec3 aNormal;
67+
attribute vec2 aTexCoord;
68+
69+
uniform mat4 uModelViewMatrix;
70+
uniform mat4 uProjectionMatrix;
71+
uniform mat3 uNormalMatrix;
72+
73+
varying highp vec2 vVertTexCoord;
74+
75+
void main(void) {
76+
vec4 positionVec4 = vec4(aPosition, 1.0);
77+
gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
78+
vVertTexCoord = aTexCoord;
79+
}
80+
`
81+
82+
ContactShadowRenderer.frag = `
83+
#extension GL_OES_standard_derivatives : enable
84+
precision highp float;
85+
varying highp vec2 vVertTexCoord;
86+
87+
uniform sampler2D uImg;
88+
uniform sampler2D uDepth;
89+
uniform vec2 uSize;
90+
uniform int uNumSamples;
91+
uniform float uNear;
92+
uniform float uFar;
93+
uniform vec4 uProjInfo;
94+
uniform float uSearchRadius;
95+
uniform float uIntensity;
96+
uniform float uExponent;
97+
uniform float uBias;
98+
99+
const int MAX_NUM_SAMPLES = 50;
100+
101+
float rand(vec2 co){
102+
return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453);
103+
}
104+
105+
vec3 worldFromScreen(vec2 offset) {
106+
float z = uNear * uFar / ((uNear - uFar) * texture2D(uDepth, vVertTexCoord + offset).x + uFar);
107+
return vec3((((vVertTexCoord + offset) * uSize) * uProjInfo.xy + uProjInfo.zw) * z, z);
108+
}
109+
110+
vec2 screenFromWorld(vec3 world) {
111+
return (world.xy/world.z - uProjInfo.zw)/uProjInfo.xy;
112+
}
113+
114+
const float EPSILON = 0.01;
115+
116+
mat4 axisAngleRotation(vec3 axis, float angle) {
117+
axis = normalize(axis);
118+
float s = sin(angle);
119+
float c = cos(angle);
120+
float oc = 1.0 - c;
121+
122+
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,
123+
oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,
124+
oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,
125+
0.0, 0.0, 0.0, 1.0);
126+
}
127+
vec3 adjustNormal(
128+
vec3 origNormal,
129+
vec3 displacementNormal,
130+
vec3 noDisplacementNormal
131+
) {
132+
// Find the rotation induced by the displacement
133+
float angle = acos(dot(displacementNormal, noDisplacementNormal));
134+
vec3 rawAxis = cross(displacementNormal, noDisplacementNormal);
135+
if (length(rawAxis) < 0.01) {
136+
return origNormal;
137+
}
138+
vec3 axis = normalize(rawAxis);
139+
mat4 rotation = axisAngleRotation(axis, angle);
140+
141+
// Apply the rotation to the original normal
142+
vec3 normal = (rotation * vec4(origNormal, 0.)).xyz;
143+
return normal;
144+
}
145+
146+
void main() {
147+
vec4 color = texture2D(uImg, vVertTexCoord);
148+
vec3 position = worldFromScreen(vec2(0., 0.));
149+
vec3 normal = normalize(cross(dFdx(position), dFdy(position)));
150+
151+
float radiusSquared = uSearchRadius * uSearchRadius;
152+
153+
float occlusion = 0.;
154+
155+
for (int i = 0; i < MAX_NUM_SAMPLES; i++) {
156+
if (i >= uNumSamples) break;
157+
float t = (float(i + 1) / float(uNumSamples));
158+
159+
// Sample a sort of random ish coordinate in a half sphere pointing up
160+
float phi = t * 11. * ${2 * Math.PI} + 0.5*rand(gl_FragCoord.xy);
161+
float theta = t * ${Math.PI / 2} + 0.5*rand(gl_FragCoord.xy);
162+
float radius = 1.0 - t*t;
163+
vec3 localOff = vec3(
164+
radius * cos(phi) * sin(theta),
165+
radius * cos(theta),
166+
radius * sin(phi) * sin(theta)
167+
);
168+
169+
// Translate that to be a hemisphere oriented with the surface normal
170+
vec3 rotatedOff = adjustNormal(localOff, normal, vec3(0., 1., 0.));
171+
vec3 testPosition = position + rotatedOff * uSearchRadius;
172+
vec2 screenPosition = screenFromWorld(testPosition);
173+
vec2 offset = screenPosition / uSize - vVertTexCoord;
174+
175+
// At that screen space coordinate, what is the position of the object we see?
176+
vec3 samplePos = worldFromScreen(offset);
177+
178+
// The amount of occlusion is proportional to the *cosine* of the angle between
179+
// the line connecting the object to the surface and the surface normal. This is
180+
// because light coming in at an angle is more spread out and thus delivers less
181+
// energy to the surface.
182+
//
183+
// The dot product of originToSample and the normal is proportional to this energy
184+
// because dot(a, b) is equivalent to length(a)*length(b)*cos(angle_between_a_and_b)
185+
vec3 originToSample = samplePos - position;
186+
float squaredDistanceToSample = dot(originToSample, originToSample);
187+
float vn = dot(originToSample, normal) - uBias;
188+
189+
// We only let stuff start making a shadow when it's within our search radius. At
190+
// the edge it should not occlude, and as it gets closer, it should occlude more.
191+
// We'll give it a cubic falloff so it looks smoother.
192+
float f = max(radiusSquared - squaredDistanceToSample, 0.0) / radiusSquared;
193+
float sampleOcclusion = f * f * f * max(vn / (EPSILON + squaredDistanceToSample), 0.0);
194+
195+
occlusion += sampleOcclusion;
196+
}
197+
occlusion = 1.0 - (occlusion / float(uNumSamples));
198+
occlusion = clamp(pow(occlusion, 1.0 + uExponent), 0.0, 1.0);
199+
gl_FragColor = vec4(color.rgb * mix(1., occlusion, uIntensity), color.a);
200+
}
201+
`

0 commit comments

Comments
 (0)