@holoscript/engine 6.0.3 → 6.0.4
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/dist/AutoMesher-CK47F6AV.js +17 -0
- package/dist/GPUBuffers-2LHBCD7X.js +9 -0
- package/dist/WebGPUContext-TNEUYU2Y.js +11 -0
- package/dist/animation/index.cjs +38 -38
- package/dist/animation/index.d.cts +1 -1
- package/dist/animation/index.d.ts +1 -1
- package/dist/animation/index.js +1 -1
- package/dist/audio/index.cjs +16 -6
- package/dist/audio/index.d.cts +1 -1
- package/dist/audio/index.d.ts +1 -1
- package/dist/audio/index.js +1 -1
- package/dist/camera/index.cjs +23 -23
- package/dist/camera/index.d.cts +1 -1
- package/dist/camera/index.d.ts +1 -1
- package/dist/camera/index.js +1 -1
- package/dist/character/index.cjs +6 -4
- package/dist/character/index.js +1 -1
- package/dist/choreography/index.cjs +1194 -0
- package/dist/choreography/index.d.cts +687 -0
- package/dist/choreography/index.d.ts +687 -0
- package/dist/choreography/index.js +1156 -0
- package/dist/chunk-2CSNRI2N.js +217 -0
- package/dist/chunk-33T2WINR.js +266 -0
- package/dist/chunk-35R73OFM.js +1257 -0
- package/dist/chunk-4MMDSUNP.js +1256 -0
- package/dist/chunk-5V6HOU72.js +319 -0
- package/dist/chunk-6QOP6PYF.js +1038 -0
- package/dist/chunk-7KMJVHIL.js +8944 -0
- package/dist/chunk-7VPUC62U.js +1106 -0
- package/dist/chunk-A2Y6RCAT.js +1878 -0
- package/dist/chunk-AHM42MK6.js +8944 -0
- package/dist/chunk-BL7IDTHE.js +218 -0
- package/dist/chunk-CITOMSWL.js +10462 -0
- package/dist/chunk-CXDPKW2K.js +8944 -0
- package/dist/chunk-CXZPLD4S.js +223 -0
- package/dist/chunk-CZYJE7IH.js +5169 -0
- package/dist/chunk-D2OP7YC7.js +6325 -0
- package/dist/chunk-EDRVQHUU.js +1544 -0
- package/dist/chunk-EJSLOOW2.js +3589 -0
- package/dist/chunk-F53SFGW5.js +1878 -0
- package/dist/chunk-HCFPELPY.js +919 -0
- package/dist/chunk-HNEE36PY.js +93 -0
- package/dist/chunk-HYXNV36F.js +1256 -0
- package/dist/chunk-IB7KHVFY.js +821 -0
- package/dist/chunk-IBBO7YYG.js +690 -0
- package/dist/chunk-ILIBGINU.js +5470 -0
- package/dist/chunk-IS4MHLKN.js +5479 -0
- package/dist/chunk-JT2PFKWD.js +5479 -0
- package/dist/chunk-K4CUB4NY.js +1038 -0
- package/dist/chunk-KATDQXRJ.js +10462 -0
- package/dist/chunk-KBQE6ZFJ.js +8944 -0
- package/dist/chunk-KBVD5K7E.js +560 -0
- package/dist/chunk-KCDPVQRY.js +4088 -0
- package/dist/chunk-KN4QJPKN.js +8944 -0
- package/dist/chunk-KWJ3ROSI.js +8944 -0
- package/dist/chunk-L45VF6DD.js +919 -0
- package/dist/chunk-LY4T37YK.js +307 -0
- package/dist/chunk-MDN5WZXA.js +1544 -0
- package/dist/chunk-MGCDP6VU.js +928 -0
- package/dist/chunk-NCX7X6G2.js +8681 -0
- package/dist/chunk-OF54BPVD.js +913 -0
- package/dist/chunk-OWSN2Q3Q.js +690 -0
- package/dist/chunk-PRRB5TTA.js +406 -0
- package/dist/chunk-PXWVQF76.js +4086 -0
- package/dist/chunk-PYCOIDT2.js +812 -0
- package/dist/chunk-PZCSADOV.js +928 -0
- package/dist/chunk-Q2XBVS2K.js +1038 -0
- package/dist/chunk-QDZRXWN5.js +1776 -0
- package/dist/chunk-RNWOZ6WQ.js +913 -0
- package/dist/chunk-ROLFT4CJ.js +1693 -0
- package/dist/chunk-SLTJRZ2N.js +266 -0
- package/dist/chunk-SRUS5XSU.js +4088 -0
- package/dist/chunk-TKCA3WZ5.js +5409 -0
- package/dist/chunk-TNRMXYI2.js +1650 -0
- package/dist/chunk-TQB3GJGM.js +9763 -0
- package/dist/chunk-TUFGXG6K.js +510 -0
- package/dist/chunk-U6KMTGQJ.js +632 -0
- package/dist/chunk-VMGJQST6.js +8681 -0
- package/dist/chunk-X4F4TCG4.js +5470 -0
- package/dist/chunk-ZIFROE75.js +1544 -0
- package/dist/chunk-ZIJQYHSQ.js +1204 -0
- package/dist/combat/index.cjs +4 -4
- package/dist/combat/index.d.cts +1 -1
- package/dist/combat/index.d.ts +1 -1
- package/dist/combat/index.js +1 -1
- package/dist/ecs/index.cjs +1 -1
- package/dist/ecs/index.js +1 -1
- package/dist/environment/index.cjs +14 -14
- package/dist/environment/index.d.cts +1 -1
- package/dist/environment/index.d.ts +1 -1
- package/dist/environment/index.js +1 -1
- package/dist/gpu/index.cjs +4810 -0
- package/dist/gpu/index.js +3714 -0
- package/dist/hologram/index.cjs +27 -1
- package/dist/hologram/index.js +1 -1
- package/dist/index-B2PIsAmR.d.cts +2180 -0
- package/dist/index-B2PIsAmR.d.ts +2180 -0
- package/dist/index-BHySEPX7.d.cts +2921 -0
- package/dist/index-BJV21zuy.d.cts +341 -0
- package/dist/index-BJV21zuy.d.ts +341 -0
- package/dist/index-BQutTphC.d.cts +790 -0
- package/dist/index-ByIq2XrS.d.cts +3910 -0
- package/dist/index-BysHjDSO.d.cts +224 -0
- package/dist/index-BysHjDSO.d.ts +224 -0
- package/dist/index-CKwAJGck.d.ts +455 -0
- package/dist/index-CUl3QstQ.d.cts +3006 -0
- package/dist/index-CUl3QstQ.d.ts +3006 -0
- package/dist/index-CmYtNiI-.d.cts +953 -0
- package/dist/index-CmYtNiI-.d.ts +953 -0
- package/dist/index-CnRzWxi_.d.cts +522 -0
- package/dist/index-CnRzWxi_.d.ts +522 -0
- package/dist/index-CwRWbSC7.d.ts +2921 -0
- package/dist/index-CxKIBstO.d.ts +790 -0
- package/dist/index-DJ6-R8vh.d.cts +455 -0
- package/dist/index-DQKisbcI.d.cts +4968 -0
- package/dist/index-DQKisbcI.d.ts +4968 -0
- package/dist/index-DRT2zJez.d.ts +3910 -0
- package/dist/index-DfNLiAka.d.cts +192 -0
- package/dist/index-DfNLiAka.d.ts +192 -0
- package/dist/index-nMvkoRm8.d.cts +405 -0
- package/dist/index-nMvkoRm8.d.ts +405 -0
- package/dist/index-s9yOFU37.d.cts +604 -0
- package/dist/index-s9yOFU37.d.ts +604 -0
- package/dist/index.cjs +22966 -6960
- package/dist/index.d.cts +864 -20
- package/dist/index.d.ts +864 -20
- package/dist/index.js +3062 -48
- package/dist/input/index.cjs +1 -1
- package/dist/input/index.js +1 -1
- package/dist/orbital/index.cjs +3 -3
- package/dist/orbital/index.d.cts +1 -1
- package/dist/orbital/index.d.ts +1 -1
- package/dist/orbital/index.js +1 -1
- package/dist/particles/index.cjs +16 -16
- package/dist/particles/index.d.cts +1 -1
- package/dist/particles/index.d.ts +1 -1
- package/dist/particles/index.js +1 -1
- package/dist/physics/index.cjs +2377 -21
- package/dist/physics/index.d.cts +1 -1
- package/dist/physics/index.d.ts +1 -1
- package/dist/physics/index.js +35 -1
- package/dist/postfx/index.cjs +3491 -0
- package/dist/postfx/index.js +93 -0
- package/dist/procedural/index.cjs +1 -1
- package/dist/procedural/index.js +1 -1
- package/dist/puppeteer-5VF6KDVO.js +52197 -0
- package/dist/puppeteer-IZVZ3SG4.js +52197 -0
- package/dist/rendering/index.cjs +33 -32
- package/dist/rendering/index.d.cts +1 -1
- package/dist/rendering/index.d.ts +1 -1
- package/dist/rendering/index.js +8 -6
- package/dist/runtime/index.cjs +23 -13
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +8 -6
- package/dist/runtime/protocols/index.cjs +349 -0
- package/dist/runtime/protocols/index.js +15 -0
- package/dist/scene/index.cjs +8 -8
- package/dist/scene/index.d.cts +1 -1
- package/dist/scene/index.d.ts +1 -1
- package/dist/scene/index.js +1 -1
- package/dist/shader/index.cjs +3087 -0
- package/dist/shader/index.js +3044 -0
- package/dist/simulation/index.cjs +10680 -0
- package/dist/simulation/index.d.cts +3 -0
- package/dist/simulation/index.d.ts +3 -0
- package/dist/simulation/index.js +307 -0
- package/dist/spatial/index.cjs +2443 -0
- package/dist/spatial/index.d.cts +1545 -0
- package/dist/spatial/index.d.ts +1545 -0
- package/dist/spatial/index.js +2400 -0
- package/dist/terrain/index.cjs +1 -1
- package/dist/terrain/index.d.cts +1 -1
- package/dist/terrain/index.d.ts +1 -1
- package/dist/terrain/index.js +1 -1
- package/dist/transformers.node-4NKAPD5U.js +45620 -0
- package/dist/vm/index.cjs +7 -8
- package/dist/vm/index.d.cts +1 -1
- package/dist/vm/index.d.ts +1 -1
- package/dist/vm/index.js +1 -1
- package/dist/vm-bridge/index.cjs +2 -2
- package/dist/vm-bridge/index.d.cts +2 -2
- package/dist/vm-bridge/index.d.ts +2 -2
- package/dist/vm-bridge/index.js +1 -1
- package/dist/vr/index.cjs +6 -6
- package/dist/vr/index.js +1 -1
- package/dist/world/index.cjs +3 -3
- package/dist/world/index.d.cts +1 -1
- package/dist/world/index.d.ts +1 -1
- package/dist/world/index.js +1 -1
- package/package.json +53 -21
- package/LICENSE +0 -21
|
@@ -0,0 +1,3491 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/postfx/index.ts
|
|
21
|
+
var postfx_exports = {};
|
|
22
|
+
__export(postfx_exports, {
|
|
23
|
+
BLIT_SHADER: () => BLIT_SHADER,
|
|
24
|
+
BLOOM_SHADER: () => BLOOM_SHADER,
|
|
25
|
+
CAUSTICS_SHADER: () => CAUSTICS_SHADER,
|
|
26
|
+
CHROMATIC_ABERRATION_SHADER: () => CHROMATIC_ABERRATION_SHADER,
|
|
27
|
+
COLOR_GRADE_SHADER: () => COLOR_GRADE_SHADER,
|
|
28
|
+
CausticsEffect: () => CausticsEffect,
|
|
29
|
+
ChromaticAberrationEffect: () => ChromaticAberrationEffect,
|
|
30
|
+
DEFAULT_PARAMS: () => DEFAULT_PARAMS,
|
|
31
|
+
DEFAULT_PIPELINE_CONFIG: () => DEFAULT_PIPELINE_CONFIG,
|
|
32
|
+
DOF_SHADER: () => DOF_SHADER,
|
|
33
|
+
FILM_GRAIN_SHADER: () => FILM_GRAIN_SHADER,
|
|
34
|
+
FOG_SHADER: () => FOG_SHADER,
|
|
35
|
+
FULLSCREEN_VERTEX_SHADER: () => FULLSCREEN_VERTEX_SHADER,
|
|
36
|
+
FXAAEffect: () => FXAAEffect,
|
|
37
|
+
FXAA_SHADER: () => FXAA_SHADER,
|
|
38
|
+
FilmGrainEffect: () => FilmGrainEffect,
|
|
39
|
+
MOTION_BLUR_SHADER: () => MOTION_BLUR_SHADER,
|
|
40
|
+
PP_PRESETS: () => PP_PRESETS,
|
|
41
|
+
PostProcessEffect: () => PostProcessEffect,
|
|
42
|
+
PostProcessPipeline: () => PostProcessPipeline,
|
|
43
|
+
PostProcessStack: () => PostProcessStack,
|
|
44
|
+
PostProcessingStack: () => PostProcessingStack,
|
|
45
|
+
SHADER_UTILS: () => SHADER_UTILS,
|
|
46
|
+
SHARPEN_SHADER: () => SHARPEN_SHADER,
|
|
47
|
+
SSAOEffect: () => SSAOEffect,
|
|
48
|
+
SSAO_SHADER: () => SSAO_SHADER,
|
|
49
|
+
SSGIEffect: () => SSGIEffect,
|
|
50
|
+
SSGI_SHADER: () => SSGI_SHADER,
|
|
51
|
+
SSREffect: () => SSREffect,
|
|
52
|
+
SSR_SHADER: () => SSR_SHADER,
|
|
53
|
+
SharpenEffect: () => SharpenEffect,
|
|
54
|
+
TONEMAP_SHADER: () => TONEMAP_SHADER,
|
|
55
|
+
ToneMapEffect: () => ToneMapEffect,
|
|
56
|
+
UNIFORM_SIZES: () => UNIFORM_SIZES,
|
|
57
|
+
VIGNETTE_SHADER: () => VIGNETTE_SHADER,
|
|
58
|
+
VignetteEffect: () => VignetteEffect,
|
|
59
|
+
buildEffectShader: () => buildEffectShader,
|
|
60
|
+
createEffect: () => createEffect,
|
|
61
|
+
createHDRPipeline: () => createHDRPipeline,
|
|
62
|
+
createLDRPipeline: () => createLDRPipeline,
|
|
63
|
+
createPostProcessPipeline: () => createPostProcessPipeline,
|
|
64
|
+
getDefaultParams: () => getDefaultParams,
|
|
65
|
+
mergeParams: () => mergeParams,
|
|
66
|
+
validateParams: () => validateParams
|
|
67
|
+
});
|
|
68
|
+
module.exports = __toCommonJS(postfx_exports);
|
|
69
|
+
|
|
70
|
+
// src/rendering/postprocess/PostProcessTypes.ts
|
|
71
|
+
var DEFAULT_PARAMS = {
|
|
72
|
+
bloom: {
|
|
73
|
+
enabled: true,
|
|
74
|
+
intensity: 1,
|
|
75
|
+
threshold: 1,
|
|
76
|
+
softThreshold: 0.5,
|
|
77
|
+
radius: 4,
|
|
78
|
+
iterations: 5,
|
|
79
|
+
anamorphic: 0,
|
|
80
|
+
highQuality: false,
|
|
81
|
+
blendMode: "add"
|
|
82
|
+
},
|
|
83
|
+
tonemap: {
|
|
84
|
+
enabled: true,
|
|
85
|
+
intensity: 1,
|
|
86
|
+
operator: "aces",
|
|
87
|
+
exposure: 1,
|
|
88
|
+
gamma: 2.2,
|
|
89
|
+
whitePoint: 1,
|
|
90
|
+
contrast: 1,
|
|
91
|
+
saturation: 1
|
|
92
|
+
},
|
|
93
|
+
dof: {
|
|
94
|
+
enabled: false,
|
|
95
|
+
intensity: 1,
|
|
96
|
+
focusDistance: 10,
|
|
97
|
+
focalLength: 50,
|
|
98
|
+
aperture: 2.8,
|
|
99
|
+
maxBlur: 1,
|
|
100
|
+
bokehShape: "circle",
|
|
101
|
+
bokehQuality: "medium",
|
|
102
|
+
nearBlur: true
|
|
103
|
+
},
|
|
104
|
+
motionBlur: {
|
|
105
|
+
enabled: false,
|
|
106
|
+
intensity: 1,
|
|
107
|
+
samples: 8,
|
|
108
|
+
velocityScale: 1,
|
|
109
|
+
maxVelocity: 64
|
|
110
|
+
},
|
|
111
|
+
ssao: {
|
|
112
|
+
enabled: false,
|
|
113
|
+
intensity: 1,
|
|
114
|
+
radius: 0.5,
|
|
115
|
+
bias: 0.025,
|
|
116
|
+
samples: 16,
|
|
117
|
+
power: 2,
|
|
118
|
+
falloff: 1,
|
|
119
|
+
mode: "hemisphere",
|
|
120
|
+
bentNormals: false,
|
|
121
|
+
spatialDenoise: false
|
|
122
|
+
},
|
|
123
|
+
fxaa: {
|
|
124
|
+
enabled: true,
|
|
125
|
+
intensity: 1,
|
|
126
|
+
quality: "high",
|
|
127
|
+
edgeThreshold: 0.166,
|
|
128
|
+
edgeThresholdMin: 0.0833
|
|
129
|
+
},
|
|
130
|
+
sharpen: {
|
|
131
|
+
enabled: false,
|
|
132
|
+
intensity: 0.5,
|
|
133
|
+
amount: 0.3,
|
|
134
|
+
threshold: 0.1
|
|
135
|
+
},
|
|
136
|
+
vignette: {
|
|
137
|
+
enabled: false,
|
|
138
|
+
intensity: 0.5,
|
|
139
|
+
roundness: 1,
|
|
140
|
+
smoothness: 0.5,
|
|
141
|
+
color: [0, 0, 0]
|
|
142
|
+
},
|
|
143
|
+
colorGrade: {
|
|
144
|
+
enabled: false,
|
|
145
|
+
intensity: 1,
|
|
146
|
+
shadows: [0, 0, 0],
|
|
147
|
+
midtones: [0, 0, 0],
|
|
148
|
+
highlights: [0, 0, 0],
|
|
149
|
+
shadowsOffset: 0,
|
|
150
|
+
highlightsOffset: 0,
|
|
151
|
+
hueShift: 0,
|
|
152
|
+
temperature: 0,
|
|
153
|
+
tint: 0,
|
|
154
|
+
lutIntensity: 1
|
|
155
|
+
},
|
|
156
|
+
filmGrain: {
|
|
157
|
+
enabled: false,
|
|
158
|
+
intensity: 0.1,
|
|
159
|
+
size: 1.6,
|
|
160
|
+
luminanceContribution: 0.8,
|
|
161
|
+
animated: true
|
|
162
|
+
},
|
|
163
|
+
chromaticAberration: {
|
|
164
|
+
enabled: false,
|
|
165
|
+
intensity: 0.5,
|
|
166
|
+
redOffset: [0.01, 0],
|
|
167
|
+
greenOffset: [0, 0],
|
|
168
|
+
blueOffset: [-0.01, 0],
|
|
169
|
+
radial: true
|
|
170
|
+
},
|
|
171
|
+
fog: {
|
|
172
|
+
enabled: false,
|
|
173
|
+
intensity: 1,
|
|
174
|
+
color: [0.7, 0.8, 0.9],
|
|
175
|
+
density: 0.02,
|
|
176
|
+
start: 10,
|
|
177
|
+
end: 100,
|
|
178
|
+
height: 0,
|
|
179
|
+
heightFalloff: 1,
|
|
180
|
+
mode: "exponential"
|
|
181
|
+
},
|
|
182
|
+
caustics: {
|
|
183
|
+
enabled: false,
|
|
184
|
+
intensity: 0.8,
|
|
185
|
+
scale: 8,
|
|
186
|
+
speed: 0.5,
|
|
187
|
+
color: [0.2, 0.5, 0.8],
|
|
188
|
+
depthFade: 0.5,
|
|
189
|
+
waterLevel: 0.5,
|
|
190
|
+
dispersion: 0,
|
|
191
|
+
foamIntensity: 0,
|
|
192
|
+
shadowStrength: 0
|
|
193
|
+
},
|
|
194
|
+
ssr: {
|
|
195
|
+
enabled: false,
|
|
196
|
+
intensity: 0.8,
|
|
197
|
+
maxSteps: 64,
|
|
198
|
+
stepSize: 0.05,
|
|
199
|
+
thickness: 0.1,
|
|
200
|
+
roughnessFade: 0.5,
|
|
201
|
+
edgeFade: 4,
|
|
202
|
+
roughnessBlur: 0,
|
|
203
|
+
fresnelStrength: 0
|
|
204
|
+
},
|
|
205
|
+
ssgi: {
|
|
206
|
+
enabled: false,
|
|
207
|
+
intensity: 0.5,
|
|
208
|
+
radius: 2,
|
|
209
|
+
samples: 16,
|
|
210
|
+
bounceIntensity: 1,
|
|
211
|
+
falloff: 1,
|
|
212
|
+
temporalBlend: 0,
|
|
213
|
+
spatialDenoise: false,
|
|
214
|
+
multiBounce: 0
|
|
215
|
+
},
|
|
216
|
+
custom: {
|
|
217
|
+
enabled: true,
|
|
218
|
+
intensity: 1,
|
|
219
|
+
shader: "",
|
|
220
|
+
uniforms: {}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
function getDefaultParams(type) {
|
|
224
|
+
return { ...DEFAULT_PARAMS[type] };
|
|
225
|
+
}
|
|
226
|
+
function mergeParams(type, partial) {
|
|
227
|
+
return { ...DEFAULT_PARAMS[type], ...partial };
|
|
228
|
+
}
|
|
229
|
+
function validateParams(type, params) {
|
|
230
|
+
const errors = [];
|
|
231
|
+
if (params == null) {
|
|
232
|
+
return { valid: false, errors: ["params is null or undefined"] };
|
|
233
|
+
}
|
|
234
|
+
if (typeof params.enabled !== "boolean") {
|
|
235
|
+
errors.push("enabled must be boolean");
|
|
236
|
+
}
|
|
237
|
+
if (typeof params.intensity !== "number" || params.intensity < 0) {
|
|
238
|
+
errors.push("intensity must be non-negative number");
|
|
239
|
+
}
|
|
240
|
+
switch (type) {
|
|
241
|
+
case "bloom": {
|
|
242
|
+
const p = params;
|
|
243
|
+
if (p.threshold < 0) errors.push("bloom.threshold must be >= 0");
|
|
244
|
+
if (p.iterations < 1 || p.iterations > 16) {
|
|
245
|
+
errors.push("bloom.iterations must be 1-16");
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case "tonemap": {
|
|
250
|
+
const p = params;
|
|
251
|
+
if (p.exposure <= 0) errors.push("tonemap.exposure must be > 0");
|
|
252
|
+
if (p.gamma <= 0) errors.push("tonemap.gamma must be > 0");
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
case "dof": {
|
|
256
|
+
const p = params;
|
|
257
|
+
if (p.focusDistance <= 0) errors.push("dof.focusDistance must be > 0");
|
|
258
|
+
if (p.aperture <= 0) errors.push("dof.aperture must be > 0");
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case "ssao": {
|
|
262
|
+
const p = params;
|
|
263
|
+
if (p.samples < 4 || p.samples > 64) {
|
|
264
|
+
errors.push("ssao.samples must be 4-64");
|
|
265
|
+
}
|
|
266
|
+
if (p.radius <= 0) errors.push("ssao.radius must be > 0");
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return { valid: errors.length === 0, errors };
|
|
271
|
+
}
|
|
272
|
+
var UNIFORM_SIZES = {
|
|
273
|
+
bloom: 48,
|
|
274
|
+
// intensity, threshold, softThreshold, radius, iterations, anamorphic
|
|
275
|
+
tonemap: 32,
|
|
276
|
+
// operator, exposure, gamma, whitePoint, contrast, saturation
|
|
277
|
+
dof: 48,
|
|
278
|
+
// focusDistance, focalLength, aperture, maxBlur, near/far
|
|
279
|
+
motionBlur: 16,
|
|
280
|
+
// samples, velocityScale, maxVelocity
|
|
281
|
+
ssao: 48,
|
|
282
|
+
// radius, bias, samples, power, falloff, mode, bentNormals, spatialDenoise
|
|
283
|
+
fxaa: 16,
|
|
284
|
+
// quality, edgeThreshold, edgeThresholdMin
|
|
285
|
+
sharpen: 16,
|
|
286
|
+
// amount, threshold
|
|
287
|
+
vignette: 32,
|
|
288
|
+
// intensity, roundness, smoothness, color
|
|
289
|
+
colorGrade: 96,
|
|
290
|
+
// shadows, midtones, highlights, offsets, hue, temp, tint
|
|
291
|
+
filmGrain: 16,
|
|
292
|
+
// size, luminance, time
|
|
293
|
+
chromaticAberration: 32,
|
|
294
|
+
// offsets, radial
|
|
295
|
+
fog: 48,
|
|
296
|
+
// color, density, start, end, height, falloff
|
|
297
|
+
caustics: 64,
|
|
298
|
+
// intensity, scale, speed, time, color, depthFade, waterLevel, dispersion, foam, shadow
|
|
299
|
+
ssr: 48,
|
|
300
|
+
// maxSteps, stepSize, thickness, roughnessFade, edgeFade, intensity, roughnessBlur, fresnel
|
|
301
|
+
ssgi: 48,
|
|
302
|
+
// radius, samples, bounceIntensity, falloff, time, intensity, temporalBlend, denoise, multiBounce
|
|
303
|
+
custom: 256
|
|
304
|
+
// generic uniform buffer for custom effects
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// src/rendering/postprocess/PostProcessShaders.ts
|
|
308
|
+
var FULLSCREEN_VERTEX_SHADER = (
|
|
309
|
+
/* wgsl */
|
|
310
|
+
`
|
|
311
|
+
struct VertexOutput {
|
|
312
|
+
@builtin(position) position: vec4f,
|
|
313
|
+
@location(0) uv: vec2f,
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@vertex
|
|
317
|
+
fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
318
|
+
// Generate fullscreen triangle
|
|
319
|
+
var positions = array<vec2f, 3>(
|
|
320
|
+
vec2f(-1.0, -1.0),
|
|
321
|
+
vec2f(3.0, -1.0),
|
|
322
|
+
vec2f(-1.0, 3.0)
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
var uvs = array<vec2f, 3>(
|
|
326
|
+
vec2f(0.0, 1.0),
|
|
327
|
+
vec2f(2.0, 1.0),
|
|
328
|
+
vec2f(0.0, -1.0)
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
var output: VertexOutput;
|
|
332
|
+
output.position = vec4f(positions[vertexIndex], 0.0, 1.0);
|
|
333
|
+
output.uv = uvs[vertexIndex];
|
|
334
|
+
return output;
|
|
335
|
+
}
|
|
336
|
+
`
|
|
337
|
+
);
|
|
338
|
+
var SHADER_UTILS = (
|
|
339
|
+
/* wgsl */
|
|
340
|
+
`
|
|
341
|
+
// Luminance calculation (Rec. 709)
|
|
342
|
+
fn luminance(color: vec3f) -> f32 {
|
|
343
|
+
return dot(color, vec3f(0.2126, 0.7152, 0.0722));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// sRGB to linear conversion
|
|
347
|
+
fn srgbToLinear(color: vec3f) -> vec3f {
|
|
348
|
+
return pow(color, vec3f(2.2));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Linear to sRGB conversion
|
|
352
|
+
fn linearToSrgb(color: vec3f) -> vec3f {
|
|
353
|
+
return pow(color, vec3f(1.0 / 2.2));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Hash function for noise
|
|
357
|
+
fn hash(p: vec2f) -> f32 {
|
|
358
|
+
let k = vec2f(0.3183099, 0.3678794);
|
|
359
|
+
let x = p * k + k.yx;
|
|
360
|
+
return fract(16.0 * k.x * fract(x.x * x.y * (x.x + x.y)));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Simple 2D noise
|
|
364
|
+
fn noise2D(p: vec2f) -> f32 {
|
|
365
|
+
let i = floor(p);
|
|
366
|
+
let f = fract(p);
|
|
367
|
+
let u = f * f * (3.0 - 2.0 * f);
|
|
368
|
+
return mix(
|
|
369
|
+
mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u.x),
|
|
370
|
+
mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u.x),
|
|
371
|
+
u.y
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
`
|
|
375
|
+
);
|
|
376
|
+
var BLOOM_SHADER = (
|
|
377
|
+
/* wgsl */
|
|
378
|
+
`
|
|
379
|
+
${SHADER_UTILS}
|
|
380
|
+
|
|
381
|
+
struct BloomUniforms {
|
|
382
|
+
intensity: f32,
|
|
383
|
+
threshold: f32,
|
|
384
|
+
softThreshold: f32,
|
|
385
|
+
radius: f32,
|
|
386
|
+
iterations: f32,
|
|
387
|
+
anamorphic: f32,
|
|
388
|
+
highQuality: f32,
|
|
389
|
+
padding: f32,
|
|
390
|
+
time: f32,
|
|
391
|
+
deltaTime: f32,
|
|
392
|
+
padding2: vec2f,
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
396
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
397
|
+
@group(0) @binding(2) var<uniform> uniforms: BloomUniforms;
|
|
398
|
+
|
|
399
|
+
// Threshold with soft knee
|
|
400
|
+
fn softThreshold(color: vec3f) -> vec3f {
|
|
401
|
+
let brightness = max(max(color.r, color.g), color.b);
|
|
402
|
+
var soft = brightness - uniforms.threshold + uniforms.softThreshold;
|
|
403
|
+
soft = clamp(soft, 0.0, 2.0 * uniforms.softThreshold);
|
|
404
|
+
soft = soft * soft / (4.0 * uniforms.softThreshold + 0.00001);
|
|
405
|
+
var contribution = max(soft, brightness - uniforms.threshold);
|
|
406
|
+
contribution /= max(brightness, 0.00001);
|
|
407
|
+
return color * contribution;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// 9-tap gaussian blur
|
|
411
|
+
fn blur9(uv: vec2f, direction: vec2f) -> vec3f {
|
|
412
|
+
let texSize = vec2f(textureDimensions(inputTexture));
|
|
413
|
+
let offset = direction / texSize;
|
|
414
|
+
|
|
415
|
+
var color = textureSample(inputTexture, texSampler, uv).rgb * 0.2270270270;
|
|
416
|
+
color += textureSample(inputTexture, texSampler, uv + offset * 1.3846153846).rgb * 0.3162162162;
|
|
417
|
+
color += textureSample(inputTexture, texSampler, uv - offset * 1.3846153846).rgb * 0.3162162162;
|
|
418
|
+
color += textureSample(inputTexture, texSampler, uv + offset * 3.2307692308).rgb * 0.0702702703;
|
|
419
|
+
color += textureSample(inputTexture, texSampler, uv - offset * 3.2307692308).rgb * 0.0702702703;
|
|
420
|
+
|
|
421
|
+
return color;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
@fragment
|
|
425
|
+
fn fs_bloom(input: VertexOutput) -> @location(0) vec4f {
|
|
426
|
+
let color = textureSample(inputTexture, texSampler, input.uv).rgb;
|
|
427
|
+
|
|
428
|
+
// Extract bright pixels with soft threshold
|
|
429
|
+
var bloom = softThreshold(color);
|
|
430
|
+
|
|
431
|
+
// Simple blur approximation (in production, use multi-pass)
|
|
432
|
+
let texSize = vec2f(textureDimensions(inputTexture));
|
|
433
|
+
let radius = uniforms.radius / texSize;
|
|
434
|
+
|
|
435
|
+
var blurred = bloom;
|
|
436
|
+
for (var i = 0u; i < 4u; i++) {
|
|
437
|
+
let angle = f32(i) * 1.5707963268;
|
|
438
|
+
let offset = vec2f(cos(angle), sin(angle)) * radius;
|
|
439
|
+
blurred += textureSample(inputTexture, texSampler, input.uv + offset).rgb;
|
|
440
|
+
blurred += textureSample(inputTexture, texSampler, input.uv - offset).rgb;
|
|
441
|
+
}
|
|
442
|
+
blurred /= 9.0;
|
|
443
|
+
|
|
444
|
+
// Composite bloom
|
|
445
|
+
let result = color + softThreshold(blurred) * uniforms.intensity;
|
|
446
|
+
|
|
447
|
+
return vec4f(result, 1.0);
|
|
448
|
+
}
|
|
449
|
+
`
|
|
450
|
+
);
|
|
451
|
+
var TONEMAP_SHADER = (
|
|
452
|
+
/* wgsl */
|
|
453
|
+
`
|
|
454
|
+
${SHADER_UTILS}
|
|
455
|
+
|
|
456
|
+
struct ToneMapUniforms {
|
|
457
|
+
operator: f32,
|
|
458
|
+
exposure: f32,
|
|
459
|
+
gamma: f32,
|
|
460
|
+
whitePoint: f32,
|
|
461
|
+
contrast: f32,
|
|
462
|
+
saturation: f32,
|
|
463
|
+
intensity: f32,
|
|
464
|
+
padding: f32,
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
468
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
469
|
+
@group(0) @binding(2) var<uniform> uniforms: ToneMapUniforms;
|
|
470
|
+
|
|
471
|
+
// Reinhard tone mapping
|
|
472
|
+
fn tonemapReinhard(x: vec3f) -> vec3f {
|
|
473
|
+
return x / (x + vec3f(1.0));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Reinhard with luminance-based mapping
|
|
477
|
+
fn tonemapReinhardLum(x: vec3f) -> vec3f {
|
|
478
|
+
let l = luminance(x);
|
|
479
|
+
let nl = l / (l + 1.0);
|
|
480
|
+
return x * (nl / l);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ACES filmic tone mapping
|
|
484
|
+
fn tonemapACES(x: vec3f) -> vec3f {
|
|
485
|
+
let a = 2.51;
|
|
486
|
+
let b = 0.03;
|
|
487
|
+
let c = 2.43;
|
|
488
|
+
let d = 0.59;
|
|
489
|
+
let e = 0.14;
|
|
490
|
+
return clamp((x * (a * x + vec3f(b))) / (x * (c * x + vec3f(d)) + vec3f(e)), vec3f(0.0), vec3f(1.0));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ACES approximation (cheaper)
|
|
494
|
+
fn tonemapACESApprox(x: vec3f) -> vec3f {
|
|
495
|
+
let v = x * 0.6;
|
|
496
|
+
let a = v * (v * 2.51 + 0.03);
|
|
497
|
+
let b = v * (v * 2.43 + 0.59) + 0.14;
|
|
498
|
+
return clamp(a / b, vec3f(0.0), vec3f(1.0));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Uncharted 2 filmic
|
|
502
|
+
fn uncharted2Partial(x: vec3f) -> vec3f {
|
|
503
|
+
let A = 0.15;
|
|
504
|
+
let B = 0.50;
|
|
505
|
+
let C = 0.10;
|
|
506
|
+
let D = 0.20;
|
|
507
|
+
let E = 0.02;
|
|
508
|
+
let F = 0.30;
|
|
509
|
+
return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
fn tonemapUncharted2(x: vec3f) -> vec3f {
|
|
513
|
+
let white = 11.2;
|
|
514
|
+
let curr = uncharted2Partial(x * 2.0);
|
|
515
|
+
let whiteScale = vec3f(1.0) / uncharted2Partial(vec3f(white));
|
|
516
|
+
return curr * whiteScale;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Lottes (AMD)
|
|
520
|
+
fn tonemapLottes(x: vec3f) -> vec3f {
|
|
521
|
+
let a = vec3f(1.6);
|
|
522
|
+
let d = vec3f(0.977);
|
|
523
|
+
let hdrMax = vec3f(8.0);
|
|
524
|
+
let midIn = vec3f(0.18);
|
|
525
|
+
let midOut = vec3f(0.267);
|
|
526
|
+
|
|
527
|
+
let b = (-pow(midIn, a) + pow(hdrMax, a) * midOut) / ((pow(hdrMax, a * d) - pow(midIn, a * d)) * midOut);
|
|
528
|
+
let c = (pow(hdrMax, a * d) * pow(midIn, a) - pow(hdrMax, a) * pow(midIn, a * d) * midOut) /
|
|
529
|
+
((pow(hdrMax, a * d) - pow(midIn, a * d)) * midOut);
|
|
530
|
+
|
|
531
|
+
return pow(x, a) / (pow(x, a * d) * b + c);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Uchimura (GT)
|
|
535
|
+
fn tonemapUchimura(x: vec3f) -> vec3f {
|
|
536
|
+
let P = 1.0; // max brightness
|
|
537
|
+
let a = 1.0; // contrast
|
|
538
|
+
let m = 0.22; // linear section start
|
|
539
|
+
let l = 0.4; // linear section length
|
|
540
|
+
let c = 1.33; // black tightness
|
|
541
|
+
let b = 0.0; // black lightness
|
|
542
|
+
|
|
543
|
+
let l0 = ((P - m) * l) / a;
|
|
544
|
+
let S0 = m + l0;
|
|
545
|
+
let S1 = m + a * l0;
|
|
546
|
+
let C2 = (a * P) / (P - S1);
|
|
547
|
+
let CP = -C2 / P;
|
|
548
|
+
|
|
549
|
+
var result: vec3f;
|
|
550
|
+
for (var i = 0u; i < 3u; i++) {
|
|
551
|
+
let v = x[i];
|
|
552
|
+
var w: f32;
|
|
553
|
+
if (v < m) {
|
|
554
|
+
w = v;
|
|
555
|
+
} else if (v < S0) {
|
|
556
|
+
w = m + a * (v - m);
|
|
557
|
+
} else {
|
|
558
|
+
w = P - (P - S1) * exp(CP * (v - S0));
|
|
559
|
+
}
|
|
560
|
+
result[i] = w;
|
|
561
|
+
}
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Khronos PBR neutral
|
|
566
|
+
fn tonemapKhronosPBR(color: vec3f) -> vec3f {
|
|
567
|
+
let startCompression = 0.8 - 0.04;
|
|
568
|
+
let desaturation = 0.15;
|
|
569
|
+
|
|
570
|
+
var x = min(color, vec3f(1.0));
|
|
571
|
+
let peak = max(max(color.r, color.g), color.b);
|
|
572
|
+
|
|
573
|
+
if (peak < startCompression) {
|
|
574
|
+
return x;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
let d = 1.0 - startCompression;
|
|
578
|
+
let newPeak = 1.0 - d * d / (peak + d - startCompression);
|
|
579
|
+
x *= newPeak / peak;
|
|
580
|
+
|
|
581
|
+
let g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);
|
|
582
|
+
return mix(x, vec3f(newPeak), g);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
@fragment
|
|
586
|
+
fn fs_tonemap(input: VertexOutput) -> @location(0) vec4f {
|
|
587
|
+
var color = textureSample(inputTexture, texSampler, input.uv).rgb;
|
|
588
|
+
|
|
589
|
+
// Apply exposure
|
|
590
|
+
color *= uniforms.exposure;
|
|
591
|
+
|
|
592
|
+
// Apply contrast (around mid-gray)
|
|
593
|
+
let midGray = 0.18;
|
|
594
|
+
color = midGray * pow(color / midGray, vec3f(uniforms.contrast));
|
|
595
|
+
|
|
596
|
+
// Apply saturation
|
|
597
|
+
let lum = luminance(color);
|
|
598
|
+
color = mix(vec3f(lum), color, uniforms.saturation);
|
|
599
|
+
|
|
600
|
+
// Apply tone mapping
|
|
601
|
+
let op = u32(uniforms.operator);
|
|
602
|
+
var mapped: vec3f;
|
|
603
|
+
switch (op) {
|
|
604
|
+
case 0u: { mapped = clamp(color, vec3f(0.0), vec3f(1.0)); } // None
|
|
605
|
+
case 1u: { mapped = tonemapReinhard(color); }
|
|
606
|
+
case 2u: { mapped = tonemapReinhardLum(color); }
|
|
607
|
+
case 3u: { mapped = tonemapACES(color); }
|
|
608
|
+
case 4u: { mapped = tonemapACESApprox(color); }
|
|
609
|
+
case 5u: { mapped = tonemapACES(color); } // Filmic = ACES
|
|
610
|
+
case 6u: { mapped = tonemapUncharted2(color); }
|
|
611
|
+
case 7u: { mapped = tonemapUchimura(color); }
|
|
612
|
+
case 8u: { mapped = tonemapLottes(color); }
|
|
613
|
+
case 9u: { mapped = tonemapKhronosPBR(color); }
|
|
614
|
+
default: { mapped = tonemapACES(color); }
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Apply gamma correction
|
|
618
|
+
let result = pow(mapped, vec3f(1.0 / uniforms.gamma));
|
|
619
|
+
|
|
620
|
+
return vec4f(result, 1.0);
|
|
621
|
+
}
|
|
622
|
+
`
|
|
623
|
+
);
|
|
624
|
+
var FXAA_SHADER = (
|
|
625
|
+
/* wgsl */
|
|
626
|
+
`
|
|
627
|
+
${SHADER_UTILS}
|
|
628
|
+
|
|
629
|
+
struct FXAAUniforms {
|
|
630
|
+
quality: f32,
|
|
631
|
+
edgeThreshold: f32,
|
|
632
|
+
edgeThresholdMin: f32,
|
|
633
|
+
intensity: f32,
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
637
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
638
|
+
@group(0) @binding(2) var<uniform> uniforms: FXAAUniforms;
|
|
639
|
+
|
|
640
|
+
@fragment
|
|
641
|
+
fn fs_fxaa(input: VertexOutput) -> @location(0) vec4f {
|
|
642
|
+
let texSize = vec2f(textureDimensions(inputTexture));
|
|
643
|
+
let invSize = 1.0 / texSize;
|
|
644
|
+
|
|
645
|
+
// Sample center and neighbors
|
|
646
|
+
let center = textureSample(inputTexture, texSampler, input.uv);
|
|
647
|
+
let lumC = luminance(center.rgb);
|
|
648
|
+
|
|
649
|
+
let lumN = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(0.0, -1.0) * invSize).rgb);
|
|
650
|
+
let lumS = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(0.0, 1.0) * invSize).rgb);
|
|
651
|
+
let lumE = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(1.0, 0.0) * invSize).rgb);
|
|
652
|
+
let lumW = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(-1.0, 0.0) * invSize).rgb);
|
|
653
|
+
|
|
654
|
+
let lumMin = min(lumC, min(min(lumN, lumS), min(lumE, lumW)));
|
|
655
|
+
let lumMax = max(lumC, max(max(lumN, lumS), max(lumE, lumW)));
|
|
656
|
+
let lumRange = lumMax - lumMin;
|
|
657
|
+
|
|
658
|
+
// Skip if edge contrast is too low
|
|
659
|
+
if (lumRange < max(uniforms.edgeThresholdMin, lumMax * uniforms.edgeThreshold)) {
|
|
660
|
+
return center;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Compute edge direction
|
|
664
|
+
let lumNW = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(-1.0, -1.0) * invSize).rgb);
|
|
665
|
+
let lumNE = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(1.0, -1.0) * invSize).rgb);
|
|
666
|
+
let lumSW = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(-1.0, 1.0) * invSize).rgb);
|
|
667
|
+
let lumSE = luminance(textureSample(inputTexture, texSampler, input.uv + vec2f(1.0, 1.0) * invSize).rgb);
|
|
668
|
+
|
|
669
|
+
let edgeH = abs((lumNW + lumNE) - 2.0 * lumN) +
|
|
670
|
+
abs((lumW + lumE) - 2.0 * lumC) * 2.0 +
|
|
671
|
+
abs((lumSW + lumSE) - 2.0 * lumS);
|
|
672
|
+
|
|
673
|
+
let edgeV = abs((lumNW + lumSW) - 2.0 * lumW) +
|
|
674
|
+
abs((lumN + lumS) - 2.0 * lumC) * 2.0 +
|
|
675
|
+
abs((lumNE + lumSE) - 2.0 * lumE);
|
|
676
|
+
|
|
677
|
+
let isHorizontal = edgeH >= edgeV;
|
|
678
|
+
|
|
679
|
+
// Blend direction
|
|
680
|
+
let stepLength = select(invSize.x, invSize.y, isHorizontal);
|
|
681
|
+
var lum1: f32;
|
|
682
|
+
var lum2: f32;
|
|
683
|
+
|
|
684
|
+
if (isHorizontal) {
|
|
685
|
+
lum1 = lumN;
|
|
686
|
+
lum2 = lumS;
|
|
687
|
+
} else {
|
|
688
|
+
lum1 = lumW;
|
|
689
|
+
lum2 = lumE;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
let gradient1 = abs(lum1 - lumC);
|
|
693
|
+
let gradient2 = abs(lum2 - lumC);
|
|
694
|
+
|
|
695
|
+
let is1Steeper = gradient1 >= gradient2;
|
|
696
|
+
let gradientScaled = 0.25 * max(gradient1, gradient2);
|
|
697
|
+
let lumLocalAvg = 0.5 * (select(lum2, lum1, is1Steeper) + lumC);
|
|
698
|
+
|
|
699
|
+
// Subpixel anti-aliasing
|
|
700
|
+
let subpixC = (2.0 * (lumN + lumS + lumE + lumW) + lumNW + lumNE + lumSW + lumSE) / 12.0;
|
|
701
|
+
let subpixFactor = clamp(abs(subpixC - lumC) / lumRange, 0.0, 1.0);
|
|
702
|
+
let subpix = (-(subpixFactor * subpixFactor) + 1.0) * subpixFactor;
|
|
703
|
+
|
|
704
|
+
// Apply blend
|
|
705
|
+
var finalUV = input.uv;
|
|
706
|
+
let blendFactor = max(subpix, 0.5);
|
|
707
|
+
|
|
708
|
+
if (isHorizontal) {
|
|
709
|
+
finalUV.y += select(stepLength, -stepLength, is1Steeper) * blendFactor;
|
|
710
|
+
} else {
|
|
711
|
+
finalUV.x += select(stepLength, -stepLength, is1Steeper) * blendFactor;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
let result = textureSample(inputTexture, texSampler, finalUV);
|
|
715
|
+
return mix(center, result, uniforms.intensity);
|
|
716
|
+
}
|
|
717
|
+
`
|
|
718
|
+
);
|
|
719
|
+
var VIGNETTE_SHADER = (
|
|
720
|
+
/* wgsl */
|
|
721
|
+
`
|
|
722
|
+
struct VignetteUniforms {
|
|
723
|
+
intensity: f32,
|
|
724
|
+
roundness: f32,
|
|
725
|
+
smoothness: f32,
|
|
726
|
+
padding: f32,
|
|
727
|
+
color: vec4f,
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
731
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
732
|
+
@group(0) @binding(2) var<uniform> uniforms: VignetteUniforms;
|
|
733
|
+
|
|
734
|
+
@fragment
|
|
735
|
+
fn fs_vignette(input: VertexOutput) -> @location(0) vec4f {
|
|
736
|
+
let color = textureSample(inputTexture, texSampler, input.uv);
|
|
737
|
+
|
|
738
|
+
let uv = input.uv * 2.0 - 1.0;
|
|
739
|
+
let aspect = 1.0; // Could be passed via uniforms
|
|
740
|
+
|
|
741
|
+
var coords = uv;
|
|
742
|
+
coords.x *= aspect;
|
|
743
|
+
|
|
744
|
+
// Compute vignette
|
|
745
|
+
let dist = length(coords) * uniforms.roundness;
|
|
746
|
+
let vignette = 1.0 - smoothstep(1.0 - uniforms.smoothness, 1.0, dist);
|
|
747
|
+
|
|
748
|
+
// Blend with vignette color
|
|
749
|
+
let vignetteColor = mix(uniforms.color.rgb, color.rgb, vignette);
|
|
750
|
+
let result = mix(color.rgb, vignetteColor, uniforms.intensity);
|
|
751
|
+
|
|
752
|
+
return vec4f(result, color.a);
|
|
753
|
+
}
|
|
754
|
+
`
|
|
755
|
+
);
|
|
756
|
+
var FILM_GRAIN_SHADER = (
|
|
757
|
+
/* wgsl */
|
|
758
|
+
`
|
|
759
|
+
${SHADER_UTILS}
|
|
760
|
+
|
|
761
|
+
struct FilmGrainUniforms {
|
|
762
|
+
intensity: f32,
|
|
763
|
+
size: f32,
|
|
764
|
+
luminanceContribution: f32,
|
|
765
|
+
time: f32,
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
769
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
770
|
+
@group(0) @binding(2) var<uniform> uniforms: FilmGrainUniforms;
|
|
771
|
+
|
|
772
|
+
@fragment
|
|
773
|
+
fn fs_filmgrain(input: VertexOutput) -> @location(0) vec4f {
|
|
774
|
+
let color = textureSample(inputTexture, texSampler, input.uv);
|
|
775
|
+
|
|
776
|
+
let texSize = vec2f(textureDimensions(inputTexture));
|
|
777
|
+
let noiseUV = input.uv * texSize / uniforms.size;
|
|
778
|
+
|
|
779
|
+
// Generate animated noise
|
|
780
|
+
let grain = noise2D(noiseUV + vec2f(uniforms.time * 123.456, uniforms.time * 789.012)) * 2.0 - 1.0;
|
|
781
|
+
|
|
782
|
+
// Scale grain by luminance
|
|
783
|
+
let lum = luminance(color.rgb);
|
|
784
|
+
let grainAmount = uniforms.intensity * mix(1.0, 1.0 - lum, uniforms.luminanceContribution);
|
|
785
|
+
|
|
786
|
+
let result = color.rgb + vec3f(grain * grainAmount);
|
|
787
|
+
|
|
788
|
+
return vec4f(result, color.a);
|
|
789
|
+
}
|
|
790
|
+
`
|
|
791
|
+
);
|
|
792
|
+
var SHARPEN_SHADER = (
|
|
793
|
+
/* wgsl */
|
|
794
|
+
`
|
|
795
|
+
${SHADER_UTILS}
|
|
796
|
+
|
|
797
|
+
struct SharpenUniforms {
|
|
798
|
+
intensity: f32,
|
|
799
|
+
amount: f32,
|
|
800
|
+
threshold: f32,
|
|
801
|
+
padding: f32,
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
805
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
806
|
+
@group(0) @binding(2) var<uniform> uniforms: SharpenUniforms;
|
|
807
|
+
|
|
808
|
+
@fragment
|
|
809
|
+
fn fs_sharpen(input: VertexOutput) -> @location(0) vec4f {
|
|
810
|
+
let texSize = vec2f(textureDimensions(inputTexture));
|
|
811
|
+
let texel = 1.0 / texSize;
|
|
812
|
+
|
|
813
|
+
// Sample 3x3 neighborhood
|
|
814
|
+
let center = textureSample(inputTexture, texSampler, input.uv).rgb;
|
|
815
|
+
let n = textureSample(inputTexture, texSampler, input.uv + vec2f(0.0, -texel.y)).rgb;
|
|
816
|
+
let s = textureSample(inputTexture, texSampler, input.uv + vec2f(0.0, texel.y)).rgb;
|
|
817
|
+
let e = textureSample(inputTexture, texSampler, input.uv + vec2f(texel.x, 0.0)).rgb;
|
|
818
|
+
let w = textureSample(inputTexture, texSampler, input.uv + vec2f(-texel.x, 0.0)).rgb;
|
|
819
|
+
|
|
820
|
+
// Compute unsharp mask
|
|
821
|
+
let blur = (n + s + e + w) * 0.25;
|
|
822
|
+
let diff = center - blur;
|
|
823
|
+
|
|
824
|
+
// Apply threshold
|
|
825
|
+
let sharpened = select(
|
|
826
|
+
center,
|
|
827
|
+
center + diff * uniforms.amount,
|
|
828
|
+
length(diff) > uniforms.threshold
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
let result = mix(center, sharpened, uniforms.intensity);
|
|
832
|
+
|
|
833
|
+
return vec4f(result, 1.0);
|
|
834
|
+
}
|
|
835
|
+
`
|
|
836
|
+
);
|
|
837
|
+
var CHROMATIC_ABERRATION_SHADER = (
|
|
838
|
+
/* wgsl */
|
|
839
|
+
`
|
|
840
|
+
struct ChromaticUniforms {
|
|
841
|
+
intensity: f32,
|
|
842
|
+
radial: f32,
|
|
843
|
+
padding: vec2f,
|
|
844
|
+
redOffset: vec2f,
|
|
845
|
+
greenOffset: vec2f,
|
|
846
|
+
blueOffset: vec2f,
|
|
847
|
+
padding2: vec2f,
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
851
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
852
|
+
@group(0) @binding(2) var<uniform> uniforms: ChromaticUniforms;
|
|
853
|
+
|
|
854
|
+
@fragment
|
|
855
|
+
fn fs_chromatic(input: VertexOutput) -> @location(0) vec4f {
|
|
856
|
+
let uv = input.uv;
|
|
857
|
+
|
|
858
|
+
var rOffset = uniforms.redOffset * uniforms.intensity;
|
|
859
|
+
var gOffset = uniforms.greenOffset * uniforms.intensity;
|
|
860
|
+
var bOffset = uniforms.blueOffset * uniforms.intensity;
|
|
861
|
+
|
|
862
|
+
// Apply radial distortion if enabled
|
|
863
|
+
if (uniforms.radial > 0.5) {
|
|
864
|
+
let center = vec2f(0.5);
|
|
865
|
+
let dir = uv - center;
|
|
866
|
+
let dist = length(dir);
|
|
867
|
+
let radialFactor = dist * dist;
|
|
868
|
+
|
|
869
|
+
rOffset *= radialFactor;
|
|
870
|
+
gOffset *= radialFactor;
|
|
871
|
+
bOffset *= radialFactor;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
let r = textureSample(inputTexture, texSampler, uv + rOffset).r;
|
|
875
|
+
let g = textureSample(inputTexture, texSampler, uv + gOffset).g;
|
|
876
|
+
let b = textureSample(inputTexture, texSampler, uv + bOffset).b;
|
|
877
|
+
|
|
878
|
+
return vec4f(r, g, b, 1.0);
|
|
879
|
+
}
|
|
880
|
+
`
|
|
881
|
+
);
|
|
882
|
+
var DOF_SHADER = (
|
|
883
|
+
/* wgsl */
|
|
884
|
+
`
|
|
885
|
+
${SHADER_UTILS}
|
|
886
|
+
|
|
887
|
+
struct DOFUniforms {
|
|
888
|
+
focusDistance: f32,
|
|
889
|
+
focalLength: f32,
|
|
890
|
+
aperture: f32,
|
|
891
|
+
maxBlur: f32,
|
|
892
|
+
nearPlane: f32,
|
|
893
|
+
farPlane: f32,
|
|
894
|
+
padding: vec2f,
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
898
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
899
|
+
@group(0) @binding(2) var<uniform> uniforms: DOFUniforms;
|
|
900
|
+
@group(0) @binding(3) var depthTexture: texture_2d<f32>;
|
|
901
|
+
|
|
902
|
+
fn linearizeDepth(d: f32) -> f32 {
|
|
903
|
+
return uniforms.nearPlane * uniforms.farPlane /
|
|
904
|
+
(uniforms.farPlane - d * (uniforms.farPlane - uniforms.nearPlane));
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
fn circleOfConfusion(depth: f32) -> f32 {
|
|
908
|
+
let s1 = depth;
|
|
909
|
+
let s2 = uniforms.focusDistance;
|
|
910
|
+
let f = uniforms.focalLength;
|
|
911
|
+
let a = uniforms.aperture;
|
|
912
|
+
let coc = abs(a * f * (s2 - s1) / (s1 * (s2 - f)));
|
|
913
|
+
return clamp(coc, 0.0, uniforms.maxBlur);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
@fragment
|
|
917
|
+
fn fs_dof(input: VertexOutput) -> @location(0) vec4f {
|
|
918
|
+
let dims = vec2f(textureDimensions(inputTexture));
|
|
919
|
+
let texelSize = 1.0 / dims;
|
|
920
|
+
|
|
921
|
+
let rawDepth = textureSample(depthTexture, texSampler, input.uv).r;
|
|
922
|
+
let depth = linearizeDepth(rawDepth);
|
|
923
|
+
let coc = circleOfConfusion(depth);
|
|
924
|
+
|
|
925
|
+
// Disc blur with 16 samples in a Poisson-like pattern
|
|
926
|
+
let offsets = array<vec2f, 16>(
|
|
927
|
+
vec2f(-0.94201, -0.39906), vec2f( 0.94558, -0.76890),
|
|
928
|
+
vec2f(-0.09418, -0.92938), vec2f( 0.34495, 0.29387),
|
|
929
|
+
vec2f(-0.91588, 0.45771), vec2f(-0.81544, 0.00298),
|
|
930
|
+
vec2f(-0.38277, -0.56270), vec2f( 0.97484, 0.75648),
|
|
931
|
+
vec2f( 0.44323, -0.97511), vec2f( 0.53742, 0.01683),
|
|
932
|
+
vec2f(-0.26496, -0.01497), vec2f(-0.44693, 0.93910),
|
|
933
|
+
vec2f( 0.79197, 0.19090), vec2f(-0.24188, -0.99706),
|
|
934
|
+
vec2f( 0.04578, 0.53300), vec2f(-0.75738, -0.81580)
|
|
935
|
+
);
|
|
936
|
+
|
|
937
|
+
var color = vec4f(0.0);
|
|
938
|
+
var totalWeight = 0.0;
|
|
939
|
+
|
|
940
|
+
for (var i = 0u; i < 16u; i++) {
|
|
941
|
+
let sampleUV = input.uv + offsets[i] * texelSize * coc * 8.0;
|
|
942
|
+
let sampleColor = textureSample(inputTexture, texSampler, sampleUV);
|
|
943
|
+
let sampleDepth = linearizeDepth(textureSample(depthTexture, texSampler, sampleUV).r);
|
|
944
|
+
let sampleCoC = circleOfConfusion(sampleDepth);
|
|
945
|
+
let w = max(sampleCoC, coc * 0.2);
|
|
946
|
+
color += sampleColor * w;
|
|
947
|
+
totalWeight += w;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return color / totalWeight;
|
|
951
|
+
}
|
|
952
|
+
`
|
|
953
|
+
);
|
|
954
|
+
var SSAO_SHADER = (
|
|
955
|
+
/* wgsl */
|
|
956
|
+
`
|
|
957
|
+
${SHADER_UTILS}
|
|
958
|
+
|
|
959
|
+
struct SSAOUniforms {
|
|
960
|
+
radius: f32,
|
|
961
|
+
bias: f32,
|
|
962
|
+
samples: f32,
|
|
963
|
+
power: f32,
|
|
964
|
+
falloff: f32,
|
|
965
|
+
mode: f32, // 0 = hemisphere, 1 = hbao
|
|
966
|
+
bentNormals: f32, // 0 = off, 1 = on
|
|
967
|
+
spatialDenoise: f32, // 0 = off, 1 = on
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
971
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
972
|
+
@group(0) @binding(2) var<uniform> uniforms: SSAOUniforms;
|
|
973
|
+
@group(0) @binding(3) var depthTexture: texture_2d<f32>;
|
|
974
|
+
|
|
975
|
+
fn hash3(p: vec2f) -> vec3f {
|
|
976
|
+
let q = vec3f(
|
|
977
|
+
dot(p, vec2f(127.1, 311.7)),
|
|
978
|
+
dot(p, vec2f(269.5, 183.3)),
|
|
979
|
+
dot(p, vec2f(419.2, 371.9))
|
|
980
|
+
);
|
|
981
|
+
return fract(sin(q) * 43758.5453) * 2.0 - 1.0;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
fn reconstructNormal(uv: vec2f, texelSize: vec2f) -> vec3f {
|
|
985
|
+
let dc = textureSample(depthTexture, texSampler, uv).r;
|
|
986
|
+
let dl = textureSample(depthTexture, texSampler, uv - vec2f(texelSize.x, 0.0)).r;
|
|
987
|
+
let dr = textureSample(depthTexture, texSampler, uv + vec2f(texelSize.x, 0.0)).r;
|
|
988
|
+
let db = textureSample(depthTexture, texSampler, uv - vec2f(0.0, texelSize.y)).r;
|
|
989
|
+
let dt = textureSample(depthTexture, texSampler, uv + vec2f(0.0, texelSize.y)).r;
|
|
990
|
+
return normalize(vec3f(dl - dr, db - dt, 2.0 * texelSize.x));
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// HBAO: 8 directions \xD7 4 steps per direction = 32 samples
|
|
994
|
+
fn hbaoOcclusion(uv: vec2f, normal: vec3f, centerDepth: f32, texelSize: vec2f) -> vec2f {
|
|
995
|
+
var occlusion = 0.0;
|
|
996
|
+
var bentN = vec3f(0.0);
|
|
997
|
+
let directions = 8;
|
|
998
|
+
let stepsPerDir = 4;
|
|
999
|
+
let angleStep = 6.28318 / f32(directions);
|
|
1000
|
+
|
|
1001
|
+
for (var d = 0; d < directions; d++) {
|
|
1002
|
+
let angle = f32(d) * angleStep;
|
|
1003
|
+
let dir = vec2f(cos(angle), sin(angle));
|
|
1004
|
+
var maxHorizon = uniforms.bias;
|
|
1005
|
+
|
|
1006
|
+
for (var s = 1; s <= stepsPerDir; s++) {
|
|
1007
|
+
let stepScale = f32(s) / f32(stepsPerDir);
|
|
1008
|
+
let sampleOffset = dir * uniforms.radius * stepScale * texelSize * 8.0;
|
|
1009
|
+
let sampleUV = uv + sampleOffset;
|
|
1010
|
+
let sampleDepth = textureSample(depthTexture, texSampler, sampleUV).r;
|
|
1011
|
+
let depthDelta = centerDepth - sampleDepth;
|
|
1012
|
+
|
|
1013
|
+
if (depthDelta > uniforms.bias && depthDelta < uniforms.falloff) {
|
|
1014
|
+
let horizonAngle = depthDelta / (length(sampleOffset) * 500.0 + 0.001);
|
|
1015
|
+
maxHorizon = max(maxHorizon, horizonAngle);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
occlusion += maxHorizon;
|
|
1019
|
+
// Accumulate bent normal: direction of least occlusion
|
|
1020
|
+
let weight = 1.0 - min(maxHorizon * 2.0, 1.0);
|
|
1021
|
+
bentN += vec3f(dir * weight, weight);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
occlusion = 1.0 - pow(occlusion / f32(directions), uniforms.power);
|
|
1025
|
+
return vec2f(occlusion, length(bentN.xy));
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// 5\xD75 cross-bilateral spatial denoise
|
|
1029
|
+
fn spatialDenoise(uv: vec2f, centerOcclusion: f32, centerDepth: f32, centerNormal: vec3f, texelSize: vec2f) -> f32 {
|
|
1030
|
+
var sum = centerOcclusion;
|
|
1031
|
+
var totalWeight = 1.0;
|
|
1032
|
+
|
|
1033
|
+
for (var y = -2; y <= 2; y++) {
|
|
1034
|
+
for (var x = -2; x <= 2; x++) {
|
|
1035
|
+
if (x == 0 && y == 0) { continue; }
|
|
1036
|
+
let offset = vec2f(f32(x), f32(y)) * texelSize;
|
|
1037
|
+
let sampleUV = uv + offset;
|
|
1038
|
+
let sampleDepth = textureSample(depthTexture, texSampler, sampleUV).r;
|
|
1039
|
+
let sampleNormal = reconstructNormal(sampleUV, texelSize);
|
|
1040
|
+
|
|
1041
|
+
// Depth similarity weight
|
|
1042
|
+
let depthW = exp(-abs(centerDepth - sampleDepth) * 100.0);
|
|
1043
|
+
// Normal similarity weight
|
|
1044
|
+
let normalW = max(dot(centerNormal, sampleNormal), 0.0);
|
|
1045
|
+
// Spatial weight (Gaussian)
|
|
1046
|
+
let spatialW = exp(-f32(x * x + y * y) * 0.2);
|
|
1047
|
+
|
|
1048
|
+
let w = depthW * normalW * spatialW;
|
|
1049
|
+
// Re-sample occlusion at this location (simplified: use color channel)
|
|
1050
|
+
let sampleColor = textureSample(inputTexture, texSampler, sampleUV);
|
|
1051
|
+
let sampleOcclusion = luminance(sampleColor.rgb) / max(luminance(textureSample(inputTexture, texSampler, uv).rgb), 0.001);
|
|
1052
|
+
sum += clamp(sampleOcclusion, 0.0, 2.0) * w;
|
|
1053
|
+
totalWeight += w;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
return sum / totalWeight;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
@fragment
|
|
1061
|
+
fn fs_ssao(input: VertexOutput) -> @location(0) vec4f {
|
|
1062
|
+
let dims = vec2f(textureDimensions(inputTexture));
|
|
1063
|
+
let texelSize = 1.0 / dims;
|
|
1064
|
+
let color = textureSample(inputTexture, texSampler, input.uv);
|
|
1065
|
+
let centerDepth = textureSample(depthTexture, texSampler, input.uv).r;
|
|
1066
|
+
let normal = reconstructNormal(input.uv, texelSize);
|
|
1067
|
+
|
|
1068
|
+
var occlusion = 0.0;
|
|
1069
|
+
|
|
1070
|
+
if (uniforms.mode > 0.5) {
|
|
1071
|
+
// HBAO mode: 8 directions \xD7 4 steps
|
|
1072
|
+
let hbaoResult = hbaoOcclusion(input.uv, normal, centerDepth, texelSize);
|
|
1073
|
+
occlusion = hbaoResult.x;
|
|
1074
|
+
} else {
|
|
1075
|
+
// Standard hemisphere sampling
|
|
1076
|
+
let sampleCount = u32(uniforms.samples);
|
|
1077
|
+
var occ = 0.0;
|
|
1078
|
+
for (var i = 0u; i < sampleCount; i++) {
|
|
1079
|
+
let randSeed = input.uv * dims + vec2f(f32(i) * 7.0, f32(i) * 13.0);
|
|
1080
|
+
var sampleDir = normalize(hash3(randSeed));
|
|
1081
|
+
if (dot(sampleDir, normal) < 0.0) {
|
|
1082
|
+
sampleDir = -sampleDir;
|
|
1083
|
+
}
|
|
1084
|
+
let scale = f32(i + 1u) / f32(sampleCount);
|
|
1085
|
+
let sampleOffset = sampleDir * uniforms.radius * mix(0.1, 1.0, scale * scale);
|
|
1086
|
+
let sampleUV = input.uv + sampleOffset.xy * texelSize * 8.0;
|
|
1087
|
+
let sampleDepth = textureSample(depthTexture, texSampler, sampleUV).r;
|
|
1088
|
+
let rangeCheck = smoothstep(0.0, 1.0,
|
|
1089
|
+
uniforms.falloff / abs(centerDepth - sampleDepth + 0.0001));
|
|
1090
|
+
if (sampleDepth < centerDepth - uniforms.bias) {
|
|
1091
|
+
occ += rangeCheck;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
occlusion = 1.0 - pow(occ / f32(sampleCount), uniforms.power);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// Spatial denoise pass (applied inline for simplicity)
|
|
1098
|
+
if (uniforms.spatialDenoise > 0.5) {
|
|
1099
|
+
// Approximate denoise by blending with neighbors
|
|
1100
|
+
var blurred = occlusion;
|
|
1101
|
+
var tw = 1.0;
|
|
1102
|
+
for (var dy = -1; dy <= 1; dy++) {
|
|
1103
|
+
for (var dx = -1; dx <= 1; dx++) {
|
|
1104
|
+
if (dx == 0 && dy == 0) { continue; }
|
|
1105
|
+
let off = vec2f(f32(dx), f32(dy)) * texelSize;
|
|
1106
|
+
let sd = textureSample(depthTexture, texSampler, input.uv + off).r;
|
|
1107
|
+
let dw = exp(-abs(centerDepth - sd) * 50.0);
|
|
1108
|
+
let sn = reconstructNormal(input.uv + off, texelSize);
|
|
1109
|
+
let nw = max(dot(normal, sn), 0.0);
|
|
1110
|
+
let w = dw * nw;
|
|
1111
|
+
blurred += occlusion * w; // Approximation: use same occlusion
|
|
1112
|
+
tw += w;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
occlusion = blurred / tw;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
return vec4f(color.rgb * occlusion, color.a);
|
|
1119
|
+
}
|
|
1120
|
+
`
|
|
1121
|
+
);
|
|
1122
|
+
var FOG_SHADER = (
|
|
1123
|
+
/* wgsl */
|
|
1124
|
+
`
|
|
1125
|
+
${SHADER_UTILS}
|
|
1126
|
+
|
|
1127
|
+
struct FogUniforms {
|
|
1128
|
+
color: vec3f,
|
|
1129
|
+
density: f32,
|
|
1130
|
+
start: f32,
|
|
1131
|
+
end: f32,
|
|
1132
|
+
height: f32,
|
|
1133
|
+
heightFalloff: f32,
|
|
1134
|
+
mode: f32,
|
|
1135
|
+
padding: vec3f,
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
1139
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
1140
|
+
@group(0) @binding(2) var<uniform> uniforms: FogUniforms;
|
|
1141
|
+
@group(0) @binding(3) var depthTexture: texture_2d<f32>;
|
|
1142
|
+
|
|
1143
|
+
@fragment
|
|
1144
|
+
fn fs_fog(input: VertexOutput) -> @location(0) vec4f {
|
|
1145
|
+
let color = textureSample(inputTexture, texSampler, input.uv);
|
|
1146
|
+
let depth = textureSample(depthTexture, texSampler, input.uv).r;
|
|
1147
|
+
|
|
1148
|
+
// Compute fog factor based on mode
|
|
1149
|
+
var fogFactor = 0.0;
|
|
1150
|
+
let mode = u32(uniforms.mode);
|
|
1151
|
+
if (mode == 0u) {
|
|
1152
|
+
// Linear fog
|
|
1153
|
+
fogFactor = clamp((uniforms.end - depth) / (uniforms.end - uniforms.start), 0.0, 1.0);
|
|
1154
|
+
} else if (mode == 1u) {
|
|
1155
|
+
// Exponential fog
|
|
1156
|
+
fogFactor = exp(-uniforms.density * depth);
|
|
1157
|
+
} else {
|
|
1158
|
+
// Exponential-squared fog
|
|
1159
|
+
let d = uniforms.density * depth;
|
|
1160
|
+
fogFactor = exp(-d * d);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Height-based attenuation
|
|
1164
|
+
let heightUV = 1.0 - input.uv.y; // screen-space approximation of world height
|
|
1165
|
+
let heightFactor = exp(-max(heightUV - uniforms.height, 0.0) * uniforms.heightFalloff);
|
|
1166
|
+
fogFactor = mix(fogFactor, 1.0, 1.0 - heightFactor);
|
|
1167
|
+
|
|
1168
|
+
let foggedColor = mix(uniforms.color, color.rgb, fogFactor);
|
|
1169
|
+
return vec4f(foggedColor, color.a);
|
|
1170
|
+
}
|
|
1171
|
+
`
|
|
1172
|
+
);
|
|
1173
|
+
var MOTION_BLUR_SHADER = (
|
|
1174
|
+
/* wgsl */
|
|
1175
|
+
`
|
|
1176
|
+
${SHADER_UTILS}
|
|
1177
|
+
|
|
1178
|
+
struct MotionBlurUniforms {
|
|
1179
|
+
samples: f32,
|
|
1180
|
+
velocityScale: f32,
|
|
1181
|
+
maxVelocity: f32,
|
|
1182
|
+
intensity: f32,
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
1186
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
1187
|
+
@group(0) @binding(2) var<uniform> uniforms: MotionBlurUniforms;
|
|
1188
|
+
@group(0) @binding(3) var velocityTexture: texture_2d<f32>;
|
|
1189
|
+
|
|
1190
|
+
@fragment
|
|
1191
|
+
fn fs_motionblur(input: VertexOutput) -> @location(0) vec4f {
|
|
1192
|
+
let velocity = textureSample(velocityTexture, texSampler, input.uv).rg;
|
|
1193
|
+
|
|
1194
|
+
// Scale and clamp velocity
|
|
1195
|
+
var vel = velocity * uniforms.velocityScale;
|
|
1196
|
+
let speed = length(vel);
|
|
1197
|
+
if (speed > uniforms.maxVelocity) {
|
|
1198
|
+
vel = vel * (uniforms.maxVelocity / speed);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
let sampleCount = u32(uniforms.samples);
|
|
1202
|
+
var color = textureSample(inputTexture, texSampler, input.uv);
|
|
1203
|
+
var totalWeight = 1.0;
|
|
1204
|
+
|
|
1205
|
+
for (var i = 1u; i <= sampleCount; i++) {
|
|
1206
|
+
let t = (f32(i) / f32(sampleCount)) - 0.5;
|
|
1207
|
+
let sampleUV = input.uv + vel * t;
|
|
1208
|
+
let sampleColor = textureSample(inputTexture, texSampler, sampleUV);
|
|
1209
|
+
let w = 1.0 - abs(t) * 2.0; // Center-weighted
|
|
1210
|
+
color += sampleColor * w;
|
|
1211
|
+
totalWeight += w;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
let blurred = color / totalWeight;
|
|
1215
|
+
let original = textureSample(inputTexture, texSampler, input.uv);
|
|
1216
|
+
return mix(original, blurred, uniforms.intensity);
|
|
1217
|
+
}
|
|
1218
|
+
`
|
|
1219
|
+
);
|
|
1220
|
+
var COLOR_GRADE_SHADER = (
|
|
1221
|
+
/* wgsl */
|
|
1222
|
+
`
|
|
1223
|
+
${SHADER_UTILS}
|
|
1224
|
+
|
|
1225
|
+
struct ColorGradeUniforms {
|
|
1226
|
+
shadows: vec3f,
|
|
1227
|
+
shadowsOffset: f32,
|
|
1228
|
+
midtones: vec3f,
|
|
1229
|
+
highlightsOffset: f32,
|
|
1230
|
+
highlights: vec3f,
|
|
1231
|
+
hueShift: f32,
|
|
1232
|
+
temperature: f32,
|
|
1233
|
+
tint: f32,
|
|
1234
|
+
intensity: f32,
|
|
1235
|
+
lutIntensity: f32,
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
1239
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
1240
|
+
@group(0) @binding(2) var<uniform> uniforms: ColorGradeUniforms;
|
|
1241
|
+
|
|
1242
|
+
// RGB to HSL conversion
|
|
1243
|
+
fn rgbToHsl(c: vec3f) -> vec3f {
|
|
1244
|
+
let cMax = max(max(c.r, c.g), c.b);
|
|
1245
|
+
let cMin = min(min(c.r, c.g), c.b);
|
|
1246
|
+
let delta = cMax - cMin;
|
|
1247
|
+
|
|
1248
|
+
var h = 0.0;
|
|
1249
|
+
var s = 0.0;
|
|
1250
|
+
let l = (cMax + cMin) / 2.0;
|
|
1251
|
+
|
|
1252
|
+
if (delta > 0.0) {
|
|
1253
|
+
s = select(delta / (2.0 - cMax - cMin), delta / (cMax + cMin), l < 0.5);
|
|
1254
|
+
|
|
1255
|
+
if (cMax == c.r) {
|
|
1256
|
+
h = (c.g - c.b) / delta + select(0.0, 6.0, c.g < c.b);
|
|
1257
|
+
} else if (cMax == c.g) {
|
|
1258
|
+
h = (c.b - c.r) / delta + 2.0;
|
|
1259
|
+
} else {
|
|
1260
|
+
h = (c.r - c.g) / delta + 4.0;
|
|
1261
|
+
}
|
|
1262
|
+
h /= 6.0;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
return vec3f(h, s, l);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
fn hue2rgb(p: f32, q: f32, t: f32) -> f32 {
|
|
1269
|
+
var tt = t;
|
|
1270
|
+
if (tt < 0.0) { tt += 1.0; }
|
|
1271
|
+
if (tt > 1.0) { tt -= 1.0; }
|
|
1272
|
+
if (tt < 1.0/6.0) { return p + (q - p) * 6.0 * tt; }
|
|
1273
|
+
if (tt < 1.0/2.0) { return q; }
|
|
1274
|
+
if (tt < 2.0/3.0) { return p + (q - p) * (2.0/3.0 - tt) * 6.0; }
|
|
1275
|
+
return p;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
fn hslToRgb(hsl: vec3f) -> vec3f {
|
|
1279
|
+
if (hsl.y == 0.0) {
|
|
1280
|
+
return vec3f(hsl.z);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
let q = select(hsl.z + hsl.y - hsl.z * hsl.y, hsl.z * (1.0 + hsl.y), hsl.z < 0.5);
|
|
1284
|
+
let p = 2.0 * hsl.z - q;
|
|
1285
|
+
|
|
1286
|
+
return vec3f(
|
|
1287
|
+
hue2rgb(p, q, hsl.x + 1.0/3.0),
|
|
1288
|
+
hue2rgb(p, q, hsl.x),
|
|
1289
|
+
hue2rgb(p, q, hsl.x - 1.0/3.0)
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// Temperature/tint adjustment
|
|
1294
|
+
fn adjustTemperature(color: vec3f, temp: f32, tint: f32) -> vec3f {
|
|
1295
|
+
var result = color;
|
|
1296
|
+
// Warm (positive) = more red, less blue
|
|
1297
|
+
result.r += temp * 0.1;
|
|
1298
|
+
result.b -= temp * 0.1;
|
|
1299
|
+
// Tint: positive = more green
|
|
1300
|
+
result.g += tint * 0.1;
|
|
1301
|
+
return clamp(result, vec3f(0.0), vec3f(1.0));
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
@fragment
|
|
1305
|
+
fn fs_colorgrade(input: VertexOutput) -> @location(0) vec4f {
|
|
1306
|
+
var color = textureSample(inputTexture, texSampler, input.uv).rgb;
|
|
1307
|
+
|
|
1308
|
+
let lum = luminance(color);
|
|
1309
|
+
|
|
1310
|
+
// Shadows/Midtones/Highlights
|
|
1311
|
+
let shadowWeight = 1.0 - smoothstep(0.0, 0.33, lum);
|
|
1312
|
+
let highlightWeight = smoothstep(0.66, 1.0, lum);
|
|
1313
|
+
let midtoneWeight = 1.0 - shadowWeight - highlightWeight;
|
|
1314
|
+
|
|
1315
|
+
color += uniforms.shadows * shadowWeight;
|
|
1316
|
+
color += uniforms.midtones * midtoneWeight;
|
|
1317
|
+
color += uniforms.highlights * highlightWeight;
|
|
1318
|
+
|
|
1319
|
+
// Hue shift
|
|
1320
|
+
if (abs(uniforms.hueShift) > 0.001) {
|
|
1321
|
+
var hsl = rgbToHsl(color);
|
|
1322
|
+
hsl.x = fract(hsl.x + uniforms.hueShift);
|
|
1323
|
+
color = hslToRgb(hsl);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// Temperature and tint
|
|
1327
|
+
color = adjustTemperature(color, uniforms.temperature, uniforms.tint);
|
|
1328
|
+
|
|
1329
|
+
return vec4f(color, 1.0);
|
|
1330
|
+
}
|
|
1331
|
+
`
|
|
1332
|
+
);
|
|
1333
|
+
var BLIT_SHADER = (
|
|
1334
|
+
/* wgsl */
|
|
1335
|
+
`
|
|
1336
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
1337
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
1338
|
+
|
|
1339
|
+
@fragment
|
|
1340
|
+
fn fs_blit(input: VertexOutput) -> @location(0) vec4f {
|
|
1341
|
+
return textureSample(inputTexture, texSampler, input.uv);
|
|
1342
|
+
}
|
|
1343
|
+
`
|
|
1344
|
+
);
|
|
1345
|
+
var CAUSTICS_SHADER = (
|
|
1346
|
+
/* wgsl */
|
|
1347
|
+
`
|
|
1348
|
+
${SHADER_UTILS}
|
|
1349
|
+
|
|
1350
|
+
struct CausticsUniforms {
|
|
1351
|
+
intensity: f32,
|
|
1352
|
+
scale: f32,
|
|
1353
|
+
speed: f32,
|
|
1354
|
+
time: f32,
|
|
1355
|
+
color: vec3f,
|
|
1356
|
+
depthFade: f32,
|
|
1357
|
+
waterLevel: f32,
|
|
1358
|
+
dispersion: f32,
|
|
1359
|
+
foamIntensity: f32,
|
|
1360
|
+
shadowStrength: f32,
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
1364
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
1365
|
+
@group(0) @binding(2) var<uniform> uniforms: CausticsUniforms;
|
|
1366
|
+
@group(0) @binding(3) var depthTexture: texture_2d<f32>;
|
|
1367
|
+
|
|
1368
|
+
fn voronoiDist(p: vec2f) -> f32 {
|
|
1369
|
+
let i = floor(p);
|
|
1370
|
+
let f = fract(p);
|
|
1371
|
+
var md = 1.0;
|
|
1372
|
+
for (var y = -1; y <= 1; y++) {
|
|
1373
|
+
for (var x = -1; x <= 1; x++) {
|
|
1374
|
+
let n = vec2f(f32(x), f32(y));
|
|
1375
|
+
let h1 = fract(sin(dot(i + n, vec2f(127.1, 311.7))) * 43758.5453);
|
|
1376
|
+
let h2 = fract(sin(dot(i + n, vec2f(269.5, 183.3))) * 43758.5453);
|
|
1377
|
+
let pt = n + vec2f(h1, h2) - f;
|
|
1378
|
+
md = min(md, dot(pt, pt));
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
return sqrt(md);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// Refractive caustic with IoR-based convergence
|
|
1385
|
+
fn refractiveCausticPP(uv: vec2f, time: f32, scale: f32, ior: f32) -> f32 {
|
|
1386
|
+
let eps = 0.01;
|
|
1387
|
+
let h0 = voronoiDist(uv * scale + vec2f(time * 0.3, time * 0.7));
|
|
1388
|
+
let hx = voronoiDist((uv + vec2f(eps, 0.0)) * scale + vec2f(time * 0.3, time * 0.7));
|
|
1389
|
+
let hy = voronoiDist((uv + vec2f(0.0, eps)) * scale + vec2f(time * 0.3, time * 0.7));
|
|
1390
|
+
let grad = vec2f(hx - h0, hy - h0) / eps;
|
|
1391
|
+
let refracted = grad * (1.0 / ior - 1.0);
|
|
1392
|
+
let convergence = voronoiDist((uv + refracted * 0.1) * scale * 1.3 + vec2f(-time * 0.5, time * 0.4));
|
|
1393
|
+
return pow(1.0 - convergence, 4.0);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// Turbulence-driven foam
|
|
1397
|
+
fn foamNoise(uv: vec2f, time: f32, scale: f32) -> f32 {
|
|
1398
|
+
let n1 = fract(sin(dot(floor(uv * scale * 4.0), vec2f(127.1, 311.7))) * 43758.5453);
|
|
1399
|
+
let n2 = fract(sin(dot(floor(uv * scale * 8.0 + vec2f(time * 0.5, 0.0)), vec2f(269.5, 183.3))) * 43758.5453);
|
|
1400
|
+
let turbulence = abs(n1 * 2.0 - 1.0) + abs(n2 * 2.0 - 1.0) * 0.5;
|
|
1401
|
+
return smoothstep(0.8, 1.2, turbulence);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
@fragment
|
|
1405
|
+
fn fs_caustics(input: VertexOutput) -> @location(0) vec4f {
|
|
1406
|
+
let color = textureSample(inputTexture, texSampler, input.uv);
|
|
1407
|
+
let depth = textureSample(depthTexture, texSampler, input.uv).r;
|
|
1408
|
+
|
|
1409
|
+
let worldY = 1.0 - input.uv.y;
|
|
1410
|
+
if (worldY > uniforms.waterLevel) {
|
|
1411
|
+
return color;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
let depthFactor = exp(-depth * uniforms.depthFade);
|
|
1415
|
+
var causticColor = vec3f(0.0);
|
|
1416
|
+
|
|
1417
|
+
if (uniforms.dispersion > 0.001) {
|
|
1418
|
+
// Chromatic dispersion: separate R/G/B with different IoR
|
|
1419
|
+
let baseIoR = 1.33;
|
|
1420
|
+
let t = uniforms.time * uniforms.speed;
|
|
1421
|
+
let rC = refractiveCausticPP(input.uv, t, uniforms.scale, baseIoR - uniforms.dispersion);
|
|
1422
|
+
let gC = refractiveCausticPP(input.uv, t, uniforms.scale, baseIoR);
|
|
1423
|
+
let bC = refractiveCausticPP(input.uv, t, uniforms.scale, baseIoR + uniforms.dispersion);
|
|
1424
|
+
causticColor = vec3f(rC, gC, bC) * uniforms.color * uniforms.intensity * depthFactor;
|
|
1425
|
+
} else {
|
|
1426
|
+
// Standard dual-layer caustics
|
|
1427
|
+
let uv1 = input.uv * uniforms.scale + vec2f(uniforms.time * uniforms.speed * 0.3, uniforms.time * uniforms.speed * 0.7);
|
|
1428
|
+
let uv2 = input.uv * uniforms.scale * 1.3 + vec2f(-uniforms.time * uniforms.speed * 0.5, uniforms.time * uniforms.speed * 0.4);
|
|
1429
|
+
let c1 = voronoiDist(uv1);
|
|
1430
|
+
let c2 = voronoiDist(uv2);
|
|
1431
|
+
let caustic = pow(1.0 - c1, 3.0) * pow(1.0 - c2, 3.0);
|
|
1432
|
+
causticColor = uniforms.color * caustic * uniforms.intensity * depthFactor;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// Foam overlay
|
|
1436
|
+
let foam = foamNoise(input.uv, uniforms.time * uniforms.speed, uniforms.scale) * uniforms.foamIntensity;
|
|
1437
|
+
|
|
1438
|
+
// Caustic shadows: darken where caustics are absent
|
|
1439
|
+
let causticLum = dot(causticColor, vec3f(0.333));
|
|
1440
|
+
let shadow = mix(1.0, 1.0 - uniforms.shadowStrength, (1.0 - causticLum) * depthFactor);
|
|
1441
|
+
|
|
1442
|
+
let result = color.rgb * shadow + causticColor + vec3f(foam);
|
|
1443
|
+
return vec4f(result, color.a);
|
|
1444
|
+
}
|
|
1445
|
+
`
|
|
1446
|
+
);
|
|
1447
|
+
var SSR_SHADER = (
|
|
1448
|
+
/* wgsl */
|
|
1449
|
+
`
|
|
1450
|
+
${SHADER_UTILS}
|
|
1451
|
+
|
|
1452
|
+
struct SSRUniforms {
|
|
1453
|
+
maxSteps: f32,
|
|
1454
|
+
stepSize: f32,
|
|
1455
|
+
thickness: f32,
|
|
1456
|
+
roughnessFade: f32,
|
|
1457
|
+
edgeFade: f32,
|
|
1458
|
+
intensity: f32,
|
|
1459
|
+
roughnessBlur: f32,
|
|
1460
|
+
fresnelStrength: f32,
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
1464
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
1465
|
+
@group(0) @binding(2) var<uniform> uniforms: SSRUniforms;
|
|
1466
|
+
@group(0) @binding(3) var depthTexture: texture_2d<f32>;
|
|
1467
|
+
@group(0) @binding(4) var normalTexture: texture_2d<f32>;
|
|
1468
|
+
|
|
1469
|
+
@fragment
|
|
1470
|
+
fn fs_ssr(input: VertexOutput) -> @location(0) vec4f {
|
|
1471
|
+
let color = textureSample(inputTexture, texSampler, input.uv);
|
|
1472
|
+
let depth = textureSample(depthTexture, texSampler, input.uv).r;
|
|
1473
|
+
let texSize = vec2f(textureDimensions(inputTexture));
|
|
1474
|
+
let texel = 1.0 / texSize;
|
|
1475
|
+
|
|
1476
|
+
// Reconstruct normal from depth
|
|
1477
|
+
let dc = depth;
|
|
1478
|
+
let dl = textureSample(depthTexture, texSampler, input.uv - vec2f(texel.x, 0.0)).r;
|
|
1479
|
+
let dr = textureSample(depthTexture, texSampler, input.uv + vec2f(texel.x, 0.0)).r;
|
|
1480
|
+
let db = textureSample(depthTexture, texSampler, input.uv - vec2f(0.0, texel.y)).r;
|
|
1481
|
+
let dt = textureSample(depthTexture, texSampler, input.uv + vec2f(0.0, texel.y)).r;
|
|
1482
|
+
let normal = normalize(vec3f(dl - dr, db - dt, 2.0 * texel.x));
|
|
1483
|
+
|
|
1484
|
+
// View direction (simplified \u2014 assumes forward-facing camera)
|
|
1485
|
+
let viewDir = normalize(vec3f(input.uv * 2.0 - 1.0, -1.0));
|
|
1486
|
+
|
|
1487
|
+
// Reflect view around normal
|
|
1488
|
+
let reflectDir = reflect(viewDir, normal);
|
|
1489
|
+
let stepDir = reflectDir.xy * uniforms.stepSize;
|
|
1490
|
+
|
|
1491
|
+
var hitUV = input.uv;
|
|
1492
|
+
var hit = false;
|
|
1493
|
+
let steps = i32(uniforms.maxSteps);
|
|
1494
|
+
|
|
1495
|
+
for (var i = 1; i <= steps; i++) {
|
|
1496
|
+
hitUV += stepDir;
|
|
1497
|
+
|
|
1498
|
+
// Bounds check
|
|
1499
|
+
if (hitUV.x < 0.0 || hitUV.x > 1.0 || hitUV.y < 0.0 || hitUV.y > 1.0) { break; }
|
|
1500
|
+
|
|
1501
|
+
let sampleDepth = textureSample(depthTexture, texSampler, hitUV).r;
|
|
1502
|
+
let expectedDepth = depth + f32(i) * uniforms.stepSize;
|
|
1503
|
+
|
|
1504
|
+
if (expectedDepth > sampleDepth && expectedDepth - sampleDepth < uniforms.thickness) {
|
|
1505
|
+
hit = true;
|
|
1506
|
+
|
|
1507
|
+
// Binary refinement (4 steps)
|
|
1508
|
+
var refineStep = stepDir * 0.5;
|
|
1509
|
+
for (var j = 0; j < 4; j++) {
|
|
1510
|
+
hitUV -= refineStep;
|
|
1511
|
+
let rd = textureSample(depthTexture, texSampler, hitUV).r;
|
|
1512
|
+
let re = depth + length(hitUV - input.uv) / uniforms.stepSize * uniforms.stepSize;
|
|
1513
|
+
if (re > rd) {
|
|
1514
|
+
hitUV += refineStep;
|
|
1515
|
+
}
|
|
1516
|
+
refineStep *= 0.5;
|
|
1517
|
+
}
|
|
1518
|
+
break;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
if (!hit) { return color; }
|
|
1523
|
+
|
|
1524
|
+
// Roughness blur: golden-angle 8-sample blur at hit point scaled by roughness
|
|
1525
|
+
var reflectionColor = vec3f(0.0);
|
|
1526
|
+
if (uniforms.roughnessBlur > 0.001) {
|
|
1527
|
+
let blurRadius = uniforms.roughnessBlur * 0.01;
|
|
1528
|
+
let goldenAngle = 2.399963;
|
|
1529
|
+
var totalW = 0.0;
|
|
1530
|
+
for (var s = 0; s < 8; s++) {
|
|
1531
|
+
let angle = f32(s) * goldenAngle;
|
|
1532
|
+
let r = sqrt(f32(s + 1) / 8.0) * blurRadius;
|
|
1533
|
+
let blurOffset = vec2f(cos(angle), sin(angle)) * r;
|
|
1534
|
+
let sampleC = textureSample(inputTexture, texSampler, hitUV + blurOffset).rgb;
|
|
1535
|
+
let w = 1.0 - f32(s) / 8.0;
|
|
1536
|
+
reflectionColor += sampleC * w;
|
|
1537
|
+
totalW += w;
|
|
1538
|
+
}
|
|
1539
|
+
reflectionColor /= totalW;
|
|
1540
|
+
} else {
|
|
1541
|
+
reflectionColor = textureSample(inputTexture, texSampler, hitUV).rgb;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// Schlick Fresnel weighting
|
|
1545
|
+
let cosTheta = max(dot(-viewDir, normal), 0.0);
|
|
1546
|
+
let f0 = 0.04; // dielectric
|
|
1547
|
+
let fresnel = f0 + (1.0 - f0) * pow(1.0 - cosTheta, 5.0);
|
|
1548
|
+
let fresnelWeight = mix(1.0, fresnel, uniforms.fresnelStrength);
|
|
1549
|
+
|
|
1550
|
+
// Edge fade
|
|
1551
|
+
let edgeDist = max(abs(hitUV.x - 0.5), abs(hitUV.y - 0.5)) * 2.0;
|
|
1552
|
+
let edgeFade = 1.0 - pow(clamp(edgeDist, 0.0, 1.0), uniforms.edgeFade);
|
|
1553
|
+
|
|
1554
|
+
// Distance fade
|
|
1555
|
+
let travelDist = length(hitUV - input.uv);
|
|
1556
|
+
let distFade = 1.0 - clamp(travelDist * 2.0, 0.0, 1.0);
|
|
1557
|
+
|
|
1558
|
+
let reflectionMask = edgeFade * distFade * uniforms.intensity * fresnelWeight;
|
|
1559
|
+
return vec4f(mix(color.rgb, reflectionColor, reflectionMask), color.a);
|
|
1560
|
+
}
|
|
1561
|
+
`
|
|
1562
|
+
);
|
|
1563
|
+
var SSGI_SHADER = (
|
|
1564
|
+
/* wgsl */
|
|
1565
|
+
`
|
|
1566
|
+
${SHADER_UTILS}
|
|
1567
|
+
|
|
1568
|
+
struct SSGIUniforms {
|
|
1569
|
+
radius: f32,
|
|
1570
|
+
samples: f32,
|
|
1571
|
+
bounceIntensity: f32,
|
|
1572
|
+
falloff: f32,
|
|
1573
|
+
time: f32,
|
|
1574
|
+
intensity: f32,
|
|
1575
|
+
temporalBlend: f32,
|
|
1576
|
+
spatialDenoise: f32,
|
|
1577
|
+
multiBounce: f32,
|
|
1578
|
+
padding: vec3f,
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
@group(0) @binding(0) var texSampler: sampler;
|
|
1582
|
+
@group(0) @binding(1) var inputTexture: texture_2d<f32>;
|
|
1583
|
+
@group(0) @binding(2) var<uniform> uniforms: SSGIUniforms;
|
|
1584
|
+
@group(0) @binding(3) var depthTexture: texture_2d<f32>;
|
|
1585
|
+
|
|
1586
|
+
@fragment
|
|
1587
|
+
fn fs_ssgi(input: VertexOutput) -> @location(0) vec4f {
|
|
1588
|
+
let color = textureSample(inputTexture, texSampler, input.uv);
|
|
1589
|
+
let centerDepth = textureSample(depthTexture, texSampler, input.uv).r;
|
|
1590
|
+
let texSize = vec2f(textureDimensions(inputTexture));
|
|
1591
|
+
let texel = 1.0 / texSize;
|
|
1592
|
+
|
|
1593
|
+
// Reconstruct normal from depth
|
|
1594
|
+
let dl = textureSample(depthTexture, texSampler, input.uv - vec2f(texel.x, 0.0)).r;
|
|
1595
|
+
let dr = textureSample(depthTexture, texSampler, input.uv + vec2f(texel.x, 0.0)).r;
|
|
1596
|
+
let db = textureSample(depthTexture, texSampler, input.uv - vec2f(0.0, texel.y)).r;
|
|
1597
|
+
let dt = textureSample(depthTexture, texSampler, input.uv + vec2f(0.0, texel.y)).r;
|
|
1598
|
+
let normal = normalize(vec3f(dl - dr, db - dt, 2.0 * texel.x));
|
|
1599
|
+
|
|
1600
|
+
var indirect = vec3f(0.0);
|
|
1601
|
+
let sampleCount = i32(uniforms.samples);
|
|
1602
|
+
let goldenAngle = 2.399963;
|
|
1603
|
+
|
|
1604
|
+
for (var i = 0; i < sampleCount; i++) {
|
|
1605
|
+
let fi = f32(i);
|
|
1606
|
+
let r = sqrt(fi / uniforms.samples) * uniforms.radius;
|
|
1607
|
+
let theta = fi * goldenAngle + uniforms.time * 0.1; // Slight temporal jitter
|
|
1608
|
+
let offset = vec2f(cos(theta), sin(theta)) * r * texel * 8.0;
|
|
1609
|
+
let sampleUV = input.uv + offset;
|
|
1610
|
+
|
|
1611
|
+
let sampleColor = textureSample(inputTexture, texSampler, sampleUV).rgb;
|
|
1612
|
+
let sampleDepth = textureSample(depthTexture, texSampler, sampleUV).r;
|
|
1613
|
+
|
|
1614
|
+
// Weight by depth proximity (nearby surfaces contribute more)
|
|
1615
|
+
let depthDiff = abs(centerDepth - sampleDepth);
|
|
1616
|
+
let depthWeight = exp(-depthDiff * uniforms.falloff * 10.0);
|
|
1617
|
+
|
|
1618
|
+
// Cosine weight: approximate normal-based falloff
|
|
1619
|
+
let sampleDir = normalize(vec3f(offset, 0.05));
|
|
1620
|
+
let cosWeight = max(dot(sampleDir, normal), 0.0);
|
|
1621
|
+
|
|
1622
|
+
indirect += sampleColor * depthWeight * cosWeight;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
indirect /= uniforms.samples;
|
|
1626
|
+
indirect *= uniforms.bounceIntensity;
|
|
1627
|
+
|
|
1628
|
+
// Multi-bounce approximation: self-illumination feedback
|
|
1629
|
+
if (uniforms.multiBounce > 0.001) {
|
|
1630
|
+
indirect *= (1.0 + uniforms.multiBounce * luminance(indirect));
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// Spatial denoise: 3\xD73 edge-stopping cross-bilateral filter
|
|
1634
|
+
if (uniforms.spatialDenoise > 0.5) {
|
|
1635
|
+
var denoised = indirect;
|
|
1636
|
+
var tw = 1.0;
|
|
1637
|
+
for (var dy = -1; dy <= 1; dy++) {
|
|
1638
|
+
for (var dx = -1; dx <= 1; dx++) {
|
|
1639
|
+
if (dx == 0 && dy == 0) { continue; }
|
|
1640
|
+
let off = vec2f(f32(dx), f32(dy)) * texel;
|
|
1641
|
+
let sd = textureSample(depthTexture, texSampler, input.uv + off).r;
|
|
1642
|
+
// Depth weight
|
|
1643
|
+
let dw = exp(-abs(centerDepth - sd) * uniforms.falloff * 10.0);
|
|
1644
|
+
// Normal weight
|
|
1645
|
+
let snl = textureSample(depthTexture, texSampler, input.uv + off - vec2f(texel.x, 0.0)).r;
|
|
1646
|
+
let snr = textureSample(depthTexture, texSampler, input.uv + off + vec2f(texel.x, 0.0)).r;
|
|
1647
|
+
let snb = textureSample(depthTexture, texSampler, input.uv + off - vec2f(0.0, texel.y)).r;
|
|
1648
|
+
let snt = textureSample(depthTexture, texSampler, input.uv + off + vec2f(0.0, texel.y)).r;
|
|
1649
|
+
let sn = normalize(vec3f(snl - snr, snb - snt, 2.0 * texel.x));
|
|
1650
|
+
let nw = max(dot(normal, sn), 0.0);
|
|
1651
|
+
let w = dw * nw;
|
|
1652
|
+
// Sample neighbor's indirect (approximation: use color luminance ratio)
|
|
1653
|
+
let neighborColor = textureSample(inputTexture, texSampler, input.uv + off).rgb;
|
|
1654
|
+
denoised += neighborColor * uniforms.bounceIntensity * w * 0.5;
|
|
1655
|
+
tw += w;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
indirect = denoised / tw;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
var result = color.rgb + indirect * uniforms.intensity;
|
|
1662
|
+
|
|
1663
|
+
// Temporal blend: mix with previous frame color (approximation using current frame offset)
|
|
1664
|
+
if (uniforms.temporalBlend > 0.001) {
|
|
1665
|
+
// Approximate temporal reprojection by blending with slightly jittered sample
|
|
1666
|
+
let temporalUV = input.uv + vec2f(sin(uniforms.time * 31.0), cos(uniforms.time * 37.0)) * texel * 0.5;
|
|
1667
|
+
let prevColor = textureSample(inputTexture, texSampler, temporalUV).rgb;
|
|
1668
|
+
result = mix(result, prevColor + indirect * uniforms.intensity * 0.5, uniforms.temporalBlend * 0.3);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
return vec4f(result, color.a);
|
|
1672
|
+
}
|
|
1673
|
+
`
|
|
1674
|
+
);
|
|
1675
|
+
function buildEffectShader(fragmentShader) {
|
|
1676
|
+
return `${FULLSCREEN_VERTEX_SHADER}
|
|
1677
|
+
${SHADER_UTILS}
|
|
1678
|
+
${fragmentShader}`;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// src/rendering/postprocess/PostProcessEffect.ts
|
|
1682
|
+
var PostProcessEffect = class {
|
|
1683
|
+
type;
|
|
1684
|
+
name;
|
|
1685
|
+
params;
|
|
1686
|
+
pipeline = null;
|
|
1687
|
+
uniformBuffer = null;
|
|
1688
|
+
bindGroup = null;
|
|
1689
|
+
sampler = null;
|
|
1690
|
+
_initialized = false;
|
|
1691
|
+
constructor(type, name, params) {
|
|
1692
|
+
this.type = type;
|
|
1693
|
+
this.name = name ?? type;
|
|
1694
|
+
this.params = { ...getDefaultParams(type), ...params };
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Check if effect is enabled
|
|
1698
|
+
*/
|
|
1699
|
+
get enabled() {
|
|
1700
|
+
return this.params.enabled;
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Enable/disable effect
|
|
1704
|
+
*/
|
|
1705
|
+
set enabled(value) {
|
|
1706
|
+
this.params.enabled = value;
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Get effect intensity
|
|
1710
|
+
*/
|
|
1711
|
+
get intensity() {
|
|
1712
|
+
return this.params.intensity;
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Set effect intensity
|
|
1716
|
+
*/
|
|
1717
|
+
set intensity(value) {
|
|
1718
|
+
this.params.intensity = Math.max(0, value);
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Get current parameters
|
|
1722
|
+
*/
|
|
1723
|
+
getParams() {
|
|
1724
|
+
return this.params;
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Update parameters
|
|
1728
|
+
*/
|
|
1729
|
+
setParams(params) {
|
|
1730
|
+
this.params = { ...this.params, ...params };
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Check if effect is initialized
|
|
1734
|
+
*/
|
|
1735
|
+
get initialized() {
|
|
1736
|
+
return this._initialized;
|
|
1737
|
+
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Initialize GPU resources
|
|
1740
|
+
*/
|
|
1741
|
+
async initialize(device) {
|
|
1742
|
+
if (this._initialized) return;
|
|
1743
|
+
const uniformSize = UNIFORM_SIZES[this.type];
|
|
1744
|
+
this.uniformBuffer = device.createBuffer({
|
|
1745
|
+
size: uniformSize,
|
|
1746
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
1747
|
+
label: `${this.name}_uniforms`
|
|
1748
|
+
});
|
|
1749
|
+
this.sampler = device.createSampler({
|
|
1750
|
+
magFilter: "linear",
|
|
1751
|
+
minFilter: "linear",
|
|
1752
|
+
addressModeU: "clamp-to-edge",
|
|
1753
|
+
addressModeV: "clamp-to-edge",
|
|
1754
|
+
label: `${this.name}_sampler`
|
|
1755
|
+
});
|
|
1756
|
+
await this.createPipeline(device);
|
|
1757
|
+
this._initialized = true;
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Dispose GPU resources
|
|
1761
|
+
*/
|
|
1762
|
+
dispose() {
|
|
1763
|
+
this.uniformBuffer?.destroy();
|
|
1764
|
+
this.uniformBuffer = null;
|
|
1765
|
+
this.pipeline = null;
|
|
1766
|
+
this.bindGroup = null;
|
|
1767
|
+
this.sampler = null;
|
|
1768
|
+
this._initialized = false;
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
var BloomEffect = class extends PostProcessEffect {
|
|
1772
|
+
downsamplePipelines = [];
|
|
1773
|
+
upsamplePipelines = [];
|
|
1774
|
+
mipViews = [];
|
|
1775
|
+
mipTexture = null;
|
|
1776
|
+
constructor(params) {
|
|
1777
|
+
super("bloom", "Bloom", params);
|
|
1778
|
+
}
|
|
1779
|
+
async createPipeline(device) {
|
|
1780
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
1781
|
+
label: "bloom_bind_group_layout",
|
|
1782
|
+
entries: [
|
|
1783
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
|
|
1784
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
1785
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }
|
|
1786
|
+
]
|
|
1787
|
+
});
|
|
1788
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
1789
|
+
label: "bloom_pipeline_layout",
|
|
1790
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
1791
|
+
});
|
|
1792
|
+
const shaderModule = device.createShaderModule({
|
|
1793
|
+
label: "bloom_shader",
|
|
1794
|
+
code: FULLSCREEN_VERTEX_SHADER + BLOOM_SHADER
|
|
1795
|
+
});
|
|
1796
|
+
this.pipeline = device.createRenderPipeline({
|
|
1797
|
+
label: "bloom_pipeline",
|
|
1798
|
+
layout: pipelineLayout,
|
|
1799
|
+
vertex: {
|
|
1800
|
+
module: shaderModule,
|
|
1801
|
+
entryPoint: "vs_main"
|
|
1802
|
+
},
|
|
1803
|
+
fragment: {
|
|
1804
|
+
module: shaderModule,
|
|
1805
|
+
entryPoint: "fs_bloom",
|
|
1806
|
+
targets: [{ format: "rgba16float" }]
|
|
1807
|
+
},
|
|
1808
|
+
primitive: { topology: "triangle-list" }
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
updateUniforms(device, frameData) {
|
|
1812
|
+
if (!this.uniformBuffer) return;
|
|
1813
|
+
const p = this.params;
|
|
1814
|
+
const data = new Float32Array([
|
|
1815
|
+
p.intensity,
|
|
1816
|
+
p.threshold,
|
|
1817
|
+
p.softThreshold,
|
|
1818
|
+
p.radius,
|
|
1819
|
+
p.iterations,
|
|
1820
|
+
p.anamorphic,
|
|
1821
|
+
p.highQuality ? 1 : 0,
|
|
1822
|
+
0,
|
|
1823
|
+
// padding
|
|
1824
|
+
frameData.time,
|
|
1825
|
+
frameData.deltaTime,
|
|
1826
|
+
0,
|
|
1827
|
+
0
|
|
1828
|
+
// padding
|
|
1829
|
+
]);
|
|
1830
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, data);
|
|
1831
|
+
}
|
|
1832
|
+
render(context) {
|
|
1833
|
+
if (!this.enabled || !this.pipeline || !this.uniformBuffer || !this.sampler) return;
|
|
1834
|
+
this.updateUniforms(context.device, context.frameData);
|
|
1835
|
+
const bindGroup = context.device.createBindGroup({
|
|
1836
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
1837
|
+
entries: [
|
|
1838
|
+
{ binding: 0, resource: this.sampler },
|
|
1839
|
+
{ binding: 1, resource: context.input.view },
|
|
1840
|
+
{ binding: 2, resource: { buffer: this.uniformBuffer } }
|
|
1841
|
+
]
|
|
1842
|
+
});
|
|
1843
|
+
const passEncoder = context.commandEncoder.beginRenderPass({
|
|
1844
|
+
colorAttachments: [
|
|
1845
|
+
{
|
|
1846
|
+
view: context.output.view,
|
|
1847
|
+
loadOp: "clear",
|
|
1848
|
+
storeOp: "store",
|
|
1849
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 }
|
|
1850
|
+
}
|
|
1851
|
+
]
|
|
1852
|
+
});
|
|
1853
|
+
passEncoder.setPipeline(this.pipeline);
|
|
1854
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
1855
|
+
passEncoder.draw(3);
|
|
1856
|
+
passEncoder.end();
|
|
1857
|
+
}
|
|
1858
|
+
dispose() {
|
|
1859
|
+
super.dispose();
|
|
1860
|
+
this.mipTexture?.destroy();
|
|
1861
|
+
this.mipTexture = null;
|
|
1862
|
+
this.mipViews = [];
|
|
1863
|
+
this.downsamplePipelines = [];
|
|
1864
|
+
this.upsamplePipelines = [];
|
|
1865
|
+
}
|
|
1866
|
+
};
|
|
1867
|
+
var ToneMapEffect = class extends PostProcessEffect {
|
|
1868
|
+
constructor(params) {
|
|
1869
|
+
super("tonemap", "Tone Mapping", params);
|
|
1870
|
+
}
|
|
1871
|
+
async createPipeline(device) {
|
|
1872
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
1873
|
+
label: "tonemap_bind_group_layout",
|
|
1874
|
+
entries: [
|
|
1875
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
|
|
1876
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
1877
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }
|
|
1878
|
+
]
|
|
1879
|
+
});
|
|
1880
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
1881
|
+
label: "tonemap_pipeline_layout",
|
|
1882
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
1883
|
+
});
|
|
1884
|
+
const shaderModule = device.createShaderModule({
|
|
1885
|
+
label: "tonemap_shader",
|
|
1886
|
+
code: FULLSCREEN_VERTEX_SHADER + TONEMAP_SHADER
|
|
1887
|
+
});
|
|
1888
|
+
this.pipeline = device.createRenderPipeline({
|
|
1889
|
+
label: "tonemap_pipeline",
|
|
1890
|
+
layout: pipelineLayout,
|
|
1891
|
+
vertex: {
|
|
1892
|
+
module: shaderModule,
|
|
1893
|
+
entryPoint: "vs_main"
|
|
1894
|
+
},
|
|
1895
|
+
fragment: {
|
|
1896
|
+
module: shaderModule,
|
|
1897
|
+
entryPoint: "fs_tonemap",
|
|
1898
|
+
targets: [{ format: "rgba8unorm" }]
|
|
1899
|
+
},
|
|
1900
|
+
primitive: { topology: "triangle-list" }
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
updateUniforms(device, _frameData) {
|
|
1904
|
+
if (!this.uniformBuffer) return;
|
|
1905
|
+
const p = this.params;
|
|
1906
|
+
const operatorMap = {
|
|
1907
|
+
none: 0,
|
|
1908
|
+
reinhard: 1,
|
|
1909
|
+
reinhardLum: 2,
|
|
1910
|
+
aces: 3,
|
|
1911
|
+
acesApprox: 4,
|
|
1912
|
+
filmic: 5,
|
|
1913
|
+
uncharted2: 6,
|
|
1914
|
+
uchimura: 7,
|
|
1915
|
+
lottes: 8,
|
|
1916
|
+
khronos: 9
|
|
1917
|
+
};
|
|
1918
|
+
const data = new Float32Array([
|
|
1919
|
+
operatorMap[p.operator] ?? 3,
|
|
1920
|
+
p.exposure,
|
|
1921
|
+
p.gamma,
|
|
1922
|
+
p.whitePoint,
|
|
1923
|
+
p.contrast,
|
|
1924
|
+
p.saturation,
|
|
1925
|
+
p.intensity,
|
|
1926
|
+
0
|
|
1927
|
+
// padding
|
|
1928
|
+
]);
|
|
1929
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, data);
|
|
1930
|
+
}
|
|
1931
|
+
render(context) {
|
|
1932
|
+
if (!this.enabled || !this.pipeline || !this.uniformBuffer || !this.sampler) return;
|
|
1933
|
+
this.updateUniforms(context.device, context.frameData);
|
|
1934
|
+
const bindGroup = context.device.createBindGroup({
|
|
1935
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
1936
|
+
entries: [
|
|
1937
|
+
{ binding: 0, resource: this.sampler },
|
|
1938
|
+
{ binding: 1, resource: context.input.view },
|
|
1939
|
+
{ binding: 2, resource: { buffer: this.uniformBuffer } }
|
|
1940
|
+
]
|
|
1941
|
+
});
|
|
1942
|
+
const passEncoder = context.commandEncoder.beginRenderPass({
|
|
1943
|
+
colorAttachments: [
|
|
1944
|
+
{
|
|
1945
|
+
view: context.output.view,
|
|
1946
|
+
loadOp: "clear",
|
|
1947
|
+
storeOp: "store",
|
|
1948
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 }
|
|
1949
|
+
}
|
|
1950
|
+
]
|
|
1951
|
+
});
|
|
1952
|
+
passEncoder.setPipeline(this.pipeline);
|
|
1953
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
1954
|
+
passEncoder.draw(3);
|
|
1955
|
+
passEncoder.end();
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
var FXAAEffect = class extends PostProcessEffect {
|
|
1959
|
+
constructor(params) {
|
|
1960
|
+
super("fxaa", "FXAA", params);
|
|
1961
|
+
}
|
|
1962
|
+
async createPipeline(device) {
|
|
1963
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
1964
|
+
label: "fxaa_bind_group_layout",
|
|
1965
|
+
entries: [
|
|
1966
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
|
|
1967
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
1968
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }
|
|
1969
|
+
]
|
|
1970
|
+
});
|
|
1971
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
1972
|
+
label: "fxaa_pipeline_layout",
|
|
1973
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
1974
|
+
});
|
|
1975
|
+
const shaderModule = device.createShaderModule({
|
|
1976
|
+
label: "fxaa_shader",
|
|
1977
|
+
code: FULLSCREEN_VERTEX_SHADER + FXAA_SHADER
|
|
1978
|
+
});
|
|
1979
|
+
this.pipeline = device.createRenderPipeline({
|
|
1980
|
+
label: "fxaa_pipeline",
|
|
1981
|
+
layout: pipelineLayout,
|
|
1982
|
+
vertex: {
|
|
1983
|
+
module: shaderModule,
|
|
1984
|
+
entryPoint: "vs_main"
|
|
1985
|
+
},
|
|
1986
|
+
fragment: {
|
|
1987
|
+
module: shaderModule,
|
|
1988
|
+
entryPoint: "fs_fxaa",
|
|
1989
|
+
targets: [{ format: "rgba8unorm" }]
|
|
1990
|
+
},
|
|
1991
|
+
primitive: { topology: "triangle-list" }
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
updateUniforms(device, _frameData) {
|
|
1995
|
+
if (!this.uniformBuffer) return;
|
|
1996
|
+
const p = this.params;
|
|
1997
|
+
const qualityMap = { low: 0, medium: 1, high: 2, ultra: 3 };
|
|
1998
|
+
const data = new Float32Array([
|
|
1999
|
+
qualityMap[p.quality] ?? 2,
|
|
2000
|
+
p.edgeThreshold,
|
|
2001
|
+
p.edgeThresholdMin,
|
|
2002
|
+
p.intensity
|
|
2003
|
+
]);
|
|
2004
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, data);
|
|
2005
|
+
}
|
|
2006
|
+
render(context) {
|
|
2007
|
+
if (!this.enabled || !this.pipeline || !this.uniformBuffer || !this.sampler) return;
|
|
2008
|
+
this.updateUniforms(context.device, context.frameData);
|
|
2009
|
+
const bindGroup = context.device.createBindGroup({
|
|
2010
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
2011
|
+
entries: [
|
|
2012
|
+
{ binding: 0, resource: this.sampler },
|
|
2013
|
+
{ binding: 1, resource: context.input.view },
|
|
2014
|
+
{ binding: 2, resource: { buffer: this.uniformBuffer } }
|
|
2015
|
+
]
|
|
2016
|
+
});
|
|
2017
|
+
const passEncoder = context.commandEncoder.beginRenderPass({
|
|
2018
|
+
colorAttachments: [
|
|
2019
|
+
{
|
|
2020
|
+
view: context.output.view,
|
|
2021
|
+
loadOp: "clear",
|
|
2022
|
+
storeOp: "store",
|
|
2023
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 }
|
|
2024
|
+
}
|
|
2025
|
+
]
|
|
2026
|
+
});
|
|
2027
|
+
passEncoder.setPipeline(this.pipeline);
|
|
2028
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
2029
|
+
passEncoder.draw(3);
|
|
2030
|
+
passEncoder.end();
|
|
2031
|
+
}
|
|
2032
|
+
};
|
|
2033
|
+
var VignetteEffect = class extends PostProcessEffect {
|
|
2034
|
+
constructor(params) {
|
|
2035
|
+
super("vignette", "Vignette", params);
|
|
2036
|
+
}
|
|
2037
|
+
async createPipeline(device) {
|
|
2038
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
2039
|
+
label: "vignette_bind_group_layout",
|
|
2040
|
+
entries: [
|
|
2041
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
|
|
2042
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
2043
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }
|
|
2044
|
+
]
|
|
2045
|
+
});
|
|
2046
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
2047
|
+
label: "vignette_pipeline_layout",
|
|
2048
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
2049
|
+
});
|
|
2050
|
+
const shaderModule = device.createShaderModule({
|
|
2051
|
+
label: "vignette_shader",
|
|
2052
|
+
code: FULLSCREEN_VERTEX_SHADER + VIGNETTE_SHADER
|
|
2053
|
+
});
|
|
2054
|
+
this.pipeline = device.createRenderPipeline({
|
|
2055
|
+
label: "vignette_pipeline",
|
|
2056
|
+
layout: pipelineLayout,
|
|
2057
|
+
vertex: {
|
|
2058
|
+
module: shaderModule,
|
|
2059
|
+
entryPoint: "vs_main"
|
|
2060
|
+
},
|
|
2061
|
+
fragment: {
|
|
2062
|
+
module: shaderModule,
|
|
2063
|
+
entryPoint: "fs_vignette",
|
|
2064
|
+
targets: [{ format: "rgba8unorm" }]
|
|
2065
|
+
},
|
|
2066
|
+
primitive: { topology: "triangle-list" }
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
updateUniforms(device, _frameData) {
|
|
2070
|
+
if (!this.uniformBuffer) return;
|
|
2071
|
+
const p = this.params;
|
|
2072
|
+
const data = new Float32Array([
|
|
2073
|
+
p.intensity,
|
|
2074
|
+
p.roundness,
|
|
2075
|
+
p.smoothness,
|
|
2076
|
+
0,
|
|
2077
|
+
// padding
|
|
2078
|
+
p.color[0],
|
|
2079
|
+
p.color[1],
|
|
2080
|
+
p.color[2],
|
|
2081
|
+
1
|
|
2082
|
+
]);
|
|
2083
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, data);
|
|
2084
|
+
}
|
|
2085
|
+
render(context) {
|
|
2086
|
+
if (!this.enabled || !this.pipeline || !this.uniformBuffer || !this.sampler) return;
|
|
2087
|
+
this.updateUniforms(context.device, context.frameData);
|
|
2088
|
+
const bindGroup = context.device.createBindGroup({
|
|
2089
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
2090
|
+
entries: [
|
|
2091
|
+
{ binding: 0, resource: this.sampler },
|
|
2092
|
+
{ binding: 1, resource: context.input.view },
|
|
2093
|
+
{ binding: 2, resource: { buffer: this.uniformBuffer } }
|
|
2094
|
+
]
|
|
2095
|
+
});
|
|
2096
|
+
const passEncoder = context.commandEncoder.beginRenderPass({
|
|
2097
|
+
colorAttachments: [
|
|
2098
|
+
{
|
|
2099
|
+
view: context.output.view,
|
|
2100
|
+
loadOp: "clear",
|
|
2101
|
+
storeOp: "store",
|
|
2102
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 }
|
|
2103
|
+
}
|
|
2104
|
+
]
|
|
2105
|
+
});
|
|
2106
|
+
passEncoder.setPipeline(this.pipeline);
|
|
2107
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
2108
|
+
passEncoder.draw(3);
|
|
2109
|
+
passEncoder.end();
|
|
2110
|
+
}
|
|
2111
|
+
};
|
|
2112
|
+
var FilmGrainEffect = class extends PostProcessEffect {
|
|
2113
|
+
constructor(params) {
|
|
2114
|
+
super("filmGrain", "Film Grain", params);
|
|
2115
|
+
}
|
|
2116
|
+
async createPipeline(device) {
|
|
2117
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
2118
|
+
label: "filmgrain_bind_group_layout",
|
|
2119
|
+
entries: [
|
|
2120
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
|
|
2121
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
2122
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }
|
|
2123
|
+
]
|
|
2124
|
+
});
|
|
2125
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
2126
|
+
label: "filmgrain_pipeline_layout",
|
|
2127
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
2128
|
+
});
|
|
2129
|
+
const shaderModule = device.createShaderModule({
|
|
2130
|
+
label: "filmgrain_shader",
|
|
2131
|
+
code: FULLSCREEN_VERTEX_SHADER + FILM_GRAIN_SHADER
|
|
2132
|
+
});
|
|
2133
|
+
this.pipeline = device.createRenderPipeline({
|
|
2134
|
+
label: "filmgrain_pipeline",
|
|
2135
|
+
layout: pipelineLayout,
|
|
2136
|
+
vertex: {
|
|
2137
|
+
module: shaderModule,
|
|
2138
|
+
entryPoint: "vs_main"
|
|
2139
|
+
},
|
|
2140
|
+
fragment: {
|
|
2141
|
+
module: shaderModule,
|
|
2142
|
+
entryPoint: "fs_filmgrain",
|
|
2143
|
+
targets: [{ format: "rgba8unorm" }]
|
|
2144
|
+
},
|
|
2145
|
+
primitive: { topology: "triangle-list" }
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
updateUniforms(device, frameData) {
|
|
2149
|
+
if (!this.uniformBuffer) return;
|
|
2150
|
+
const p = this.params;
|
|
2151
|
+
const data = new Float32Array([
|
|
2152
|
+
p.intensity,
|
|
2153
|
+
p.size,
|
|
2154
|
+
p.luminanceContribution,
|
|
2155
|
+
p.animated ? frameData.time : 0
|
|
2156
|
+
]);
|
|
2157
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, data);
|
|
2158
|
+
}
|
|
2159
|
+
render(context) {
|
|
2160
|
+
if (!this.enabled || !this.pipeline || !this.uniformBuffer || !this.sampler) return;
|
|
2161
|
+
this.updateUniforms(context.device, context.frameData);
|
|
2162
|
+
const bindGroup = context.device.createBindGroup({
|
|
2163
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
2164
|
+
entries: [
|
|
2165
|
+
{ binding: 0, resource: this.sampler },
|
|
2166
|
+
{ binding: 1, resource: context.input.view },
|
|
2167
|
+
{ binding: 2, resource: { buffer: this.uniformBuffer } }
|
|
2168
|
+
]
|
|
2169
|
+
});
|
|
2170
|
+
const passEncoder = context.commandEncoder.beginRenderPass({
|
|
2171
|
+
colorAttachments: [
|
|
2172
|
+
{
|
|
2173
|
+
view: context.output.view,
|
|
2174
|
+
loadOp: "clear",
|
|
2175
|
+
storeOp: "store",
|
|
2176
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 }
|
|
2177
|
+
}
|
|
2178
|
+
]
|
|
2179
|
+
});
|
|
2180
|
+
passEncoder.setPipeline(this.pipeline);
|
|
2181
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
2182
|
+
passEncoder.draw(3);
|
|
2183
|
+
passEncoder.end();
|
|
2184
|
+
}
|
|
2185
|
+
};
|
|
2186
|
+
var SharpenEffect = class extends PostProcessEffect {
|
|
2187
|
+
constructor(params) {
|
|
2188
|
+
super("sharpen", "Sharpen", params);
|
|
2189
|
+
}
|
|
2190
|
+
async createPipeline(device) {
|
|
2191
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
2192
|
+
label: "sharpen_bind_group_layout",
|
|
2193
|
+
entries: [
|
|
2194
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
|
|
2195
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
2196
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }
|
|
2197
|
+
]
|
|
2198
|
+
});
|
|
2199
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
2200
|
+
label: "sharpen_pipeline_layout",
|
|
2201
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
2202
|
+
});
|
|
2203
|
+
const shaderModule = device.createShaderModule({
|
|
2204
|
+
label: "sharpen_shader",
|
|
2205
|
+
code: FULLSCREEN_VERTEX_SHADER + SHARPEN_SHADER
|
|
2206
|
+
});
|
|
2207
|
+
this.pipeline = device.createRenderPipeline({
|
|
2208
|
+
label: "sharpen_pipeline",
|
|
2209
|
+
layout: pipelineLayout,
|
|
2210
|
+
vertex: {
|
|
2211
|
+
module: shaderModule,
|
|
2212
|
+
entryPoint: "vs_main"
|
|
2213
|
+
},
|
|
2214
|
+
fragment: {
|
|
2215
|
+
module: shaderModule,
|
|
2216
|
+
entryPoint: "fs_sharpen",
|
|
2217
|
+
targets: [{ format: "rgba8unorm" }]
|
|
2218
|
+
},
|
|
2219
|
+
primitive: { topology: "triangle-list" }
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
updateUniforms(device, _frameData) {
|
|
2223
|
+
if (!this.uniformBuffer) return;
|
|
2224
|
+
const p = this.params;
|
|
2225
|
+
const data = new Float32Array([
|
|
2226
|
+
p.intensity,
|
|
2227
|
+
p.amount,
|
|
2228
|
+
p.threshold,
|
|
2229
|
+
0
|
|
2230
|
+
// padding
|
|
2231
|
+
]);
|
|
2232
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, data);
|
|
2233
|
+
}
|
|
2234
|
+
render(context) {
|
|
2235
|
+
if (!this.enabled || !this.pipeline || !this.uniformBuffer || !this.sampler) return;
|
|
2236
|
+
this.updateUniforms(context.device, context.frameData);
|
|
2237
|
+
const bindGroup = context.device.createBindGroup({
|
|
2238
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
2239
|
+
entries: [
|
|
2240
|
+
{ binding: 0, resource: this.sampler },
|
|
2241
|
+
{ binding: 1, resource: context.input.view },
|
|
2242
|
+
{ binding: 2, resource: { buffer: this.uniformBuffer } }
|
|
2243
|
+
]
|
|
2244
|
+
});
|
|
2245
|
+
const passEncoder = context.commandEncoder.beginRenderPass({
|
|
2246
|
+
colorAttachments: [
|
|
2247
|
+
{
|
|
2248
|
+
view: context.output.view,
|
|
2249
|
+
loadOp: "clear",
|
|
2250
|
+
storeOp: "store",
|
|
2251
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 }
|
|
2252
|
+
}
|
|
2253
|
+
]
|
|
2254
|
+
});
|
|
2255
|
+
passEncoder.setPipeline(this.pipeline);
|
|
2256
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
2257
|
+
passEncoder.draw(3);
|
|
2258
|
+
passEncoder.end();
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
var ChromaticAberrationEffect = class extends PostProcessEffect {
|
|
2262
|
+
constructor(params) {
|
|
2263
|
+
super("chromaticAberration", "Chromatic Aberration", params);
|
|
2264
|
+
}
|
|
2265
|
+
async createPipeline(device) {
|
|
2266
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
2267
|
+
label: "chromatic_bind_group_layout",
|
|
2268
|
+
entries: [
|
|
2269
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
|
|
2270
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
2271
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }
|
|
2272
|
+
]
|
|
2273
|
+
});
|
|
2274
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
2275
|
+
label: "chromatic_pipeline_layout",
|
|
2276
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
2277
|
+
});
|
|
2278
|
+
const shaderModule = device.createShaderModule({
|
|
2279
|
+
label: "chromatic_shader",
|
|
2280
|
+
code: FULLSCREEN_VERTEX_SHADER + CHROMATIC_ABERRATION_SHADER
|
|
2281
|
+
});
|
|
2282
|
+
this.pipeline = device.createRenderPipeline({
|
|
2283
|
+
label: "chromatic_pipeline",
|
|
2284
|
+
layout: pipelineLayout,
|
|
2285
|
+
vertex: {
|
|
2286
|
+
module: shaderModule,
|
|
2287
|
+
entryPoint: "vs_main"
|
|
2288
|
+
},
|
|
2289
|
+
fragment: {
|
|
2290
|
+
module: shaderModule,
|
|
2291
|
+
entryPoint: "fs_chromatic",
|
|
2292
|
+
targets: [{ format: "rgba8unorm" }]
|
|
2293
|
+
},
|
|
2294
|
+
primitive: { topology: "triangle-list" }
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
updateUniforms(device, _frameData) {
|
|
2298
|
+
if (!this.uniformBuffer) return;
|
|
2299
|
+
const p = this.params;
|
|
2300
|
+
const data = new Float32Array([
|
|
2301
|
+
p.intensity,
|
|
2302
|
+
p.radial ? 1 : 0,
|
|
2303
|
+
0,
|
|
2304
|
+
0,
|
|
2305
|
+
// padding
|
|
2306
|
+
p.redOffset[0],
|
|
2307
|
+
p.redOffset[1],
|
|
2308
|
+
p.greenOffset[0],
|
|
2309
|
+
p.greenOffset[1],
|
|
2310
|
+
p.blueOffset[0],
|
|
2311
|
+
p.blueOffset[1],
|
|
2312
|
+
0,
|
|
2313
|
+
0
|
|
2314
|
+
// padding
|
|
2315
|
+
]);
|
|
2316
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, data);
|
|
2317
|
+
}
|
|
2318
|
+
render(context) {
|
|
2319
|
+
if (!this.enabled || !this.pipeline || !this.uniformBuffer || !this.sampler) return;
|
|
2320
|
+
this.updateUniforms(context.device, context.frameData);
|
|
2321
|
+
const bindGroup = context.device.createBindGroup({
|
|
2322
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
2323
|
+
entries: [
|
|
2324
|
+
{ binding: 0, resource: this.sampler },
|
|
2325
|
+
{ binding: 1, resource: context.input.view },
|
|
2326
|
+
{ binding: 2, resource: { buffer: this.uniformBuffer } }
|
|
2327
|
+
]
|
|
2328
|
+
});
|
|
2329
|
+
const passEncoder = context.commandEncoder.beginRenderPass({
|
|
2330
|
+
colorAttachments: [
|
|
2331
|
+
{
|
|
2332
|
+
view: context.output.view,
|
|
2333
|
+
loadOp: "clear",
|
|
2334
|
+
storeOp: "store",
|
|
2335
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 }
|
|
2336
|
+
}
|
|
2337
|
+
]
|
|
2338
|
+
});
|
|
2339
|
+
passEncoder.setPipeline(this.pipeline);
|
|
2340
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
2341
|
+
passEncoder.draw(3);
|
|
2342
|
+
passEncoder.end();
|
|
2343
|
+
}
|
|
2344
|
+
};
|
|
2345
|
+
var CausticsEffect = class extends PostProcessEffect {
|
|
2346
|
+
constructor(params) {
|
|
2347
|
+
super("caustics", "Caustics", params);
|
|
2348
|
+
}
|
|
2349
|
+
async createPipeline(device) {
|
|
2350
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
2351
|
+
label: "caustics_bind_group_layout",
|
|
2352
|
+
entries: [
|
|
2353
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
|
|
2354
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
2355
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }
|
|
2356
|
+
]
|
|
2357
|
+
});
|
|
2358
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
2359
|
+
label: "caustics_pipeline_layout",
|
|
2360
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
2361
|
+
});
|
|
2362
|
+
const shaderModule = device.createShaderModule({
|
|
2363
|
+
label: "caustics_shader",
|
|
2364
|
+
code: FULLSCREEN_VERTEX_SHADER + CAUSTICS_SHADER
|
|
2365
|
+
});
|
|
2366
|
+
this.pipeline = device.createRenderPipeline({
|
|
2367
|
+
label: "caustics_pipeline",
|
|
2368
|
+
layout: pipelineLayout,
|
|
2369
|
+
vertex: { module: shaderModule, entryPoint: "vs_main" },
|
|
2370
|
+
fragment: {
|
|
2371
|
+
module: shaderModule,
|
|
2372
|
+
entryPoint: "fs_caustics",
|
|
2373
|
+
targets: [{ format: "rgba16float" }]
|
|
2374
|
+
},
|
|
2375
|
+
primitive: { topology: "triangle-list" }
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
updateUniforms(device, frameData) {
|
|
2379
|
+
if (!this.uniformBuffer) return;
|
|
2380
|
+
const p = this.params;
|
|
2381
|
+
const data = new Float32Array([
|
|
2382
|
+
p.intensity,
|
|
2383
|
+
p.scale,
|
|
2384
|
+
p.speed,
|
|
2385
|
+
p.depthFade,
|
|
2386
|
+
p.color[0],
|
|
2387
|
+
p.color[1],
|
|
2388
|
+
p.color[2],
|
|
2389
|
+
p.waterLevel,
|
|
2390
|
+
frameData.time,
|
|
2391
|
+
p.dispersion ?? 0,
|
|
2392
|
+
p.foamIntensity ?? 0,
|
|
2393
|
+
p.shadowStrength ?? 0,
|
|
2394
|
+
0,
|
|
2395
|
+
0,
|
|
2396
|
+
0,
|
|
2397
|
+
0
|
|
2398
|
+
]);
|
|
2399
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, data);
|
|
2400
|
+
}
|
|
2401
|
+
render(context) {
|
|
2402
|
+
if (!this.enabled || !this.pipeline || !this.uniformBuffer || !this.sampler) return;
|
|
2403
|
+
this.updateUniforms(context.device, context.frameData);
|
|
2404
|
+
const bindGroup = context.device.createBindGroup({
|
|
2405
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
2406
|
+
entries: [
|
|
2407
|
+
{ binding: 0, resource: this.sampler },
|
|
2408
|
+
{ binding: 1, resource: context.input.view },
|
|
2409
|
+
{ binding: 2, resource: { buffer: this.uniformBuffer } }
|
|
2410
|
+
]
|
|
2411
|
+
});
|
|
2412
|
+
const passEncoder = context.commandEncoder.beginRenderPass({
|
|
2413
|
+
colorAttachments: [
|
|
2414
|
+
{
|
|
2415
|
+
view: context.output.view,
|
|
2416
|
+
loadOp: "clear",
|
|
2417
|
+
storeOp: "store",
|
|
2418
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 }
|
|
2419
|
+
}
|
|
2420
|
+
]
|
|
2421
|
+
});
|
|
2422
|
+
passEncoder.setPipeline(this.pipeline);
|
|
2423
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
2424
|
+
passEncoder.draw(3);
|
|
2425
|
+
passEncoder.end();
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2428
|
+
var SSREffect = class extends PostProcessEffect {
|
|
2429
|
+
constructor(params) {
|
|
2430
|
+
super("ssr", "SSR", params);
|
|
2431
|
+
}
|
|
2432
|
+
async createPipeline(device) {
|
|
2433
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
2434
|
+
label: "ssr_bind_group_layout",
|
|
2435
|
+
entries: [
|
|
2436
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
|
|
2437
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
2438
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }
|
|
2439
|
+
]
|
|
2440
|
+
});
|
|
2441
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
2442
|
+
label: "ssr_pipeline_layout",
|
|
2443
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
2444
|
+
});
|
|
2445
|
+
const shaderModule = device.createShaderModule({
|
|
2446
|
+
label: "ssr_shader",
|
|
2447
|
+
code: FULLSCREEN_VERTEX_SHADER + SSR_SHADER
|
|
2448
|
+
});
|
|
2449
|
+
this.pipeline = device.createRenderPipeline({
|
|
2450
|
+
label: "ssr_pipeline",
|
|
2451
|
+
layout: pipelineLayout,
|
|
2452
|
+
vertex: { module: shaderModule, entryPoint: "vs_main" },
|
|
2453
|
+
fragment: {
|
|
2454
|
+
module: shaderModule,
|
|
2455
|
+
entryPoint: "fs_ssr",
|
|
2456
|
+
targets: [{ format: "rgba16float" }]
|
|
2457
|
+
},
|
|
2458
|
+
primitive: { topology: "triangle-list" }
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
updateUniforms(device, _frameData) {
|
|
2462
|
+
if (!this.uniformBuffer) return;
|
|
2463
|
+
const p = this.params;
|
|
2464
|
+
const data = new Float32Array([
|
|
2465
|
+
p.intensity,
|
|
2466
|
+
p.maxSteps,
|
|
2467
|
+
p.stepSize,
|
|
2468
|
+
p.thickness,
|
|
2469
|
+
p.roughnessFade,
|
|
2470
|
+
p.edgeFade,
|
|
2471
|
+
p.roughnessBlur ?? 0,
|
|
2472
|
+
p.fresnelStrength ?? 0,
|
|
2473
|
+
0,
|
|
2474
|
+
0,
|
|
2475
|
+
0,
|
|
2476
|
+
0
|
|
2477
|
+
]);
|
|
2478
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, data);
|
|
2479
|
+
}
|
|
2480
|
+
render(context) {
|
|
2481
|
+
if (!this.enabled || !this.pipeline || !this.uniformBuffer || !this.sampler) return;
|
|
2482
|
+
this.updateUniforms(context.device, context.frameData);
|
|
2483
|
+
const bindGroup = context.device.createBindGroup({
|
|
2484
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
2485
|
+
entries: [
|
|
2486
|
+
{ binding: 0, resource: this.sampler },
|
|
2487
|
+
{ binding: 1, resource: context.input.view },
|
|
2488
|
+
{ binding: 2, resource: { buffer: this.uniformBuffer } }
|
|
2489
|
+
]
|
|
2490
|
+
});
|
|
2491
|
+
const passEncoder = context.commandEncoder.beginRenderPass({
|
|
2492
|
+
colorAttachments: [
|
|
2493
|
+
{
|
|
2494
|
+
view: context.output.view,
|
|
2495
|
+
loadOp: "clear",
|
|
2496
|
+
storeOp: "store",
|
|
2497
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 }
|
|
2498
|
+
}
|
|
2499
|
+
]
|
|
2500
|
+
});
|
|
2501
|
+
passEncoder.setPipeline(this.pipeline);
|
|
2502
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
2503
|
+
passEncoder.draw(3);
|
|
2504
|
+
passEncoder.end();
|
|
2505
|
+
}
|
|
2506
|
+
};
|
|
2507
|
+
var SSAOEffect = class extends PostProcessEffect {
|
|
2508
|
+
constructor(params) {
|
|
2509
|
+
super("ssao", "SSAO", params);
|
|
2510
|
+
}
|
|
2511
|
+
async createPipeline(device) {
|
|
2512
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
2513
|
+
label: "ssao_bind_group_layout",
|
|
2514
|
+
entries: [
|
|
2515
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
|
|
2516
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
2517
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }
|
|
2518
|
+
]
|
|
2519
|
+
});
|
|
2520
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
2521
|
+
label: "ssao_pipeline_layout",
|
|
2522
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
2523
|
+
});
|
|
2524
|
+
const shaderModule = device.createShaderModule({
|
|
2525
|
+
label: "ssao_shader",
|
|
2526
|
+
code: FULLSCREEN_VERTEX_SHADER + SSAO_SHADER
|
|
2527
|
+
});
|
|
2528
|
+
this.pipeline = device.createRenderPipeline({
|
|
2529
|
+
label: "ssao_pipeline",
|
|
2530
|
+
layout: pipelineLayout,
|
|
2531
|
+
vertex: { module: shaderModule, entryPoint: "vs_main" },
|
|
2532
|
+
fragment: {
|
|
2533
|
+
module: shaderModule,
|
|
2534
|
+
entryPoint: "fs_ssao",
|
|
2535
|
+
targets: [{ format: "rgba16float" }]
|
|
2536
|
+
},
|
|
2537
|
+
primitive: { topology: "triangle-list" }
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
updateUniforms(device, _frameData) {
|
|
2541
|
+
if (!this.uniformBuffer) return;
|
|
2542
|
+
const p = this.params;
|
|
2543
|
+
const modeVal = (p.mode ?? "hemisphere") === "hbao" ? 1 : 0;
|
|
2544
|
+
const data = new Float32Array([
|
|
2545
|
+
p.intensity,
|
|
2546
|
+
p.radius,
|
|
2547
|
+
p.bias,
|
|
2548
|
+
p.samples,
|
|
2549
|
+
p.power,
|
|
2550
|
+
p.falloff,
|
|
2551
|
+
0,
|
|
2552
|
+
0,
|
|
2553
|
+
modeVal,
|
|
2554
|
+
p.bentNormals ? 1 : 0,
|
|
2555
|
+
p.spatialDenoise ? 1 : 0,
|
|
2556
|
+
0
|
|
2557
|
+
]);
|
|
2558
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, data);
|
|
2559
|
+
}
|
|
2560
|
+
render(context) {
|
|
2561
|
+
if (!this.enabled || !this.pipeline || !this.uniformBuffer || !this.sampler) return;
|
|
2562
|
+
this.updateUniforms(context.device, context.frameData);
|
|
2563
|
+
const bindGroup = context.device.createBindGroup({
|
|
2564
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
2565
|
+
entries: [
|
|
2566
|
+
{ binding: 0, resource: this.sampler },
|
|
2567
|
+
{ binding: 1, resource: context.input.view },
|
|
2568
|
+
{ binding: 2, resource: { buffer: this.uniformBuffer } }
|
|
2569
|
+
]
|
|
2570
|
+
});
|
|
2571
|
+
const passEncoder = context.commandEncoder.beginRenderPass({
|
|
2572
|
+
colorAttachments: [
|
|
2573
|
+
{
|
|
2574
|
+
view: context.output.view,
|
|
2575
|
+
loadOp: "clear",
|
|
2576
|
+
storeOp: "store",
|
|
2577
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 }
|
|
2578
|
+
}
|
|
2579
|
+
]
|
|
2580
|
+
});
|
|
2581
|
+
passEncoder.setPipeline(this.pipeline);
|
|
2582
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
2583
|
+
passEncoder.draw(3);
|
|
2584
|
+
passEncoder.end();
|
|
2585
|
+
}
|
|
2586
|
+
};
|
|
2587
|
+
var SSGIEffect = class extends PostProcessEffect {
|
|
2588
|
+
constructor(params) {
|
|
2589
|
+
super("ssgi", "SSGI", params);
|
|
2590
|
+
}
|
|
2591
|
+
async createPipeline(device) {
|
|
2592
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
2593
|
+
label: "ssgi_bind_group_layout",
|
|
2594
|
+
entries: [
|
|
2595
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: "filtering" } },
|
|
2596
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: "float" } },
|
|
2597
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }
|
|
2598
|
+
]
|
|
2599
|
+
});
|
|
2600
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
2601
|
+
label: "ssgi_pipeline_layout",
|
|
2602
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
2603
|
+
});
|
|
2604
|
+
const shaderModule = device.createShaderModule({
|
|
2605
|
+
label: "ssgi_shader",
|
|
2606
|
+
code: FULLSCREEN_VERTEX_SHADER + SSGI_SHADER
|
|
2607
|
+
});
|
|
2608
|
+
this.pipeline = device.createRenderPipeline({
|
|
2609
|
+
label: "ssgi_pipeline",
|
|
2610
|
+
layout: pipelineLayout,
|
|
2611
|
+
vertex: { module: shaderModule, entryPoint: "vs_main" },
|
|
2612
|
+
fragment: {
|
|
2613
|
+
module: shaderModule,
|
|
2614
|
+
entryPoint: "fs_ssgi",
|
|
2615
|
+
targets: [{ format: "rgba16float" }]
|
|
2616
|
+
},
|
|
2617
|
+
primitive: { topology: "triangle-list" }
|
|
2618
|
+
});
|
|
2619
|
+
}
|
|
2620
|
+
updateUniforms(device, _frameData) {
|
|
2621
|
+
if (!this.uniformBuffer) return;
|
|
2622
|
+
const p = this.params;
|
|
2623
|
+
const data = new Float32Array([
|
|
2624
|
+
p.intensity,
|
|
2625
|
+
p.radius,
|
|
2626
|
+
p.samples,
|
|
2627
|
+
p.bounceIntensity,
|
|
2628
|
+
p.falloff,
|
|
2629
|
+
p.temporalBlend ?? 0,
|
|
2630
|
+
p.spatialDenoise ? 1 : 0,
|
|
2631
|
+
p.multiBounce ?? 0,
|
|
2632
|
+
0,
|
|
2633
|
+
0,
|
|
2634
|
+
0,
|
|
2635
|
+
0
|
|
2636
|
+
]);
|
|
2637
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, data);
|
|
2638
|
+
}
|
|
2639
|
+
render(context) {
|
|
2640
|
+
if (!this.enabled || !this.pipeline || !this.uniformBuffer || !this.sampler) return;
|
|
2641
|
+
this.updateUniforms(context.device, context.frameData);
|
|
2642
|
+
const bindGroup = context.device.createBindGroup({
|
|
2643
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
2644
|
+
entries: [
|
|
2645
|
+
{ binding: 0, resource: this.sampler },
|
|
2646
|
+
{ binding: 1, resource: context.input.view },
|
|
2647
|
+
{ binding: 2, resource: { buffer: this.uniformBuffer } }
|
|
2648
|
+
]
|
|
2649
|
+
});
|
|
2650
|
+
const passEncoder = context.commandEncoder.beginRenderPass({
|
|
2651
|
+
colorAttachments: [
|
|
2652
|
+
{
|
|
2653
|
+
view: context.output.view,
|
|
2654
|
+
loadOp: "clear",
|
|
2655
|
+
storeOp: "store",
|
|
2656
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 }
|
|
2657
|
+
}
|
|
2658
|
+
]
|
|
2659
|
+
});
|
|
2660
|
+
passEncoder.setPipeline(this.pipeline);
|
|
2661
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
2662
|
+
passEncoder.draw(3);
|
|
2663
|
+
passEncoder.end();
|
|
2664
|
+
}
|
|
2665
|
+
};
|
|
2666
|
+
function createEffect(type, params) {
|
|
2667
|
+
switch (type) {
|
|
2668
|
+
case "bloom":
|
|
2669
|
+
return new BloomEffect(params);
|
|
2670
|
+
case "tonemap":
|
|
2671
|
+
return new ToneMapEffect(params);
|
|
2672
|
+
case "fxaa":
|
|
2673
|
+
return new FXAAEffect(params);
|
|
2674
|
+
case "vignette":
|
|
2675
|
+
return new VignetteEffect(params);
|
|
2676
|
+
case "filmGrain":
|
|
2677
|
+
return new FilmGrainEffect(params);
|
|
2678
|
+
case "sharpen":
|
|
2679
|
+
return new SharpenEffect(params);
|
|
2680
|
+
case "chromaticAberration":
|
|
2681
|
+
return new ChromaticAberrationEffect(params);
|
|
2682
|
+
case "caustics":
|
|
2683
|
+
return new CausticsEffect(params);
|
|
2684
|
+
case "ssr":
|
|
2685
|
+
return new SSREffect(params);
|
|
2686
|
+
case "ssao":
|
|
2687
|
+
return new SSAOEffect(params);
|
|
2688
|
+
case "ssgi":
|
|
2689
|
+
return new SSGIEffect(params);
|
|
2690
|
+
default:
|
|
2691
|
+
throw new Error(`Unknown effect type: ${type}`);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
// src/rendering/postprocess/PostProcessPipeline.ts
|
|
2696
|
+
var DEFAULT_PIPELINE_CONFIG = {
|
|
2697
|
+
hdrEnabled: true,
|
|
2698
|
+
hdrFormat: "rgba16float",
|
|
2699
|
+
ldrFormat: "rgba8unorm",
|
|
2700
|
+
msaaSamples: 1,
|
|
2701
|
+
effects: [],
|
|
2702
|
+
autoResize: true
|
|
2703
|
+
};
|
|
2704
|
+
var PostProcessPipeline = class {
|
|
2705
|
+
device = null;
|
|
2706
|
+
config;
|
|
2707
|
+
effects = [];
|
|
2708
|
+
renderTargets = /* @__PURE__ */ new Map();
|
|
2709
|
+
pingPongTargets = [null, null];
|
|
2710
|
+
currentWidth = 0;
|
|
2711
|
+
currentHeight = 0;
|
|
2712
|
+
_initialized = false;
|
|
2713
|
+
frameCount = 0;
|
|
2714
|
+
constructor(config) {
|
|
2715
|
+
this.config = { ...DEFAULT_PIPELINE_CONFIG, ...config };
|
|
2716
|
+
}
|
|
2717
|
+
/**
|
|
2718
|
+
* Check if pipeline is initialized
|
|
2719
|
+
*/
|
|
2720
|
+
get initialized() {
|
|
2721
|
+
return this._initialized;
|
|
2722
|
+
}
|
|
2723
|
+
/**
|
|
2724
|
+
* Get current configuration
|
|
2725
|
+
*/
|
|
2726
|
+
getConfig() {
|
|
2727
|
+
return { ...this.config };
|
|
2728
|
+
}
|
|
2729
|
+
/**
|
|
2730
|
+
* Get list of active effects
|
|
2731
|
+
*/
|
|
2732
|
+
getEffects() {
|
|
2733
|
+
return [...this.effects];
|
|
2734
|
+
}
|
|
2735
|
+
/**
|
|
2736
|
+
* Get effect by name
|
|
2737
|
+
*/
|
|
2738
|
+
getEffect(name) {
|
|
2739
|
+
return this.effects.find((e) => e.name === name);
|
|
2740
|
+
}
|
|
2741
|
+
/**
|
|
2742
|
+
* Get effect by type
|
|
2743
|
+
*/
|
|
2744
|
+
getEffectByType(type) {
|
|
2745
|
+
return this.effects.find((e) => e.type === type);
|
|
2746
|
+
}
|
|
2747
|
+
/**
|
|
2748
|
+
* Initialize the pipeline with GPU device
|
|
2749
|
+
*/
|
|
2750
|
+
async initialize(device, width, height) {
|
|
2751
|
+
if (this._initialized && this.device === device) {
|
|
2752
|
+
if (width !== this.currentWidth || height !== this.currentHeight) {
|
|
2753
|
+
await this.resize(width, height);
|
|
2754
|
+
}
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
this.device = device;
|
|
2758
|
+
this.currentWidth = width;
|
|
2759
|
+
this.currentHeight = height;
|
|
2760
|
+
await this.createRenderTargets();
|
|
2761
|
+
for (const effectConfig of this.config.effects) {
|
|
2762
|
+
await this.addEffect(effectConfig);
|
|
2763
|
+
}
|
|
2764
|
+
this._initialized = true;
|
|
2765
|
+
}
|
|
2766
|
+
/**
|
|
2767
|
+
* Create or recreate render targets
|
|
2768
|
+
*/
|
|
2769
|
+
async createRenderTargets() {
|
|
2770
|
+
if (!this.device) return;
|
|
2771
|
+
this.disposeRenderTargets();
|
|
2772
|
+
const format = this.config.hdrEnabled ? this.config.hdrFormat : this.config.ldrFormat;
|
|
2773
|
+
for (let i = 0; i < 2; i++) {
|
|
2774
|
+
const texture = this.device.createTexture({
|
|
2775
|
+
size: { width: this.currentWidth, height: this.currentHeight },
|
|
2776
|
+
format,
|
|
2777
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
2778
|
+
label: `postprocess_pingpong_${i}`
|
|
2779
|
+
});
|
|
2780
|
+
this.pingPongTargets[i] = {
|
|
2781
|
+
id: `pingpong_${i}`,
|
|
2782
|
+
texture,
|
|
2783
|
+
view: texture.createView(),
|
|
2784
|
+
config: {
|
|
2785
|
+
width: this.currentWidth,
|
|
2786
|
+
height: this.currentHeight,
|
|
2787
|
+
format
|
|
2788
|
+
}
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Dispose render targets
|
|
2794
|
+
*/
|
|
2795
|
+
disposeRenderTargets() {
|
|
2796
|
+
for (const target of this.renderTargets.values()) {
|
|
2797
|
+
target.texture.destroy();
|
|
2798
|
+
}
|
|
2799
|
+
this.renderTargets.clear();
|
|
2800
|
+
for (const target of this.pingPongTargets) {
|
|
2801
|
+
target?.texture.destroy();
|
|
2802
|
+
}
|
|
2803
|
+
this.pingPongTargets = [null, null];
|
|
2804
|
+
}
|
|
2805
|
+
/**
|
|
2806
|
+
* Resize render targets
|
|
2807
|
+
*/
|
|
2808
|
+
async resize(width, height) {
|
|
2809
|
+
if (width === this.currentWidth && height === this.currentHeight) return;
|
|
2810
|
+
this.currentWidth = width;
|
|
2811
|
+
this.currentHeight = height;
|
|
2812
|
+
await this.createRenderTargets();
|
|
2813
|
+
}
|
|
2814
|
+
/**
|
|
2815
|
+
* Add an effect to the pipeline
|
|
2816
|
+
*/
|
|
2817
|
+
async addEffect(config) {
|
|
2818
|
+
const effect = createEffect(config.type, config.params);
|
|
2819
|
+
if (config.name) {
|
|
2820
|
+
effect.name = config.name;
|
|
2821
|
+
}
|
|
2822
|
+
if (this.device) {
|
|
2823
|
+
await effect.initialize(this.device);
|
|
2824
|
+
}
|
|
2825
|
+
if (config.order !== void 0 && config.order < this.effects.length) {
|
|
2826
|
+
this.effects.splice(config.order, 0, effect);
|
|
2827
|
+
} else {
|
|
2828
|
+
this.effects.push(effect);
|
|
2829
|
+
}
|
|
2830
|
+
return effect;
|
|
2831
|
+
}
|
|
2832
|
+
/**
|
|
2833
|
+
* Remove an effect from the pipeline
|
|
2834
|
+
*/
|
|
2835
|
+
removeEffect(nameOrType) {
|
|
2836
|
+
const index = this.effects.findIndex((e) => e.name === nameOrType || e.type === nameOrType);
|
|
2837
|
+
if (index === -1) return false;
|
|
2838
|
+
const effect = this.effects[index];
|
|
2839
|
+
effect.dispose();
|
|
2840
|
+
this.effects.splice(index, 1);
|
|
2841
|
+
return true;
|
|
2842
|
+
}
|
|
2843
|
+
/**
|
|
2844
|
+
* Reorder effects
|
|
2845
|
+
*/
|
|
2846
|
+
reorderEffects(order) {
|
|
2847
|
+
const newEffects = [];
|
|
2848
|
+
for (const name of order) {
|
|
2849
|
+
const effect = this.effects.find((e) => e.name === name);
|
|
2850
|
+
if (effect) {
|
|
2851
|
+
newEffects.push(effect);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
for (const effect of this.effects) {
|
|
2855
|
+
if (!order.includes(effect.name)) {
|
|
2856
|
+
newEffects.push(effect);
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
this.effects = newEffects;
|
|
2860
|
+
}
|
|
2861
|
+
/**
|
|
2862
|
+
* Update effect parameters
|
|
2863
|
+
*/
|
|
2864
|
+
updateEffectParams(nameOrType, params) {
|
|
2865
|
+
const effect = this.effects.find((e) => e.name === nameOrType || e.type === nameOrType);
|
|
2866
|
+
if (!effect) return false;
|
|
2867
|
+
effect.setParams(params);
|
|
2868
|
+
return true;
|
|
2869
|
+
}
|
|
2870
|
+
/**
|
|
2871
|
+
* Enable/disable an effect
|
|
2872
|
+
*/
|
|
2873
|
+
setEffectEnabled(nameOrType, enabled) {
|
|
2874
|
+
const effect = this.effects.find((e) => e.name === nameOrType || e.type === nameOrType);
|
|
2875
|
+
if (!effect) return false;
|
|
2876
|
+
effect.enabled = enabled;
|
|
2877
|
+
return true;
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Execute the post-processing pipeline
|
|
2881
|
+
*
|
|
2882
|
+
* @param commandEncoder GPU command encoder
|
|
2883
|
+
* @param inputView Input texture view (scene render)
|
|
2884
|
+
* @param outputView Output texture view (swap chain)
|
|
2885
|
+
* @param frameData Frame timing and camera data
|
|
2886
|
+
*/
|
|
2887
|
+
render(commandEncoder, inputView, outputView, frameData = {}) {
|
|
2888
|
+
if (!this._initialized || !this.device) {
|
|
2889
|
+
console.warn("PostProcessPipeline not initialized");
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
const enabledEffects = this.effects.filter((e) => e.enabled);
|
|
2893
|
+
if (enabledEffects.length === 0) {
|
|
2894
|
+
this.copyTexture(commandEncoder, inputView, outputView);
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
const fullFrameData = {
|
|
2898
|
+
time: frameData.time ?? performance.now() / 1e3,
|
|
2899
|
+
deltaTime: frameData.deltaTime ?? 1 / 60,
|
|
2900
|
+
frameCount: this.frameCount++,
|
|
2901
|
+
resolution: [this.currentWidth, this.currentHeight],
|
|
2902
|
+
nearPlane: frameData.nearPlane ?? 0.1,
|
|
2903
|
+
farPlane: frameData.farPlane ?? 1e3,
|
|
2904
|
+
cameraPosition: frameData.cameraPosition,
|
|
2905
|
+
viewMatrix: frameData.viewMatrix,
|
|
2906
|
+
projectionMatrix: frameData.projectionMatrix,
|
|
2907
|
+
prevViewMatrix: frameData.prevViewMatrix,
|
|
2908
|
+
jitter: frameData.jitter
|
|
2909
|
+
};
|
|
2910
|
+
let currentInput = this.pingPongTargets[0];
|
|
2911
|
+
let currentOutput = this.pingPongTargets[1];
|
|
2912
|
+
let pingPongIndex = 0;
|
|
2913
|
+
this.copyToTarget(commandEncoder, inputView, currentInput);
|
|
2914
|
+
for (let i = 0; i < enabledEffects.length; i++) {
|
|
2915
|
+
const effect = enabledEffects[i];
|
|
2916
|
+
const isLast = i === enabledEffects.length - 1;
|
|
2917
|
+
const output = isLast ? this.createTempTarget(outputView) : currentOutput;
|
|
2918
|
+
const context = {
|
|
2919
|
+
device: this.device,
|
|
2920
|
+
commandEncoder,
|
|
2921
|
+
frameData: fullFrameData,
|
|
2922
|
+
input: currentInput,
|
|
2923
|
+
output
|
|
2924
|
+
};
|
|
2925
|
+
effect.render(context);
|
|
2926
|
+
if (!isLast) {
|
|
2927
|
+
pingPongIndex = 1 - pingPongIndex;
|
|
2928
|
+
currentInput = currentOutput;
|
|
2929
|
+
currentOutput = this.pingPongTargets[pingPongIndex];
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
/**
|
|
2934
|
+
* Create temporary render target wrapper for output view
|
|
2935
|
+
*/
|
|
2936
|
+
createTempTarget(view) {
|
|
2937
|
+
return {
|
|
2938
|
+
id: "output",
|
|
2939
|
+
texture: null,
|
|
2940
|
+
// Not needed for output
|
|
2941
|
+
view,
|
|
2942
|
+
config: {
|
|
2943
|
+
width: this.currentWidth,
|
|
2944
|
+
height: this.currentHeight,
|
|
2945
|
+
format: this.config.ldrFormat
|
|
2946
|
+
}
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
/**
|
|
2950
|
+
* Copy texture using a blit pass
|
|
2951
|
+
*/
|
|
2952
|
+
copyTexture(commandEncoder, source, dest) {
|
|
2953
|
+
const passEncoder = commandEncoder.beginRenderPass({
|
|
2954
|
+
colorAttachments: [
|
|
2955
|
+
{
|
|
2956
|
+
view: dest,
|
|
2957
|
+
loadOp: "clear",
|
|
2958
|
+
storeOp: "store",
|
|
2959
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 }
|
|
2960
|
+
}
|
|
2961
|
+
]
|
|
2962
|
+
});
|
|
2963
|
+
passEncoder.end();
|
|
2964
|
+
}
|
|
2965
|
+
/**
|
|
2966
|
+
* Copy input view to a render target
|
|
2967
|
+
*/
|
|
2968
|
+
copyToTarget(commandEncoder, source, target) {
|
|
2969
|
+
this.copyTexture(commandEncoder, source, target.view);
|
|
2970
|
+
}
|
|
2971
|
+
/**
|
|
2972
|
+
* Create a preset pipeline configuration
|
|
2973
|
+
*/
|
|
2974
|
+
static createPreset(preset) {
|
|
2975
|
+
switch (preset) {
|
|
2976
|
+
case "minimal":
|
|
2977
|
+
return {
|
|
2978
|
+
hdrEnabled: false,
|
|
2979
|
+
effects: [
|
|
2980
|
+
{
|
|
2981
|
+
type: "fxaa",
|
|
2982
|
+
params: {
|
|
2983
|
+
enabled: true,
|
|
2984
|
+
intensity: 1,
|
|
2985
|
+
quality: "medium",
|
|
2986
|
+
edgeThreshold: 0.166,
|
|
2987
|
+
edgeThresholdMin: 0.0833
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
]
|
|
2991
|
+
};
|
|
2992
|
+
case "standard":
|
|
2993
|
+
return {
|
|
2994
|
+
hdrEnabled: true,
|
|
2995
|
+
effects: [
|
|
2996
|
+
{
|
|
2997
|
+
type: "bloom",
|
|
2998
|
+
params: {
|
|
2999
|
+
enabled: true,
|
|
3000
|
+
intensity: 0.5,
|
|
3001
|
+
threshold: 1,
|
|
3002
|
+
softThreshold: 0.5,
|
|
3003
|
+
radius: 4,
|
|
3004
|
+
iterations: 5,
|
|
3005
|
+
anamorphic: 0,
|
|
3006
|
+
highQuality: false
|
|
3007
|
+
}
|
|
3008
|
+
},
|
|
3009
|
+
{
|
|
3010
|
+
type: "tonemap",
|
|
3011
|
+
params: {
|
|
3012
|
+
enabled: true,
|
|
3013
|
+
intensity: 1,
|
|
3014
|
+
operator: "aces",
|
|
3015
|
+
exposure: 1,
|
|
3016
|
+
gamma: 2.2,
|
|
3017
|
+
whitePoint: 1,
|
|
3018
|
+
contrast: 1,
|
|
3019
|
+
saturation: 1
|
|
3020
|
+
}
|
|
3021
|
+
},
|
|
3022
|
+
{
|
|
3023
|
+
type: "fxaa",
|
|
3024
|
+
params: {
|
|
3025
|
+
enabled: true,
|
|
3026
|
+
intensity: 1,
|
|
3027
|
+
quality: "high",
|
|
3028
|
+
edgeThreshold: 0.166,
|
|
3029
|
+
edgeThresholdMin: 0.0833
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
]
|
|
3033
|
+
};
|
|
3034
|
+
case "cinematic":
|
|
3035
|
+
return {
|
|
3036
|
+
hdrEnabled: true,
|
|
3037
|
+
effects: [
|
|
3038
|
+
{
|
|
3039
|
+
type: "bloom",
|
|
3040
|
+
params: {
|
|
3041
|
+
enabled: true,
|
|
3042
|
+
intensity: 0.8,
|
|
3043
|
+
threshold: 0.8,
|
|
3044
|
+
softThreshold: 0.5,
|
|
3045
|
+
radius: 6,
|
|
3046
|
+
iterations: 6,
|
|
3047
|
+
anamorphic: 0.2,
|
|
3048
|
+
highQuality: true
|
|
3049
|
+
}
|
|
3050
|
+
},
|
|
3051
|
+
{
|
|
3052
|
+
type: "tonemap",
|
|
3053
|
+
params: {
|
|
3054
|
+
enabled: true,
|
|
3055
|
+
intensity: 1,
|
|
3056
|
+
operator: "aces",
|
|
3057
|
+
exposure: 1.1,
|
|
3058
|
+
gamma: 2.2,
|
|
3059
|
+
whitePoint: 1,
|
|
3060
|
+
contrast: 1.05,
|
|
3061
|
+
saturation: 1.1
|
|
3062
|
+
}
|
|
3063
|
+
},
|
|
3064
|
+
{
|
|
3065
|
+
type: "vignette",
|
|
3066
|
+
params: {
|
|
3067
|
+
enabled: true,
|
|
3068
|
+
intensity: 0.3,
|
|
3069
|
+
roundness: 1.2,
|
|
3070
|
+
smoothness: 0.4,
|
|
3071
|
+
color: [0, 0, 0]
|
|
3072
|
+
}
|
|
3073
|
+
},
|
|
3074
|
+
{
|
|
3075
|
+
type: "filmGrain",
|
|
3076
|
+
params: {
|
|
3077
|
+
enabled: true,
|
|
3078
|
+
intensity: 0.05,
|
|
3079
|
+
size: 1.5,
|
|
3080
|
+
luminanceContribution: 0.8,
|
|
3081
|
+
animated: true
|
|
3082
|
+
}
|
|
3083
|
+
},
|
|
3084
|
+
{
|
|
3085
|
+
type: "fxaa",
|
|
3086
|
+
params: {
|
|
3087
|
+
enabled: true,
|
|
3088
|
+
intensity: 1,
|
|
3089
|
+
quality: "ultra",
|
|
3090
|
+
edgeThreshold: 0.125,
|
|
3091
|
+
edgeThresholdMin: 0.0625
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
]
|
|
3095
|
+
};
|
|
3096
|
+
case "performance":
|
|
3097
|
+
return {
|
|
3098
|
+
hdrEnabled: false,
|
|
3099
|
+
msaaSamples: 1,
|
|
3100
|
+
effects: [
|
|
3101
|
+
{
|
|
3102
|
+
type: "tonemap",
|
|
3103
|
+
params: {
|
|
3104
|
+
enabled: true,
|
|
3105
|
+
intensity: 1,
|
|
3106
|
+
operator: "reinhardLum",
|
|
3107
|
+
exposure: 1,
|
|
3108
|
+
gamma: 2.2,
|
|
3109
|
+
whitePoint: 1,
|
|
3110
|
+
contrast: 1,
|
|
3111
|
+
saturation: 1
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
]
|
|
3115
|
+
};
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
/**
|
|
3119
|
+
* Dispose all resources
|
|
3120
|
+
*/
|
|
3121
|
+
dispose() {
|
|
3122
|
+
for (const effect of this.effects) {
|
|
3123
|
+
effect.dispose();
|
|
3124
|
+
}
|
|
3125
|
+
this.effects = [];
|
|
3126
|
+
this.disposeRenderTargets();
|
|
3127
|
+
this.device = null;
|
|
3128
|
+
this._initialized = false;
|
|
3129
|
+
}
|
|
3130
|
+
/**
|
|
3131
|
+
* Get performance statistics
|
|
3132
|
+
*/
|
|
3133
|
+
getStats() {
|
|
3134
|
+
const bytesPerPixel = this.config.hdrEnabled ? 8 : 4;
|
|
3135
|
+
const pixelCount = this.currentWidth * this.currentHeight;
|
|
3136
|
+
const renderTargetMemory = (this.pingPongTargets.filter((t) => t !== null).length + this.renderTargets.size) * pixelCount * bytesPerPixel;
|
|
3137
|
+
return {
|
|
3138
|
+
effectCount: this.effects.length,
|
|
3139
|
+
enabledEffects: this.effects.filter((e) => e.enabled).length,
|
|
3140
|
+
renderTargetCount: this.renderTargets.size + 2,
|
|
3141
|
+
estimatedMemoryMB: renderTargetMemory / (1024 * 1024)
|
|
3142
|
+
};
|
|
3143
|
+
}
|
|
3144
|
+
};
|
|
3145
|
+
function createPostProcessPipeline(preset, customConfig) {
|
|
3146
|
+
const presetConfig = preset ? PostProcessPipeline.createPreset(preset) : {};
|
|
3147
|
+
return new PostProcessPipeline({ ...presetConfig, ...customConfig });
|
|
3148
|
+
}
|
|
3149
|
+
function createHDRPipeline() {
|
|
3150
|
+
return createPostProcessPipeline("standard");
|
|
3151
|
+
}
|
|
3152
|
+
function createLDRPipeline() {
|
|
3153
|
+
return createPostProcessPipeline("minimal");
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
// src/rendering/PostProcessing.ts
|
|
3157
|
+
function createDefaultProfile(id, name) {
|
|
3158
|
+
return {
|
|
3159
|
+
id,
|
|
3160
|
+
name,
|
|
3161
|
+
bloom: { enabled: false, threshold: 0.8, intensity: 0.5, radius: 4, softKnee: 0.5 },
|
|
3162
|
+
ssao: { enabled: false, radius: 0.5, intensity: 1, bias: 0.025, samples: 16 },
|
|
3163
|
+
colorGrading: {
|
|
3164
|
+
enabled: false,
|
|
3165
|
+
temperature: 0,
|
|
3166
|
+
tint: 0,
|
|
3167
|
+
saturation: 1,
|
|
3168
|
+
contrast: 1,
|
|
3169
|
+
brightness: 0,
|
|
3170
|
+
gamma: 1
|
|
3171
|
+
},
|
|
3172
|
+
vignette: { enabled: false, intensity: 0.3, smoothness: 0.5, roundness: 1 },
|
|
3173
|
+
chromaticAberration: { enabled: false, intensity: 0.1 },
|
|
3174
|
+
toneMapping: "aces",
|
|
3175
|
+
exposure: 1,
|
|
3176
|
+
antiAliasing: "fxaa"
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
var PP_PRESETS = {
|
|
3180
|
+
cinematic: {
|
|
3181
|
+
name: "Cinematic",
|
|
3182
|
+
bloom: { enabled: true, threshold: 0.7, intensity: 0.6, radius: 5, softKnee: 0.5 },
|
|
3183
|
+
colorGrading: {
|
|
3184
|
+
enabled: true,
|
|
3185
|
+
temperature: 10,
|
|
3186
|
+
tint: -5,
|
|
3187
|
+
saturation: 1.1,
|
|
3188
|
+
contrast: 1.15,
|
|
3189
|
+
brightness: -0.05,
|
|
3190
|
+
gamma: 0.95
|
|
3191
|
+
},
|
|
3192
|
+
vignette: { enabled: true, intensity: 0.35, smoothness: 0.6, roundness: 1 },
|
|
3193
|
+
toneMapping: "filmic"
|
|
3194
|
+
},
|
|
3195
|
+
retro: {
|
|
3196
|
+
name: "Retro",
|
|
3197
|
+
colorGrading: {
|
|
3198
|
+
enabled: true,
|
|
3199
|
+
temperature: 30,
|
|
3200
|
+
tint: 10,
|
|
3201
|
+
saturation: 0.7,
|
|
3202
|
+
contrast: 1.3,
|
|
3203
|
+
brightness: -0.1,
|
|
3204
|
+
gamma: 1.1
|
|
3205
|
+
},
|
|
3206
|
+
chromaticAberration: { enabled: true, intensity: 0.3 },
|
|
3207
|
+
vignette: { enabled: true, intensity: 0.5, smoothness: 0.4, roundness: 0.8 },
|
|
3208
|
+
toneMapping: "reinhard"
|
|
3209
|
+
},
|
|
3210
|
+
sciFi: {
|
|
3211
|
+
name: "Sci-Fi",
|
|
3212
|
+
bloom: { enabled: true, threshold: 0.5, intensity: 1, radius: 8, softKnee: 0.3 },
|
|
3213
|
+
ssao: { enabled: true, radius: 0.3, intensity: 1.5, bias: 0.02, samples: 32 },
|
|
3214
|
+
colorGrading: {
|
|
3215
|
+
enabled: true,
|
|
3216
|
+
temperature: -20,
|
|
3217
|
+
tint: 0,
|
|
3218
|
+
saturation: 0.9,
|
|
3219
|
+
contrast: 1.2,
|
|
3220
|
+
brightness: 0,
|
|
3221
|
+
gamma: 0.9
|
|
3222
|
+
},
|
|
3223
|
+
toneMapping: "aces"
|
|
3224
|
+
}
|
|
3225
|
+
};
|
|
3226
|
+
var PostProcessingStack = class {
|
|
3227
|
+
profiles = /* @__PURE__ */ new Map();
|
|
3228
|
+
activeProfileId = null;
|
|
3229
|
+
constructor() {
|
|
3230
|
+
this.profiles.set("default", createDefaultProfile("default", "Default"));
|
|
3231
|
+
}
|
|
3232
|
+
// ---------------------------------------------------------------------------
|
|
3233
|
+
// Profile Management
|
|
3234
|
+
// ---------------------------------------------------------------------------
|
|
3235
|
+
createProfile(id, name) {
|
|
3236
|
+
const profile = createDefaultProfile(id, name);
|
|
3237
|
+
this.profiles.set(id, profile);
|
|
3238
|
+
return profile;
|
|
3239
|
+
}
|
|
3240
|
+
loadPreset(presetName, id) {
|
|
3241
|
+
const preset = PP_PRESETS[presetName];
|
|
3242
|
+
if (!preset) return null;
|
|
3243
|
+
const profileId = id ?? presetName;
|
|
3244
|
+
const profile = {
|
|
3245
|
+
...createDefaultProfile(profileId, preset.name ?? presetName),
|
|
3246
|
+
...preset,
|
|
3247
|
+
id: profileId
|
|
3248
|
+
};
|
|
3249
|
+
this.profiles.set(profileId, profile);
|
|
3250
|
+
return profile;
|
|
3251
|
+
}
|
|
3252
|
+
getProfile(id) {
|
|
3253
|
+
return this.profiles.get(id);
|
|
3254
|
+
}
|
|
3255
|
+
getProfileCount() {
|
|
3256
|
+
return this.profiles.size;
|
|
3257
|
+
}
|
|
3258
|
+
removeProfile(id) {
|
|
3259
|
+
if (this.activeProfileId === id) this.activeProfileId = null;
|
|
3260
|
+
return this.profiles.delete(id);
|
|
3261
|
+
}
|
|
3262
|
+
// ---------------------------------------------------------------------------
|
|
3263
|
+
// Active Profile
|
|
3264
|
+
// ---------------------------------------------------------------------------
|
|
3265
|
+
setActive(profileId) {
|
|
3266
|
+
if (!this.profiles.has(profileId)) return false;
|
|
3267
|
+
this.activeProfileId = profileId;
|
|
3268
|
+
return true;
|
|
3269
|
+
}
|
|
3270
|
+
getActive() {
|
|
3271
|
+
if (!this.activeProfileId) return null;
|
|
3272
|
+
return this.profiles.get(this.activeProfileId) ?? null;
|
|
3273
|
+
}
|
|
3274
|
+
// ---------------------------------------------------------------------------
|
|
3275
|
+
// Effect Toggle
|
|
3276
|
+
// ---------------------------------------------------------------------------
|
|
3277
|
+
setEffectEnabled(profileId, effect, enabled) {
|
|
3278
|
+
const profile = this.profiles.get(profileId);
|
|
3279
|
+
if (!profile) return false;
|
|
3280
|
+
profile[effect].enabled = enabled;
|
|
3281
|
+
return true;
|
|
3282
|
+
}
|
|
3283
|
+
// ---------------------------------------------------------------------------
|
|
3284
|
+
// Blending
|
|
3285
|
+
// ---------------------------------------------------------------------------
|
|
3286
|
+
/**
|
|
3287
|
+
* Blend between two profiles by factor t (0 = from, 1 = to).
|
|
3288
|
+
* Useful for smooth transitions between environments.
|
|
3289
|
+
*/
|
|
3290
|
+
blendProfiles(fromId, toId, t) {
|
|
3291
|
+
const from = this.profiles.get(fromId);
|
|
3292
|
+
const to = this.profiles.get(toId);
|
|
3293
|
+
if (!from || !to) return null;
|
|
3294
|
+
const lerp = (a, b) => a + (b - a) * t;
|
|
3295
|
+
return {
|
|
3296
|
+
id: `blend_${fromId}_${toId}`,
|
|
3297
|
+
name: `Blend`,
|
|
3298
|
+
bloom: {
|
|
3299
|
+
enabled: t > 0.5 ? to.bloom.enabled : from.bloom.enabled,
|
|
3300
|
+
threshold: lerp(from.bloom.threshold, to.bloom.threshold),
|
|
3301
|
+
intensity: lerp(from.bloom.intensity, to.bloom.intensity),
|
|
3302
|
+
radius: lerp(from.bloom.radius, to.bloom.radius),
|
|
3303
|
+
softKnee: lerp(from.bloom.softKnee, to.bloom.softKnee)
|
|
3304
|
+
},
|
|
3305
|
+
ssao: {
|
|
3306
|
+
enabled: t > 0.5 ? to.ssao.enabled : from.ssao.enabled,
|
|
3307
|
+
radius: lerp(from.ssao.radius, to.ssao.radius),
|
|
3308
|
+
intensity: lerp(from.ssao.intensity, to.ssao.intensity),
|
|
3309
|
+
bias: lerp(from.ssao.bias, to.ssao.bias),
|
|
3310
|
+
samples: Math.round(lerp(from.ssao.samples, to.ssao.samples))
|
|
3311
|
+
},
|
|
3312
|
+
colorGrading: {
|
|
3313
|
+
enabled: t > 0.5 ? to.colorGrading.enabled : from.colorGrading.enabled,
|
|
3314
|
+
temperature: lerp(from.colorGrading.temperature, to.colorGrading.temperature),
|
|
3315
|
+
tint: lerp(from.colorGrading.tint, to.colorGrading.tint),
|
|
3316
|
+
saturation: lerp(from.colorGrading.saturation, to.colorGrading.saturation),
|
|
3317
|
+
contrast: lerp(from.colorGrading.contrast, to.colorGrading.contrast),
|
|
3318
|
+
brightness: lerp(from.colorGrading.brightness, to.colorGrading.brightness),
|
|
3319
|
+
gamma: lerp(from.colorGrading.gamma, to.colorGrading.gamma)
|
|
3320
|
+
},
|
|
3321
|
+
vignette: {
|
|
3322
|
+
enabled: t > 0.5 ? to.vignette.enabled : from.vignette.enabled,
|
|
3323
|
+
intensity: lerp(from.vignette.intensity, to.vignette.intensity),
|
|
3324
|
+
smoothness: lerp(from.vignette.smoothness, to.vignette.smoothness),
|
|
3325
|
+
roundness: lerp(from.vignette.roundness, to.vignette.roundness)
|
|
3326
|
+
},
|
|
3327
|
+
chromaticAberration: {
|
|
3328
|
+
enabled: t > 0.5 ? to.chromaticAberration.enabled : from.chromaticAberration.enabled,
|
|
3329
|
+
intensity: lerp(from.chromaticAberration.intensity, to.chromaticAberration.intensity)
|
|
3330
|
+
},
|
|
3331
|
+
toneMapping: t > 0.5 ? to.toneMapping : from.toneMapping,
|
|
3332
|
+
exposure: lerp(from.exposure, to.exposure),
|
|
3333
|
+
antiAliasing: t > 0.5 ? to.antiAliasing : from.antiAliasing
|
|
3334
|
+
};
|
|
3335
|
+
}
|
|
3336
|
+
};
|
|
3337
|
+
|
|
3338
|
+
// src/rendering/PostProcessStack.ts
|
|
3339
|
+
var _effectId = 0;
|
|
3340
|
+
var PostProcessStack = class {
|
|
3341
|
+
effects = /* @__PURE__ */ new Map();
|
|
3342
|
+
sortedCache = [];
|
|
3343
|
+
dirty = true;
|
|
3344
|
+
enabled = true;
|
|
3345
|
+
// ---------------------------------------------------------------------------
|
|
3346
|
+
// Effect Management
|
|
3347
|
+
// ---------------------------------------------------------------------------
|
|
3348
|
+
addEffect(name, priority, process, params) {
|
|
3349
|
+
const id = `ppfx_${_effectId++}`;
|
|
3350
|
+
const effect = {
|
|
3351
|
+
id,
|
|
3352
|
+
name,
|
|
3353
|
+
priority,
|
|
3354
|
+
enabled: true,
|
|
3355
|
+
weight: 1,
|
|
3356
|
+
params: new Map(Object.entries(params ?? {})),
|
|
3357
|
+
process
|
|
3358
|
+
};
|
|
3359
|
+
this.effects.set(id, effect);
|
|
3360
|
+
this.dirty = true;
|
|
3361
|
+
return effect;
|
|
3362
|
+
}
|
|
3363
|
+
removeEffect(id) {
|
|
3364
|
+
this.dirty = true;
|
|
3365
|
+
return this.effects.delete(id);
|
|
3366
|
+
}
|
|
3367
|
+
// ---------------------------------------------------------------------------
|
|
3368
|
+
// Enable / Disable
|
|
3369
|
+
// ---------------------------------------------------------------------------
|
|
3370
|
+
setEnabled(id, enabled) {
|
|
3371
|
+
const effect = this.effects.get(id);
|
|
3372
|
+
if (effect) effect.enabled = enabled;
|
|
3373
|
+
}
|
|
3374
|
+
setGlobalEnabled(enabled) {
|
|
3375
|
+
this.enabled = enabled;
|
|
3376
|
+
}
|
|
3377
|
+
isGlobalEnabled() {
|
|
3378
|
+
return this.enabled;
|
|
3379
|
+
}
|
|
3380
|
+
setWeight(id, weight) {
|
|
3381
|
+
const effect = this.effects.get(id);
|
|
3382
|
+
if (effect) effect.weight = Math.max(0, Math.min(1, weight));
|
|
3383
|
+
}
|
|
3384
|
+
// ---------------------------------------------------------------------------
|
|
3385
|
+
// Processing
|
|
3386
|
+
// ---------------------------------------------------------------------------
|
|
3387
|
+
process(input, width, height) {
|
|
3388
|
+
if (!this.enabled) return input;
|
|
3389
|
+
const sorted = this.getSorted();
|
|
3390
|
+
let buffer = input;
|
|
3391
|
+
for (const effect of sorted) {
|
|
3392
|
+
if (!effect.enabled || effect.weight <= 0) continue;
|
|
3393
|
+
const processed = effect.process(buffer, width, height);
|
|
3394
|
+
if (effect.weight >= 1) {
|
|
3395
|
+
buffer = processed;
|
|
3396
|
+
} else {
|
|
3397
|
+
const blended = new Float32Array(buffer.length);
|
|
3398
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
3399
|
+
blended[i] = buffer[i] * (1 - effect.weight) + processed[i] * effect.weight;
|
|
3400
|
+
}
|
|
3401
|
+
buffer = blended;
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
return buffer;
|
|
3405
|
+
}
|
|
3406
|
+
// ---------------------------------------------------------------------------
|
|
3407
|
+
// Ordering
|
|
3408
|
+
// ---------------------------------------------------------------------------
|
|
3409
|
+
getSorted() {
|
|
3410
|
+
if (this.dirty) {
|
|
3411
|
+
this.sortedCache = [...this.effects.values()].sort((a, b) => a.priority - b.priority);
|
|
3412
|
+
this.dirty = false;
|
|
3413
|
+
}
|
|
3414
|
+
return this.sortedCache;
|
|
3415
|
+
}
|
|
3416
|
+
reorder(id, newPriority) {
|
|
3417
|
+
const effect = this.effects.get(id);
|
|
3418
|
+
if (effect) {
|
|
3419
|
+
effect.priority = newPriority;
|
|
3420
|
+
this.dirty = true;
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
// ---------------------------------------------------------------------------
|
|
3424
|
+
// Queries
|
|
3425
|
+
// ---------------------------------------------------------------------------
|
|
3426
|
+
getEffect(id) {
|
|
3427
|
+
return this.effects.get(id);
|
|
3428
|
+
}
|
|
3429
|
+
getEffectCount() {
|
|
3430
|
+
return this.effects.size;
|
|
3431
|
+
}
|
|
3432
|
+
getActiveCount() {
|
|
3433
|
+
return [...this.effects.values()].filter((e) => e.enabled).length;
|
|
3434
|
+
}
|
|
3435
|
+
getEffectNames() {
|
|
3436
|
+
return this.getSorted().map((e) => e.name);
|
|
3437
|
+
}
|
|
3438
|
+
setParam(id, param, value) {
|
|
3439
|
+
this.effects.get(id)?.params.set(param, value);
|
|
3440
|
+
}
|
|
3441
|
+
getParam(id, param) {
|
|
3442
|
+
return this.effects.get(id)?.params.get(param);
|
|
3443
|
+
}
|
|
3444
|
+
};
|
|
3445
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3446
|
+
0 && (module.exports = {
|
|
3447
|
+
BLIT_SHADER,
|
|
3448
|
+
BLOOM_SHADER,
|
|
3449
|
+
CAUSTICS_SHADER,
|
|
3450
|
+
CHROMATIC_ABERRATION_SHADER,
|
|
3451
|
+
COLOR_GRADE_SHADER,
|
|
3452
|
+
CausticsEffect,
|
|
3453
|
+
ChromaticAberrationEffect,
|
|
3454
|
+
DEFAULT_PARAMS,
|
|
3455
|
+
DEFAULT_PIPELINE_CONFIG,
|
|
3456
|
+
DOF_SHADER,
|
|
3457
|
+
FILM_GRAIN_SHADER,
|
|
3458
|
+
FOG_SHADER,
|
|
3459
|
+
FULLSCREEN_VERTEX_SHADER,
|
|
3460
|
+
FXAAEffect,
|
|
3461
|
+
FXAA_SHADER,
|
|
3462
|
+
FilmGrainEffect,
|
|
3463
|
+
MOTION_BLUR_SHADER,
|
|
3464
|
+
PP_PRESETS,
|
|
3465
|
+
PostProcessEffect,
|
|
3466
|
+
PostProcessPipeline,
|
|
3467
|
+
PostProcessStack,
|
|
3468
|
+
PostProcessingStack,
|
|
3469
|
+
SHADER_UTILS,
|
|
3470
|
+
SHARPEN_SHADER,
|
|
3471
|
+
SSAOEffect,
|
|
3472
|
+
SSAO_SHADER,
|
|
3473
|
+
SSGIEffect,
|
|
3474
|
+
SSGI_SHADER,
|
|
3475
|
+
SSREffect,
|
|
3476
|
+
SSR_SHADER,
|
|
3477
|
+
SharpenEffect,
|
|
3478
|
+
TONEMAP_SHADER,
|
|
3479
|
+
ToneMapEffect,
|
|
3480
|
+
UNIFORM_SIZES,
|
|
3481
|
+
VIGNETTE_SHADER,
|
|
3482
|
+
VignetteEffect,
|
|
3483
|
+
buildEffectShader,
|
|
3484
|
+
createEffect,
|
|
3485
|
+
createHDRPipeline,
|
|
3486
|
+
createLDRPipeline,
|
|
3487
|
+
createPostProcessPipeline,
|
|
3488
|
+
getDefaultParams,
|
|
3489
|
+
mergeParams,
|
|
3490
|
+
validateParams
|
|
3491
|
+
});
|