@driftengine/core 4.4.0 → 4.4.2
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/CHANGELOG.json +47 -0
- package/README.md +1 -1
- package/dist/render/ambientOcclusionPass.js +5 -1
- package/dist/render/backend/webgl2/renderer.d.ts +3 -0
- package/dist/render/backend/webgl2/renderer.js +13 -0
- package/dist/render/backend/webgpu/postPass.d.ts +20 -4
- package/dist/render/backend/webgpu/postPass.js +17 -4
- package/dist/render/backend/webgpu/renderer.d.ts +10 -0
- package/dist/render/backend/webgpu/renderer.js +74 -7
- package/dist/render/flameLight.js +15 -2
- package/dist/render/pointShadowBudget.d.ts +12 -2
- package/dist/render/pointShadowBudget.js +67 -51
- package/dist/render/recon/disocclusion.d.ts +36 -3
- package/dist/render/recon/disocclusion.js +56 -4
- package/dist/render/recon/resolve.js +95 -12
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -1
- package/dist/render/shaders/ambientOcclusion.d.ts +39 -1
- package/dist/render/shaders/ambientOcclusion.js +69 -9
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +8 -3
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +8 -3
- package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +31 -6
- package/dist/render/shaders/generated/temporalResolve.wgsl.js +31 -6
- package/dist/render/shaders/recon/resolve.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/resolve.wgsl.js +112 -8
- package/dist/render/shaders/temporalResolve.js +133 -5
- package/dist/render/temporalAa.d.ts +145 -0
- package/dist/render/temporalAa.js +190 -0
- package/dist/render/temporalPass.d.ts +16 -14
- package/dist/render/temporalPass.js +59 -14
- package/package.json +6 -6
- package/src/render/ambientOcclusionPass.ts +5 -1
- package/src/render/backend/webgl2/renderer.ts +21 -1
- package/src/render/backend/webgpu/postPass.ts +34 -4
- package/src/render/backend/webgpu/renderer.ts +77 -7
- package/src/render/flameLight.ts +15 -2
- package/src/render/pointShadowBudget.ts +65 -47
- package/src/render/recon/disocclusion.ts +54 -3
- package/src/render/recon/resolve.ts +137 -10
- package/src/render/sceneTarget.ts +8 -1
- package/src/render/shaders/ambientOcclusion.ts +81 -9
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +8 -3
- package/src/render/shaders/generated/temporalResolve.wgsl.ts +31 -6
- package/src/render/shaders/recon/resolve.wgsl.ts +112 -8
- package/src/render/shaders/temporalResolve.ts +141 -5
- package/src/render/temporalAa.ts +227 -0
- package/src/render/temporalPass.ts +88 -12
- package/dist/render/shaders/agx.d.ts +0 -31
- package/dist/render/shaders/agx.js +0 -81
|
@@ -15,6 +15,9 @@ import { TEMPORAL_RESOLVE_FRAG } from './shaders/temporalResolve.ts';
|
|
|
15
15
|
* grade all read the scene texture, and reading the unresolved one would grade a stable picture
|
|
16
16
|
* from an unstable source and put the crawl back on screen through the bloom.
|
|
17
17
|
*/
|
|
18
|
+
/** The resolve's three outputs: the picture and the two halves of the flicker record. */
|
|
19
|
+
const DRAW_ALL = [0x8ce0, 0x8ce1, 0x8ce2]; // COLOR_ATTACHMENT0, 1, 2
|
|
20
|
+
|
|
18
21
|
export class TemporalPass {
|
|
19
22
|
private readonly program: WebGLProgram;
|
|
20
23
|
private readonly uniforms: Record<string, WebGLUniformLocation | null>;
|
|
@@ -22,6 +25,21 @@ export class TemporalPass {
|
|
|
22
25
|
|
|
23
26
|
/** The pair, and which of them the next resolve writes into. */
|
|
24
27
|
private textures: (WebGLTexture | null)[] = [null, null];
|
|
28
|
+
/**
|
|
29
|
+
* Each pixel's flicker record, a pair beside the history's and swapped with it: see the
|
|
30
|
+
* anti-flicker in `temporalAa.ts`. In the history's own format, which holds what the record does —
|
|
31
|
+
* a change and a sample are colours' luma, and eight bits hold them wherever eight bits hold the
|
|
32
|
+
* colour.
|
|
33
|
+
*/
|
|
34
|
+
private records: (WebGLTexture | null)[] = [null, null];
|
|
35
|
+
/** The rest of each pixel's record — depth, pull and recent swing — beside the first. */
|
|
36
|
+
private motions: (WebGLTexture | null)[] = [null, null];
|
|
37
|
+
/**
|
|
38
|
+
* Whether the records can be half float, which the depth in them needs. Asked of the context
|
|
39
|
+
* rather than taken from the scene's format, since a record of an eight-bit scene still holds
|
|
40
|
+
* metres. Without it the anti-flicker is off and the resolve is the one before it existed.
|
|
41
|
+
*/
|
|
42
|
+
private readonly recordFloat: boolean;
|
|
25
43
|
private framebuffers: (WebGLFramebuffer | null)[] = [null, null];
|
|
26
44
|
private write = 0;
|
|
27
45
|
|
|
@@ -40,6 +58,7 @@ export class TemporalPass {
|
|
|
40
58
|
*/
|
|
41
59
|
private readonly floatColor: boolean,
|
|
42
60
|
) {
|
|
61
|
+
this.recordFloat = gl.getExtension('EXT_color_buffer_float') !== null;
|
|
43
62
|
this.program = compileProgram(gl, FULLSCREEN_VERT, TEMPORAL_RESOLVE_FRAG, 'temporalResolve');
|
|
44
63
|
this.uniforms = uniformLocations(gl, this.program, 'temporalPass');
|
|
45
64
|
const vao = gl.createVertexArray();
|
|
@@ -55,19 +74,45 @@ export class TemporalPass {
|
|
|
55
74
|
|
|
56
75
|
for (let i = 0; i < 2; i++) {
|
|
57
76
|
if (this.textures[i] === null) this.textures[i] = gl.createTexture();
|
|
58
|
-
|
|
59
|
-
if (this.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
if (this.records[i] === null) this.records[i] = gl.createTexture();
|
|
78
|
+
if (this.motions[i] === null) this.motions[i] = gl.createTexture();
|
|
79
|
+
for (const texture of [this.textures[i], this.records[i], this.motions[i]]) {
|
|
80
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
81
|
+
if (texture === this.textures[i] ? this.floatColor : this.recordFloat) {
|
|
82
|
+
/* The depth record is two numbers a pixel: metres, and how far they swing. */
|
|
83
|
+
const pair = texture === this.motions[i];
|
|
84
|
+
gl.texImage2D(
|
|
85
|
+
gl.TEXTURE_2D,
|
|
86
|
+
0,
|
|
87
|
+
pair ? gl.RG16F : gl.RGBA16F,
|
|
88
|
+
width,
|
|
89
|
+
height,
|
|
90
|
+
0,
|
|
91
|
+
pair ? gl.RG : gl.RGBA,
|
|
92
|
+
gl.HALF_FLOAT,
|
|
93
|
+
null,
|
|
94
|
+
);
|
|
95
|
+
} else {
|
|
96
|
+
gl.texImage2D(
|
|
97
|
+
gl.TEXTURE_2D,
|
|
98
|
+
0,
|
|
99
|
+
gl.RGBA,
|
|
100
|
+
width,
|
|
101
|
+
height,
|
|
102
|
+
0,
|
|
103
|
+
gl.RGBA,
|
|
104
|
+
gl.UNSIGNED_BYTE,
|
|
105
|
+
null,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
/* Linear, because the history is sampled between texels wherever the reprojection lands;
|
|
109
|
+
clamped, because a sample that wrapped would pull the far edge of the screen into the
|
|
110
|
+
corner — and the shader rejects an off-screen reprojection before it can anyway. */
|
|
111
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
112
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
113
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
114
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
63
115
|
}
|
|
64
|
-
/* Linear, because the history is sampled between texels wherever the reprojection lands;
|
|
65
|
-
clamped, because a sample that wrapped would pull the far edge of the screen into the
|
|
66
|
-
corner — and the shader rejects an off-screen reprojection before it can anyway. */
|
|
67
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
68
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
69
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
70
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
71
116
|
|
|
72
117
|
if (this.framebuffers[i] === null) this.framebuffers[i] = gl.createFramebuffer();
|
|
73
118
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffers[i]);
|
|
@@ -78,6 +123,22 @@ export class TemporalPass {
|
|
|
78
123
|
this.textures[i],
|
|
79
124
|
0,
|
|
80
125
|
);
|
|
126
|
+
gl.framebufferTexture2D(
|
|
127
|
+
gl.FRAMEBUFFER,
|
|
128
|
+
gl.COLOR_ATTACHMENT1,
|
|
129
|
+
gl.TEXTURE_2D,
|
|
130
|
+
this.records[i],
|
|
131
|
+
0,
|
|
132
|
+
);
|
|
133
|
+
gl.framebufferTexture2D(
|
|
134
|
+
gl.FRAMEBUFFER,
|
|
135
|
+
gl.COLOR_ATTACHMENT2,
|
|
136
|
+
gl.TEXTURE_2D,
|
|
137
|
+
this.motions[i],
|
|
138
|
+
0,
|
|
139
|
+
);
|
|
140
|
+
/* Per framebuffer, so set once here: the resolve writes the picture and the record. */
|
|
141
|
+
gl.drawBuffers(DRAW_ALL);
|
|
81
142
|
}
|
|
82
143
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
83
144
|
}
|
|
@@ -96,6 +157,8 @@ export class TemporalPass {
|
|
|
96
157
|
width: number,
|
|
97
158
|
height: number,
|
|
98
159
|
blend: number,
|
|
160
|
+
periodStart: boolean,
|
|
161
|
+
depthToViewZ: Float32Array,
|
|
99
162
|
): WebGLTexture {
|
|
100
163
|
const { gl } = this;
|
|
101
164
|
this.ensureSize(width, height);
|
|
@@ -120,10 +183,19 @@ export class TemporalPass {
|
|
|
120
183
|
gl.activeTexture(gl.TEXTURE2);
|
|
121
184
|
gl.bindTexture(gl.TEXTURE_2D, depth);
|
|
122
185
|
gl.uniform1i(this.uniforms['uDepth'] ?? null, 2);
|
|
186
|
+
gl.activeTexture(gl.TEXTURE3);
|
|
187
|
+
gl.bindTexture(gl.TEXTURE_2D, this.records[history]);
|
|
188
|
+
gl.uniform1i(this.uniforms['uFlicker'] ?? null, 3);
|
|
189
|
+
gl.activeTexture(gl.TEXTURE4);
|
|
190
|
+
gl.bindTexture(gl.TEXTURE_2D, this.motions[history]);
|
|
191
|
+
gl.uniform1i(this.uniforms['uFlickerMotion'] ?? null, 4);
|
|
192
|
+
gl.uniform4fv(this.uniforms['uDepthToViewZ'] ?? null, depthToViewZ);
|
|
193
|
+
gl.uniform1f(this.uniforms['uAntiFlicker'] ?? null, this.recordFloat ? 1 : 0);
|
|
123
194
|
|
|
124
195
|
gl.uniformMatrix4fv(this.uniforms['uReprojection'] ?? null, false, reprojection);
|
|
125
196
|
gl.uniform2f(this.uniforms['uTexel'] ?? null, 1 / width, 1 / height);
|
|
126
197
|
gl.uniform1f(this.uniforms['uHistoryBlend'] ?? null, blend);
|
|
198
|
+
gl.uniform1f(this.uniforms['uPeriodStart'] ?? null, periodStart ? 1 : 0);
|
|
127
199
|
|
|
128
200
|
gl.bindVertexArray(this.vao);
|
|
129
201
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
@@ -141,8 +213,12 @@ export class TemporalPass {
|
|
|
141
213
|
const { gl } = this;
|
|
142
214
|
for (let i = 0; i < 2; i++) {
|
|
143
215
|
if (this.textures[i] !== null) gl.deleteTexture(this.textures[i]);
|
|
216
|
+
if (this.records[i] !== null) gl.deleteTexture(this.records[i]);
|
|
217
|
+
if (this.motions[i] !== null) gl.deleteTexture(this.motions[i]);
|
|
144
218
|
if (this.framebuffers[i] !== null) gl.deleteFramebuffer(this.framebuffers[i]);
|
|
145
219
|
this.textures[i] = null;
|
|
220
|
+
this.records[i] = null;
|
|
221
|
+
this.motions[i] = null;
|
|
146
222
|
this.framebuffers[i] = null;
|
|
147
223
|
}
|
|
148
224
|
gl.deleteProgram(this.program);
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgX: a tone curve whose shade stays open, for scenes lit mostly by bounce.
|
|
3
|
-
*
|
|
4
|
-
* **What ACES does to a courtyard.** The reference RRT and ODT are graded for a dark cinema, and
|
|
5
|
-
* their toe is steep: a surface at 1% of white leaves the curve at 4 of 255. A room lit by light
|
|
6
|
-
* bounced off its own walls lives in exactly that range, so its arcades read black beside renders
|
|
7
|
-
* of the same model, and the grime in its stone reads as ink. Measured on a bought courtyard: 39%
|
|
8
|
-
* of the frame below 16 of 255, against about 5% in the model maker's own renders.
|
|
9
|
-
*
|
|
10
|
-
* **AgX encodes the scene logarithmically and draws one sigmoid through it**, Troy Sobotka's
|
|
11
|
-
* construction as Blender, Filament and three.js ship it. The same 1% leaves at 18 of 255, middle
|
|
12
|
-
* grey lands at half the display range, and a highlight desaturates toward white instead of
|
|
13
|
-
* clipping to a hue. What it gives up is ACES's punch in the midtones: the same scene reads
|
|
14
|
-
* flatter, which a grade adds back as a look where ACES bakes it into the conversion. What would
|
|
15
|
-
* make it the wrong choice is a world graded against three.js's or Unreal's ACES, which is the
|
|
16
|
-
* match `aces` exists for.
|
|
17
|
-
*
|
|
18
|
-
* The constants are the Filament and three.js ones: Rec. 2020 primaries, the inset and outset
|
|
19
|
-
* matrices, an encoding from 2^-10 to 2^6.5 times middle grey, and the iolite polynomial fit of
|
|
20
|
-
* the default contrast curve, whose mean squared error against Blender's is 3.7e-6.
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* `agxFilmic(vec3)`: linear sRGB in, linear sRGB out, clamped to [0, 1]. Reads `uOutputExposure`,
|
|
24
|
-
* which the including source declares.
|
|
25
|
-
*/
|
|
26
|
-
export declare const AGX_GLSL = "\nvec3 agxContrast(vec3 x) {\n vec3 x2 = x * x;\n vec3 x4 = x2 * x2;\n return 15.5 * x4 * x2 - 40.14 * x4 * x + 31.96 * x4 - 6.868 * x2 * x + 0.4298 * x2 + 0.1191 * x - 0.00232;\n}\n\nvec3 agxFilmic(vec3 x) {\n x *= uOutputExposure;\n // Columns, as GLSL reads them: linear sRGB to linear Rec. 2020, then the inset.\n const mat3 SRGB_TO_REC2020 = mat3(\n 0.6274, 0.0691, 0.0164,\n 0.3293, 0.9195, 0.0880,\n 0.0433, 0.0113, 0.8956\n );\n const mat3 AGX_INSET = mat3(\n 0.856627153315983, 0.137318972929847, 0.11189821299995,\n 0.0951212405381588, 0.761241990602591, 0.0767994186031903,\n 0.0482516061458583, 0.101439036467562, 0.811302368396859\n );\n const mat3 AGX_OUTSET = mat3(\n 1.1271005818144368, -0.1413297634984383, -0.14132976349843826,\n -0.11060664309660323, 1.157823702216272, -0.11060664309660294,\n -0.016493938717834573, -0.016493938717834257, 1.2519364065950405\n );\n const mat3 REC2020_TO_SRGB = mat3(\n 1.6605, -0.1246, -0.0182,\n -0.5876, 1.1329, -0.1006,\n -0.0728, -0.0083, 1.1187\n );\n vec3 c = AGX_INSET * (SRGB_TO_REC2020 * x);\n /* log2 of middle grey times 2^-10 and 2^6.5: -12.47393 and 4.026069, 16.5 stops apart. */\n c = clamp((log2(max(c, vec3(1e-10))) + 12.47393) / 16.5, 0.0, 1.0);\n c = AGX_OUTSET * agxContrast(c);\n c = pow(max(c, vec3(0.0)), vec3(2.2));\n return clamp(REC2020_TO_SRGB * c, 0.0, 1.0);\n}\n";
|
|
27
|
-
/**
|
|
28
|
-
* The curve for a neutral grey, as the shader computes it: every matrix above keeps grey grey, so
|
|
29
|
-
* one channel is the whole of it. For a test to pin to hand-derived values.
|
|
30
|
-
*/
|
|
31
|
-
export declare function agxGrey(linear: number, exposure?: number): number;
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgX: a tone curve whose shade stays open, for scenes lit mostly by bounce.
|
|
3
|
-
*
|
|
4
|
-
* **What ACES does to a courtyard.** The reference RRT and ODT are graded for a dark cinema, and
|
|
5
|
-
* their toe is steep: a surface at 1% of white leaves the curve at 4 of 255. A room lit by light
|
|
6
|
-
* bounced off its own walls lives in exactly that range, so its arcades read black beside renders
|
|
7
|
-
* of the same model, and the grime in its stone reads as ink. Measured on a bought courtyard: 39%
|
|
8
|
-
* of the frame below 16 of 255, against about 5% in the model maker's own renders.
|
|
9
|
-
*
|
|
10
|
-
* **AgX encodes the scene logarithmically and draws one sigmoid through it**, Troy Sobotka's
|
|
11
|
-
* construction as Blender, Filament and three.js ship it. The same 1% leaves at 18 of 255, middle
|
|
12
|
-
* grey lands at half the display range, and a highlight desaturates toward white instead of
|
|
13
|
-
* clipping to a hue. What it gives up is ACES's punch in the midtones: the same scene reads
|
|
14
|
-
* flatter, which a grade adds back as a look where ACES bakes it into the conversion. What would
|
|
15
|
-
* make it the wrong choice is a world graded against three.js's or Unreal's ACES, which is the
|
|
16
|
-
* match `aces` exists for.
|
|
17
|
-
*
|
|
18
|
-
* The constants are the Filament and three.js ones: Rec. 2020 primaries, the inset and outset
|
|
19
|
-
* matrices, an encoding from 2^-10 to 2^6.5 times middle grey, and the iolite polynomial fit of
|
|
20
|
-
* the default contrast curve, whose mean squared error against Blender's is 3.7e-6.
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* `agxFilmic(vec3)`: linear sRGB in, linear sRGB out, clamped to [0, 1]. Reads `uOutputExposure`,
|
|
24
|
-
* which the including source declares.
|
|
25
|
-
*/
|
|
26
|
-
export const AGX_GLSL = `
|
|
27
|
-
vec3 agxContrast(vec3 x) {
|
|
28
|
-
vec3 x2 = x * x;
|
|
29
|
-
vec3 x4 = x2 * x2;
|
|
30
|
-
return 15.5 * x4 * x2 - 40.14 * x4 * x + 31.96 * x4 - 6.868 * x2 * x + 0.4298 * x2 + 0.1191 * x - 0.00232;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
vec3 agxFilmic(vec3 x) {
|
|
34
|
-
x *= uOutputExposure;
|
|
35
|
-
// Columns, as GLSL reads them: linear sRGB to linear Rec. 2020, then the inset.
|
|
36
|
-
const mat3 SRGB_TO_REC2020 = mat3(
|
|
37
|
-
0.6274, 0.0691, 0.0164,
|
|
38
|
-
0.3293, 0.9195, 0.0880,
|
|
39
|
-
0.0433, 0.0113, 0.8956
|
|
40
|
-
);
|
|
41
|
-
const mat3 AGX_INSET = mat3(
|
|
42
|
-
0.856627153315983, 0.137318972929847, 0.11189821299995,
|
|
43
|
-
0.0951212405381588, 0.761241990602591, 0.0767994186031903,
|
|
44
|
-
0.0482516061458583, 0.101439036467562, 0.811302368396859
|
|
45
|
-
);
|
|
46
|
-
const mat3 AGX_OUTSET = mat3(
|
|
47
|
-
1.1271005818144368, -0.1413297634984383, -0.14132976349843826,
|
|
48
|
-
-0.11060664309660323, 1.157823702216272, -0.11060664309660294,
|
|
49
|
-
-0.016493938717834573, -0.016493938717834257, 1.2519364065950405
|
|
50
|
-
);
|
|
51
|
-
const mat3 REC2020_TO_SRGB = mat3(
|
|
52
|
-
1.6605, -0.1246, -0.0182,
|
|
53
|
-
-0.5876, 1.1329, -0.1006,
|
|
54
|
-
-0.0728, -0.0083, 1.1187
|
|
55
|
-
);
|
|
56
|
-
vec3 c = AGX_INSET * (SRGB_TO_REC2020 * x);
|
|
57
|
-
/* log2 of middle grey times 2^-10 and 2^6.5: -12.47393 and 4.026069, 16.5 stops apart. */
|
|
58
|
-
c = clamp((log2(max(c, vec3(1e-10))) + 12.47393) / 16.5, 0.0, 1.0);
|
|
59
|
-
c = AGX_OUTSET * agxContrast(c);
|
|
60
|
-
c = pow(max(c, vec3(0.0)), vec3(2.2));
|
|
61
|
-
return clamp(REC2020_TO_SRGB * c, 0.0, 1.0);
|
|
62
|
-
}
|
|
63
|
-
`;
|
|
64
|
-
/**
|
|
65
|
-
* The curve for a neutral grey, as the shader computes it: every matrix above keeps grey grey, so
|
|
66
|
-
* one channel is the whole of it. For a test to pin to hand-derived values.
|
|
67
|
-
*/
|
|
68
|
-
export function agxGrey(linear, exposure = 1) {
|
|
69
|
-
const x = Math.max(linear * exposure, 1e-10);
|
|
70
|
-
const t = Math.min(Math.max((Math.log2(x) + 12.47393) / 16.5, 0), 1);
|
|
71
|
-
const t2 = t * t;
|
|
72
|
-
const t4 = t2 * t2;
|
|
73
|
-
const s = 15.5 * t4 * t2 -
|
|
74
|
-
40.14 * t4 * t +
|
|
75
|
-
31.96 * t4 -
|
|
76
|
-
6.868 * t2 * t +
|
|
77
|
-
0.4298 * t2 +
|
|
78
|
-
0.1191 * t -
|
|
79
|
-
0.00232;
|
|
80
|
-
return Math.min(Math.max(Math.max(s, 0) ** 2.2, 0), 1);
|
|
81
|
-
}
|