Setting the file. One moment. Shadows · Code Slice Hero · heygen-com/hyperframes · Skills Docsshadows.js
shadows.js
JavaScript·299 lines·10 KB
=
Math.
max
(
64
, tiles[
0
].width);
7 const states = tiles.map(() => ({
8 c: new Float64Array(3),
9 u: new Float64Array(3),
10 v: new Float64Array(3),
11 n: new Float64Array(3),
12 q: new Float64Array(4),
13 }));
14 const buckets = new Map(),
15 usedBuckets = [],
16 seen = new Int32Array(tiles.length);
17 let stamp = 0,
18 count = 0,
19 storage = new Float32Array(Math.max(64, tiles.length * 8) * stride),
20 capacity = 0;
21 const stats = { pairs: 0, shadows: 0 };
22 const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
23 const shaders = [];
24 function compile(kind, source) {
25 const s = gl.createShader(kind);
26 gl.shaderSource(s, source);
27 gl.compileShader(s);
28 if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
29 shaders.push(s);
30 return s;
31 }
32 const program = gl.createProgram();
33 gl.attachShader(
34 program,
35 compile(
36 gl.VERTEX_SHADER,
37 `#version 300 es
38 precision highp float;
39 layout(location=0) in vec2 aCorner;
40 layout(location=1) in vec3 aCenter;
41 layout(location=2) in vec3 aU;
42 layout(location=3) in vec3 aV;
43 layout(location=4) in vec3 aReceiver;
44 layout(location=5) in vec3 aReceiverU;
45 layout(location=6) in vec3 aReceiverV;
46 layout(location=7) in vec4 aBounds;
47 layout(location=8) in vec4 aShape;
48 layout(location=9) in vec3 aRay;
49 out vec2 vCasterUV;
50 out vec2 vReceiverXY;
51 out float vRay;
52 flat out vec4 vBounds;
53 flat out vec4 vShape;
54 void main(){
55 vec2 uv=aCorner*(1.0+1.5*aShape.z/aShape.xy);
56 vec3 p=aCenter+aU*uv.x+aV*uv.y;
57 float w=1.0-p.z/1712.661;
58 gl_Position=vec4((p.x-960.0)/960.0,-(p.y-540.0)/540.0,1.00020002*w-0.00116788,w);
59 vCasterUV=uv;vReceiverXY=vec2(dot(p-aReceiver,aReceiverU),dot(p-aReceiver,aReceiverV));
60 vRay=aRay.x+aRay.y*uv.x+aRay.z*uv.y;vBounds=aBounds;vShape=aShape;
61 }`,
62 ),
63 );
64 gl.attachShader(
65 program,
66 compile(
67 gl.FRAGMENT_SHADER,
68 `#version 300 es
69 precision highp float;
70 in vec2 vCasterUV;
71 in vec2 vReceiverXY;
72 in float vRay;
73 flat in vec4 vBounds;
74 flat in vec4 vShape;
75 out vec4 color;
76 void main(){
77 if(vRay<=0.2)discard;
78 // Clip to the actual rounded receiver face, never the revealed void.
79 vec2 q=abs(vReceiverXY)-(vBounds.xy-vBounds.z);
80 float receiverDistance=length(max(q,0.0))+min(max(q.x,q.y),0.0)-vBounds.z;
81 if(receiverDistance>=0.0)discard;
82 vec2 edge=(abs(vCasterUV)-1.0)*vShape.xy;
83 float distance=max(edge.x,edge.y);
84 float alpha=(1.0-smoothstep(-vShape.z,vShape.z,distance))*vBounds.w;
85 alpha*=smoothstep(0.2,3.0,vRay);
86 if(alpha<0.002)discard;
87 color=vec4(0.0,0.0,0.0,alpha);
88 }`,
89 ),
90 );
91 gl.linkProgram(program);
92 if (!gl.getProgramParameter(program, gl.LINK_STATUS))
93 throw new Error(gl.getProgramInfoLog(program));
94 const vao = gl.createVertexArray();
95 gl.bindVertexArray(vao);
96 const corners = gl.createBuffer();
97 gl.bindBuffer(gl.ARRAY_BUFFER, corners);
98 gl.bufferData(
99 gl.ARRAY_BUFFER,
100 new Float32Array([-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]),
101 gl.STATIC_DRAW,
102 );
103 gl.enableVertexAttribArray(0);
104 gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 8, 0);
105 const buffer = gl.createBuffer();
106 gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
107 for (const [loc, size, offset] of [
108 [1, 3, 0],
109 [2, 3, 3],
110 [3, 3, 6],
111 [4, 3, 9],
112 [5, 3, 12],
113 [6, 3, 15],
114 [7, 4, 18],
115 [8, 4, 22],
116 [9, 3, 26],
117 ]) {
118 gl.enableVertexAttribArray(loc);
119 gl.vertexAttribPointer(loc, size, gl.FLOAT, false, stride * 4, offset * 4);
120 gl.vertexAttribDivisor(loc, 1);
121 }
122 function prepare() {
123 for (const bucket of usedBuckets) bucket.length = 0;
124 usedBuckets.length = 0;
125 for (let i = 0; i < tiles.length; i++) {
126 const t = tiles[i],
127 s = states[i],
128 k = i * 14;
129 const cx = Math.cos(poses[k + 8]),
130 sx = Math.sin(poses[k + 8]),
131 cy = Math.cos(poses[k + 9]),
132 sy = Math.sin(poses[k + 9]),
133 cz = Math.cos(poses[k + 10]),
134 sz = Math.sin(poses[k + 10]);
135 s.u.set([cy * cz, cx * sz + sx * sy * cz, sx * sz - cx * sy * cz]);
136 s.v.set([-cy * sz, cx * cz - sx * sy * sz, sx * cz + cx * sy * sz]);
137 s.n.set([sy, -sx * cy, cx * cy]);
138 s.c.set([t.cx + poses[k + 4], t.cy + poses[k + 5], poses[k + 6]]);
139 s.w = (t.width * poses[k + 7]) / 2;
140 s.h = (t.height * poses[k + 7]) / 2;
141 s.f = poses[k + 11];
142 // The visible face is also the depth-tested shadow receiver.
143 const front =
144 s.n[0] * (960 - s.c[0]) + s.n[1] * (540 - s.c[1]) + s.n[2] * (1712.661 - s.c[2]) >= 0
145 ? 1
146 : -1;
147 const thickness = 1.5 * s.f * poses[k + 7] * front;
148 for (let a = 0; a < 3; a++) s.c[a] += s.n[a] * thickness;
149 const qx = s.c[0] + light[0] * s.c[2],
150 qy = s.c[1] + light[1] * s.c[2];
151 const ux = (s.u[0] + light[0] * s.u[2]) * s.w,
152 uy = (s.u[1] + light[1] * s.u[2]) * s.w;
153 const vx = (s.v[0] + light[0] * s.v[2]) * s.h,
154 vy = (s.v[1] + light[1] * s.v[2]) * s.h;
155 s.q.set([
156 qx - Math.abs(ux) - Math.abs(vx),
157 qy - Math.abs(uy) - Math.abs(vy),
158 qx + Math.abs(ux) + Math.abs(vx),
159 qy + Math.abs(uy) + Math.abs(vy),
160 ]);
161 for (let y = Math.floor(s.q[1] / bucketSize); y <= Math.floor(s.q[3] / bucketSize); y++)
162 for (let x = Math.floor(s.q[0] / bucketSize); x <= Math.floor(s.q[2] / bucketSize); x++) {
163 const key = x + "," + y;
164 let bucket = buckets.get(key);
165 if (!bucket) {
166 bucket = [];
167 buckets.set(key, bucket);
168 }
169 if (!bucket.length) usedBuckets.push(bucket);
170 bucket.push(i);
171 }
172 }
173 }
174 const delta = new Float64Array(3),
175 center = new Float64Array(3),
176 u = new Float64Array(3),
177 v = new Float64Array(3);
178 function pair(c, r) {
179 stats.pairs++;
180 const denom = dot(r.n, light);
181 if (Math.abs(denom) < 0.12) return;
182 for (let a = 0; a < 3; a++) delta[a] = r.c[a] - c.c[a];
183 const ray = dot(delta, r.n) / denom,
184 ru = (-dot(c.u, r.n) * c.w) / denom,
185 rv = (-dot(c.v, r.n) * c.h) / denom;
186 if (ray + Math.abs(ru) + Math.abs(rv) < 0.5) return;
187 for (let a = 0; a < 3; a++) {
188 center[a] = c.c[a] + light[a] * ray;
189 u[a] = c.u[a] * c.w + light[a] * ru;
190 v[a] = c.v[a] * c.h + light[a] * rv;
191 delta[a] = center[a] - r.c[a];
192 }
193 const blur = softness * (1 + Math.max(0, ray) * 0.035);
194 const rx = dot(delta, r.u),
195 ry = dot(delta, r.v);
196 if (
197 Math.abs(rx) > r.w + Math.abs(dot(u, r.u)) + Math.abs(dot(v, r.u)) + blur * 2 ||
198 Math.abs(ry) > r.h + Math.abs(dot(u, r.v)) + Math.abs(dot(v, r.v)) + blur * 2
199 )
200 return;
201 const crossX = u[1] * v[2] - u[2] * v[1],
202 crossY = u[2] * v[0] - u[0] * v[2],
203 crossZ = u[0] * v[1] - u[1] * v[0];
204 const area = Math.hypot(crossX, crossY, crossZ),
205 altU = area / Math.hypot(...v),
206 altV = area / Math.hypot(...u);
207 if (!Number.isFinite(altU + altV) || altU < 0.2 || altV < 0.2) return;
208 if ((count + 1) * stride > storage.length) {
209 const larger = new Float32Array(storage.length * 2);
210 larger.set(storage);
211 storage = larger;
212 }
213 const offset = count++ * stride;
214 storage.set(center, offset);
215 storage.set(u, offset + 3);
216 storage.set(v, offset + 6);
217 storage.set(r.c, offset + 9);
218 storage.set(r.u, offset + 12);
219 storage.set(r.v, offset + 15);
220 storage.set(
221 [
222 r.w,
223 r.h,
224 Math.min(r.w, r.h) * 0.12 * r.f,
225 strength,
226 altU,
227 altV,
228 Math.max(0.6, blur),
229 0,
230 ray,
231 ru,
232 rv,
233 ],
234 offset + 18,
235 );
236 }
237 return {
238 stats,
239 draw() {
240 stats.pairs = 0;
241 stats.shadows = 0;
242 count = 0;
243 if (strength <= 0) return 0;
244 prepare();
245 for (let i = 0; i < states.length; i++) {
246 const c = states[i];
247 if (c.f < 0.002) continue;
248 const pad = softness * (1 + (Math.abs(c.c[2]) + c.w + c.h) * 0.035) * 2;
249 stamp++;
250 for (
251 let y = Math.floor((c.q[1] - pad) / bucketSize);
252 y <= Math.floor((c.q[3] + pad) / bucketSize);
253 y++
254 )
255 for (
256 let x = Math.floor((c.q[0] - pad) / bucketSize);
257 x <= Math.floor((c.q[2] + pad) / bucketSize);
258 x++
259 ) {
260 const bucket = buckets.get(x + "," + y);
261 if (!bucket) continue;
262 for (const j of bucket) {
263 if (j === i || seen[j] === stamp) continue;
264 seen[j] = stamp;
265 pair(c, states[j]);
266 }
267 }
268 }
269 stats.shadows = count;
270 if (!count) return 0;
271 gl.useProgram(program);
272 gl.bindVertexArray(vao);
273 gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
274 if (capacity < storage.byteLength) {
275 capacity = storage.byteLength;
276 gl.bufferData(gl.ARRAY_BUFFER, capacity, gl.DYNAMIC_DRAW);
277 }
278 gl.bufferSubData(gl.ARRAY_BUFFER, 0, storage.subarray(0, count * stride));
279 gl.depthMask(false);
280 gl.depthFunc(gl.LEQUAL);
281 gl.disable(gl.CULL_FACE);
282 gl.enable(gl.POLYGON_OFFSET_FILL);
283 gl.polygonOffset(-1, -1);
284 gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, count);
285 gl.disable(gl.POLYGON_OFFSET_FILL);
286 gl.enable(gl.CULL_FACE);
287 gl.depthMask(true);
288 gl.depthFunc(gl.LESS);
289 return 1;
290 },
291 dispose() {
292 shaders.forEach((s) => gl.deleteShader(s));
293 gl.deleteBuffer(buffer);
294 gl.deleteBuffer(corners);
295 gl.deleteVertexArray(vao);
296 gl.deleteProgram(program);
297 },
298 };
299};