@footgun/cobalt 0.1.0
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/LICENSE +21 -0
- package/README.md +18 -0
- package/bundle.js +284 -0
- package/cobalt2.jpeg +0 -0
- package/esbuild.js +20 -0
- package/examples/01-primitives/Game.js +8 -0
- package/examples/01-primitives/component-animation.js +8 -0
- package/examples/01-primitives/component-transform.js +13 -0
- package/examples/01-primitives/constants.js +6 -0
- package/examples/01-primitives/deps.js +2 -0
- package/examples/01-primitives/entity-sprite.js +47 -0
- package/examples/01-primitives/index.html +191 -0
- package/examples/01-primitives/system-renderer.js +37 -0
- package/examples/02-sprites/Game.js +8 -0
- package/examples/02-sprites/assets/spritesheet.json +6276 -0
- package/examples/02-sprites/assets/spritesheet.png +0 -0
- package/examples/02-sprites/assets/spritesheet_emissive.png +0 -0
- package/examples/02-sprites/component-animation.js +8 -0
- package/examples/02-sprites/component-transform.js +13 -0
- package/examples/02-sprites/constants.js +6 -0
- package/examples/02-sprites/deps.js +2 -0
- package/examples/02-sprites/entity-sprite.js +47 -0
- package/examples/02-sprites/index.html +310 -0
- package/examples/02-sprites/system-renderer.js +38 -0
- package/examples/03-tiles/Game.js +8 -0
- package/examples/03-tiles/assets/spelunky-tiles.png +0 -0
- package/examples/03-tiles/assets/spelunky0.png +0 -0
- package/examples/03-tiles/assets/spelunky1.png +0 -0
- package/examples/03-tiles/component-animation.js +8 -0
- package/examples/03-tiles/component-transform.js +13 -0
- package/examples/03-tiles/constants.js +6 -0
- package/examples/03-tiles/deps.js +2 -0
- package/examples/03-tiles/entity-sprite.js +47 -0
- package/examples/03-tiles/index.html +309 -0
- package/examples/03-tiles/system-renderer.js +38 -0
- package/examples/04-overlay/assets/spritesheet.json +22 -0
- package/examples/04-overlay/assets/spritesheet.png +0 -0
- package/examples/04-overlay/assets/spritesheet_emissive.png +0 -0
- package/examples/04-overlay/constants.js +6 -0
- package/examples/04-overlay/deps.js +1 -0
- package/examples/04-overlay/index.html +133 -0
- package/examples/05-bloom/Game.js +8 -0
- package/examples/05-bloom/assets/spritesheet.json +6276 -0
- package/examples/05-bloom/assets/spritesheet.png +0 -0
- package/examples/05-bloom/assets/spritesheet_emissive.png +0 -0
- package/examples/05-bloom/component-animation.js +8 -0
- package/examples/05-bloom/component-transform.js +13 -0
- package/examples/05-bloom/constants.js +6 -0
- package/examples/05-bloom/deps.js +2 -0
- package/examples/05-bloom/entity-sprite.js +47 -0
- package/examples/05-bloom/index.html +357 -0
- package/examples/05-bloom/system-renderer.js +38 -0
- package/examples/06-displacement/Game.js +8 -0
- package/examples/06-displacement/assets/displacement_map_repeat.jpg +0 -0
- package/examples/06-displacement/assets/spelunky-tiles.png +0 -0
- package/examples/06-displacement/assets/spelunky0.png +0 -0
- package/examples/06-displacement/assets/spelunky1.png +0 -0
- package/examples/06-displacement/component-animation.js +8 -0
- package/examples/06-displacement/component-transform.js +13 -0
- package/examples/06-displacement/constants.js +6 -0
- package/examples/06-displacement/deps.js +2 -0
- package/examples/06-displacement/entity-sprite.js +47 -0
- package/examples/06-displacement/index.html +350 -0
- package/examples/06-displacement/system-renderer.js +38 -0
- package/examples/07-sdl/assets/spritesheet.json +22 -0
- package/examples/07-sdl/assets/spritesheet.png +0 -0
- package/examples/07-sdl/assets/spritesheet_emissive.png +0 -0
- package/examples/07-sdl/main.js +109 -0
- package/examples/07-sdl/package.json +19 -0
- package/examples/08-light/Game.js +8 -0
- package/examples/08-light/assets/spelunky-tiles.png +0 -0
- package/examples/08-light/assets/spelunky0.png +0 -0
- package/examples/08-light/assets/spelunky1.png +0 -0
- package/examples/08-light/constants.js +6 -0
- package/examples/08-light/deps.js +2 -0
- package/examples/08-light/index.html +477 -0
- package/package.json +34 -0
- package/src/bloom/bloom.js +467 -0
- package/src/bloom/bloom.wgsl +176 -0
- package/src/cobalt.js +231 -0
- package/src/create-texture-from-buffer.js +39 -0
- package/src/create-texture-from-url.js +35 -0
- package/src/create-texture.js +46 -0
- package/src/deps.js +3 -0
- package/src/displacement/composition.wgsl +58 -0
- package/src/displacement/displacement-composition.ts +161 -0
- package/src/displacement/displacement-parameters-buffer.ts +44 -0
- package/src/displacement/displacement-texture.ts +221 -0
- package/src/displacement/displacement.js +160 -0
- package/src/displacement/displacement.wgsl +31 -0
- package/src/displacement/triangles-buffer.ts +95 -0
- package/src/fb-blit/fb-blit.js +161 -0
- package/src/fb-blit/fb-blit.wgsl +40 -0
- package/src/fb-texture/fb-texture.js +56 -0
- package/src/light/README.md +61 -0
- package/src/light/light.js +148 -0
- package/src/light/lights-buffer.ts +98 -0
- package/src/light/lights-renderer.ts +278 -0
- package/src/light/public-api.js +20 -0
- package/src/light/readme/01_illumination.webp +0 -0
- package/src/light/readme/02_lights_texture.webp +0 -0
- package/src/light/readme/03_lights_texture_decomposed.webp +0 -0
- package/src/light/readme/04_lights_texture_mask.webp +0 -0
- package/src/light/readme/05_lights_obstacle_decomposition.webp +0 -0
- package/src/light/readme/06_lights_hard_cast_shadows.webp +0 -0
- package/src/light/texture/lights-texture-initializer.ts +191 -0
- package/src/light/texture/lights-texture-mask.ts +286 -0
- package/src/light/texture/lights-texture.ts +121 -0
- package/src/light/types.ts +23 -0
- package/src/light/viewport.ts +63 -0
- package/src/overlay/constants.js +1 -0
- package/src/overlay/overlay.js +341 -0
- package/src/overlay/overlay.wgsl +88 -0
- package/src/primitives/constants.js +1 -0
- package/src/primitives/primitives.js +252 -0
- package/src/primitives/primitives.wgsl +54 -0
- package/src/primitives/public-api.js +325 -0
- package/src/scene-composite/scene-composite.js +168 -0
- package/src/scene-composite/scene-composite.wgsl +94 -0
- package/src/sprite/constants.js +1 -0
- package/src/sprite/create-sprite-quads.js +60 -0
- package/src/sprite/public-api.js +215 -0
- package/src/sprite/read-spritesheet.js +103 -0
- package/src/sprite/sorted-binary-insert.js +45 -0
- package/src/sprite/sprite.js +268 -0
- package/src/sprite/sprite.wgsl +103 -0
- package/src/sprite/spritesheet.js +212 -0
- package/src/tile/atlas.js +193 -0
- package/src/tile/tile.js +171 -0
- package/src/tile/tile.wgsl +105 -0
- package/src/uuid.js +3 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import bloomWGSL from './bloom.wgsl'
|
|
2
|
+
import createTexture from '../create-texture.js'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// ported from https://github.com/rledrin/WebGPU-Bloom
|
|
6
|
+
|
|
7
|
+
// TODO: investigate dynamic ubo offsets again. I feel like that could be more efficient
|
|
8
|
+
// good example of this: https://github.com/austinEng/webgpu-samples/blob/main/src/sample/animometer/main.ts
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const BLOOM_MIP_COUNT = 7
|
|
12
|
+
|
|
13
|
+
const MODE_PREFILTER = 0
|
|
14
|
+
const MODE_DOWNSAMPLE = 1
|
|
15
|
+
const MODE_UPSAMPLE_FIRST = 2
|
|
16
|
+
const MODE_UPSAMPLE = 3
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
type: 'cobalt:bloom',
|
|
21
|
+
refs: [
|
|
22
|
+
{ name: 'emissive', type: 'textureView', format: 'rgba16', access: 'read' },
|
|
23
|
+
{ name: 'hdr', type: 'textureView', format: 'rgba16', access: 'read' },
|
|
24
|
+
{ name: 'bloom', type: 'textureView', format: 'rgba16', access: 'readwrite' },
|
|
25
|
+
],
|
|
26
|
+
// @params Object cobalt renderer world object
|
|
27
|
+
// @params Object options optional data passed when initing this node
|
|
28
|
+
onInit: async function (cobalt, options={}) {
|
|
29
|
+
return init(cobalt, options)
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
onRun: function (cobalt, node, webGpuCommandEncoder) {
|
|
33
|
+
// do whatever you need for this node. webgpu renderpasses, etc.
|
|
34
|
+
draw(cobalt, node.data, webGpuCommandEncoder)
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
onDestroy: function (cobalt, node) {
|
|
38
|
+
// any cleanup for your node should go here (releasing textures, etc.)
|
|
39
|
+
destroy(node)
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
onResize: function (cobalt, node) {
|
|
43
|
+
// do whatever you need when the dimensions of the renderer change (resize textures, etc.)
|
|
44
|
+
resize(cobalt, node)
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
onViewportPosition: function (cobalt, node) { },
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
function init (cobalt, nodeData) {
|
|
52
|
+
const { device } = cobalt
|
|
53
|
+
const viewportWidth = cobalt.viewport.width
|
|
54
|
+
const viewportHeight = cobalt.viewport.height
|
|
55
|
+
|
|
56
|
+
const bloom_mat = {
|
|
57
|
+
compute_pipeline: null,
|
|
58
|
+
bind_group: [ ],
|
|
59
|
+
bind_group_layout: [ ],
|
|
60
|
+
bind_groups_textures: [ ],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const layout = device.createBindGroupLayout({
|
|
64
|
+
entries: [
|
|
65
|
+
{
|
|
66
|
+
binding: 0,
|
|
67
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
68
|
+
storageTexture: {
|
|
69
|
+
access: 'write-only',
|
|
70
|
+
format: 'rgba16float',
|
|
71
|
+
viewDimension: '2d'
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
binding: 1,
|
|
76
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
77
|
+
texture: {
|
|
78
|
+
sampleType: 'float',
|
|
79
|
+
viewDimension: '2d',
|
|
80
|
+
multisampled: false
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
binding: 2,
|
|
85
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
86
|
+
texture: {
|
|
87
|
+
sampleType: 'float',
|
|
88
|
+
viewDimension: '2d',
|
|
89
|
+
multisampled: false
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
binding: 3,
|
|
94
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
95
|
+
sampler: { }
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
binding: 4,
|
|
99
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
100
|
+
buffer: {
|
|
101
|
+
type: 'uniform',
|
|
102
|
+
//minBindingSize: 24 // sizeOf(BloomParam)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
binding: 5,
|
|
107
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
108
|
+
buffer: {
|
|
109
|
+
type: 'uniform',
|
|
110
|
+
//minBindingSize: 4 // sizeOf(lode_mode Param)
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
]
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
bloom_mat.bind_group_layout.push(layout)
|
|
117
|
+
|
|
118
|
+
bloom_mat.bind_groups_textures.push(createTexture(
|
|
119
|
+
device,
|
|
120
|
+
'bloom downsampler image 0',
|
|
121
|
+
viewportWidth / 2,
|
|
122
|
+
viewportHeight / 2,
|
|
123
|
+
BLOOM_MIP_COUNT,
|
|
124
|
+
'rgba16float',
|
|
125
|
+
GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
|
|
126
|
+
))
|
|
127
|
+
|
|
128
|
+
bloom_mat.bind_groups_textures.push(createTexture(
|
|
129
|
+
device,
|
|
130
|
+
'bloom downsampler image 1',
|
|
131
|
+
viewportWidth / 2,
|
|
132
|
+
viewportHeight / 2,
|
|
133
|
+
BLOOM_MIP_COUNT,
|
|
134
|
+
'rgba16float',
|
|
135
|
+
GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
|
|
136
|
+
))
|
|
137
|
+
|
|
138
|
+
// link bloom_mat.bind_groups_textures[2]) to bloom_mat.bloomTexture
|
|
139
|
+
//bloom_mat.bind_groups_textures.push(bloom_mat.bloomTexture)
|
|
140
|
+
bloom_mat.bind_groups_textures.push(nodeData.refs.bloom.data)
|
|
141
|
+
/*
|
|
142
|
+
bloom_mat.bind_groups_textures.push(createTexture(
|
|
143
|
+
device,
|
|
144
|
+
'bloom upsampler image',
|
|
145
|
+
viewportWidth / 2,
|
|
146
|
+
viewportHeight / 2,
|
|
147
|
+
BLOOM_MIP_COUNT,
|
|
148
|
+
'rgba16float',
|
|
149
|
+
GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
|
|
150
|
+
))
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
const compute_pipeline_layout = device.createPipelineLayout({
|
|
154
|
+
bindGroupLayouts: bloom_mat.bind_group_layout
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const compute_pipeline = device.createComputePipeline({
|
|
158
|
+
layout: compute_pipeline_layout,
|
|
159
|
+
compute: {
|
|
160
|
+
module: device.createShaderModule({
|
|
161
|
+
code: bloomWGSL,
|
|
162
|
+
}),
|
|
163
|
+
entryPoint: 'cs_main',
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
set_all_bind_group(cobalt, bloom_mat, nodeData)
|
|
168
|
+
|
|
169
|
+
bloom_mat.compute_pipeline = compute_pipeline
|
|
170
|
+
|
|
171
|
+
// return whatever data you want to store for this node
|
|
172
|
+
return bloom_mat
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
function set_all_bind_group (cobalt, bloom_mat, node) {
|
|
177
|
+
|
|
178
|
+
const { refs } = node
|
|
179
|
+
|
|
180
|
+
const { device } = cobalt
|
|
181
|
+
|
|
182
|
+
// create a buffer that holds static parameters, shared across all bloom bind groups
|
|
183
|
+
const bloom_threshold = node.options.bloom_threshold ?? 0.1 //1.0
|
|
184
|
+
const bloom_knee = node.options.bloom_knee ?? 0.2
|
|
185
|
+
const combine_constant = node.options.bloom_combine_constant ?? 0.68
|
|
186
|
+
|
|
187
|
+
const dat = new Float32Array([ bloom_threshold,
|
|
188
|
+
bloom_threshold - bloom_knee,
|
|
189
|
+
bloom_knee * 2.0,
|
|
190
|
+
0.25 / bloom_knee,
|
|
191
|
+
combine_constant,
|
|
192
|
+
// required byte alignment bs
|
|
193
|
+
0, 0, 0
|
|
194
|
+
])
|
|
195
|
+
|
|
196
|
+
const params_buf = device.createBuffer({
|
|
197
|
+
label: 'bloom static parameters buffer',
|
|
198
|
+
size: dat.byteLength, // vec4<f32> and f32 and u32 with 4 bytes per float32 and 4 bytes per u32
|
|
199
|
+
mappedAtCreation: true,
|
|
200
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
new Float32Array(params_buf.getMappedRange()).set(dat)
|
|
204
|
+
|
|
205
|
+
params_buf.unmap()
|
|
206
|
+
|
|
207
|
+
bloom_mat.bind_group.length = 0
|
|
208
|
+
bloom_mat.params_buf = params_buf
|
|
209
|
+
|
|
210
|
+
// Prefilter bind group
|
|
211
|
+
bloom_mat.bind_group.push(create_bloom_bind_group(
|
|
212
|
+
device,
|
|
213
|
+
bloom_mat,
|
|
214
|
+
bloom_mat.bind_groups_textures[0].mip_view[0],
|
|
215
|
+
refs.emissive.data.view,
|
|
216
|
+
refs.hdr.data.view, // unused here, only for upsample passes
|
|
217
|
+
refs.hdr.data.sampler,
|
|
218
|
+
params_buf,
|
|
219
|
+
MODE_PREFILTER << 16 | 0, // mode_lod value
|
|
220
|
+
));
|
|
221
|
+
|
|
222
|
+
// Downsample bind groups
|
|
223
|
+
for ( let i=1; i < BLOOM_MIP_COUNT; i++) {
|
|
224
|
+
// Ping
|
|
225
|
+
bloom_mat.bind_group.push(create_bloom_bind_group(
|
|
226
|
+
device,
|
|
227
|
+
bloom_mat,
|
|
228
|
+
bloom_mat.bind_groups_textures[1].mip_view[i],
|
|
229
|
+
bloom_mat.bind_groups_textures[0].view,
|
|
230
|
+
refs.hdr.data.view, // unused here, only for upsample passes
|
|
231
|
+
refs.hdr.data.sampler,
|
|
232
|
+
params_buf,
|
|
233
|
+
MODE_DOWNSAMPLE << 16 | (i - 1), // mode_lod value
|
|
234
|
+
))
|
|
235
|
+
|
|
236
|
+
// Pong
|
|
237
|
+
bloom_mat.bind_group.push(create_bloom_bind_group(
|
|
238
|
+
device,
|
|
239
|
+
bloom_mat,
|
|
240
|
+
bloom_mat.bind_groups_textures[0].mip_view[i],
|
|
241
|
+
bloom_mat.bind_groups_textures[1].view,
|
|
242
|
+
refs.hdr.data.view, // unused here, only for upsample passes
|
|
243
|
+
refs.hdr.data.sampler,
|
|
244
|
+
params_buf,
|
|
245
|
+
MODE_DOWNSAMPLE << 16 | i, // mode_lod value
|
|
246
|
+
))
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// First Upsample
|
|
250
|
+
bloom_mat.bind_group.push(create_bloom_bind_group(
|
|
251
|
+
device,
|
|
252
|
+
bloom_mat,
|
|
253
|
+
bloom_mat.bind_groups_textures[2].mip_view[BLOOM_MIP_COUNT - 1],
|
|
254
|
+
bloom_mat.bind_groups_textures[0].view,
|
|
255
|
+
refs.hdr.data.view, // unused here, only for upsample passes
|
|
256
|
+
refs.hdr.data.sampler,
|
|
257
|
+
params_buf,
|
|
258
|
+
MODE_UPSAMPLE_FIRST << 16 | (BLOOM_MIP_COUNT - 2), // mode_lod value
|
|
259
|
+
))
|
|
260
|
+
|
|
261
|
+
let o = true
|
|
262
|
+
|
|
263
|
+
// Upsample
|
|
264
|
+
for (let i = BLOOM_MIP_COUNT-2; i >= 0; i--) {
|
|
265
|
+
if (o) {
|
|
266
|
+
bloom_mat.bind_group.push(create_bloom_bind_group(
|
|
267
|
+
device,
|
|
268
|
+
bloom_mat,
|
|
269
|
+
bloom_mat.bind_groups_textures[1].mip_view[i],
|
|
270
|
+
bloom_mat.bind_groups_textures[0].view,
|
|
271
|
+
bloom_mat.bind_groups_textures[2].view,
|
|
272
|
+
refs.hdr.data.sampler,
|
|
273
|
+
params_buf,
|
|
274
|
+
MODE_UPSAMPLE << 16 | i, // mode_lod value
|
|
275
|
+
))
|
|
276
|
+
o = false
|
|
277
|
+
} else {
|
|
278
|
+
bloom_mat.bind_group.push(create_bloom_bind_group(
|
|
279
|
+
device,
|
|
280
|
+
bloom_mat,
|
|
281
|
+
bloom_mat.bind_groups_textures[2].mip_view[i],
|
|
282
|
+
bloom_mat.bind_groups_textures[0].view,
|
|
283
|
+
bloom_mat.bind_groups_textures[1].view,
|
|
284
|
+
refs.hdr.data.sampler,
|
|
285
|
+
params_buf,
|
|
286
|
+
MODE_UPSAMPLE << 16 | i, // mode_lod value
|
|
287
|
+
))
|
|
288
|
+
o = true
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
function create_bloom_bind_group (device, bloom_mat, output_image, input_image, bloom_image, sampler, params_buf, mode_lod) {
|
|
295
|
+
|
|
296
|
+
const dat2 = new Uint32Array([ mode_lod ])
|
|
297
|
+
|
|
298
|
+
const lod_buf = device.createBuffer({
|
|
299
|
+
label: 'bloom static mode_lod buffer',
|
|
300
|
+
size: dat2.byteLength,
|
|
301
|
+
mappedAtCreation: true,
|
|
302
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
new Uint32Array(lod_buf.getMappedRange()).set(dat2)
|
|
306
|
+
|
|
307
|
+
lod_buf.unmap()
|
|
308
|
+
|
|
309
|
+
return device.createBindGroup({
|
|
310
|
+
label: 'bloom bind group layout',
|
|
311
|
+
layout: bloom_mat.bind_group_layout[0],
|
|
312
|
+
entries: [
|
|
313
|
+
{
|
|
314
|
+
binding: 0,
|
|
315
|
+
resource: output_image
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
binding: 1,
|
|
319
|
+
resource: input_image
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
binding: 2,
|
|
323
|
+
resource: bloom_image
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
binding: 3,
|
|
327
|
+
resource: sampler
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
binding: 4,
|
|
331
|
+
resource: {
|
|
332
|
+
buffer: params_buf
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
binding: 5,
|
|
337
|
+
resource: {
|
|
338
|
+
buffer: lod_buf
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
]
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
function draw (cobalt, bloom_mat, commandEncoder) {
|
|
347
|
+
const MODE_PREFILTER = 0
|
|
348
|
+
const MODE_DOWNSAMPLE = 1
|
|
349
|
+
const MODE_UPSAMPLE_FIRST = 2
|
|
350
|
+
const MODE_UPSAMPLE = 3
|
|
351
|
+
|
|
352
|
+
let bind_group_index = 0
|
|
353
|
+
|
|
354
|
+
const compute_pass = commandEncoder.beginComputePass({
|
|
355
|
+
label: 'bloom Compute Pass',
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
compute_pass.setPipeline(bloom_mat.compute_pipeline)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
// PreFilter
|
|
362
|
+
compute_pass.setBindGroup(0, bloom_mat.bind_group[bind_group_index])
|
|
363
|
+
bind_group_index += 1
|
|
364
|
+
|
|
365
|
+
let mip_size = get_mip_size(0, bloom_mat.bind_groups_textures[0])
|
|
366
|
+
|
|
367
|
+
compute_pass.dispatchWorkgroups(mip_size.width / 8 + 1, mip_size.height / 4 + 1, 1)
|
|
368
|
+
|
|
369
|
+
// Downsample
|
|
370
|
+
for (let i=1; i < BLOOM_MIP_COUNT; i++) {
|
|
371
|
+
mip_size = get_mip_size(i, bloom_mat.bind_groups_textures[0])
|
|
372
|
+
|
|
373
|
+
// Ping
|
|
374
|
+
compute_pass.setBindGroup(0, bloom_mat.bind_group[bind_group_index])
|
|
375
|
+
bind_group_index += 1
|
|
376
|
+
compute_pass.dispatchWorkgroups(mip_size.width / 8 + 1, mip_size.height / 4 + 1, 1)
|
|
377
|
+
|
|
378
|
+
// Pong
|
|
379
|
+
compute_pass.setBindGroup(0, bloom_mat.bind_group[bind_group_index])
|
|
380
|
+
bind_group_index += 1
|
|
381
|
+
compute_pass.dispatchWorkgroups(mip_size.width / 8 + 1, mip_size.height / 4 + 1, 1)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
// First Upsample
|
|
386
|
+
compute_pass.setBindGroup(0, bloom_mat.bind_group[bind_group_index])
|
|
387
|
+
bind_group_index += 1
|
|
388
|
+
mip_size = get_mip_size(BLOOM_MIP_COUNT - 1, bloom_mat.bind_groups_textures[2])
|
|
389
|
+
compute_pass.dispatchWorkgroups(mip_size.width / 8 + 1, mip_size.height / 4 + 1, 1)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
// Upsample
|
|
393
|
+
for (let i=BLOOM_MIP_COUNT - 2; i >= 0; i--) {
|
|
394
|
+
mip_size = get_mip_size(i, bloom_mat.bind_groups_textures[2])
|
|
395
|
+
|
|
396
|
+
compute_pass.setBindGroup(0, bloom_mat.bind_group[bind_group_index])
|
|
397
|
+
bind_group_index += 1
|
|
398
|
+
compute_pass.dispatchWorkgroups(mip_size.width / 8 + 1, mip_size.height / 4 + 1, 1)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
compute_pass.end()
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
function get_mip_size (current_mip, texture) {
|
|
407
|
+
let width = texture.size.width
|
|
408
|
+
let height = texture.size.height
|
|
409
|
+
|
|
410
|
+
for (let i =0; i < current_mip; i++) {
|
|
411
|
+
width /= 2
|
|
412
|
+
height /= 2
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return { width, height, depthOrArrayLayers: 1 }
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
function resize (cobalt, nodeData) {
|
|
420
|
+
const { device } = cobalt
|
|
421
|
+
const bloom_mat = nodeData.data
|
|
422
|
+
destroy(bloom_mat)
|
|
423
|
+
|
|
424
|
+
bloom_mat.bind_groups_textures.push(createTexture(
|
|
425
|
+
device,
|
|
426
|
+
'bloom downsampler image 0',
|
|
427
|
+
cobalt.viewport.width / 2,
|
|
428
|
+
cobalt.viewport.height / 2,
|
|
429
|
+
BLOOM_MIP_COUNT,
|
|
430
|
+
'rgba16float',
|
|
431
|
+
GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
|
|
432
|
+
))
|
|
433
|
+
|
|
434
|
+
bloom_mat.bind_groups_textures.push(createTexture(
|
|
435
|
+
device,
|
|
436
|
+
'bloom downsampler image 1',
|
|
437
|
+
cobalt.viewport.width / 2,
|
|
438
|
+
cobalt.viewport.height / 2,
|
|
439
|
+
BLOOM_MIP_COUNT,
|
|
440
|
+
'rgba16float',
|
|
441
|
+
GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
|
|
442
|
+
))
|
|
443
|
+
|
|
444
|
+
// link bloom_mat.bind_groups_textures[2]) to bloom_mat.bloomTexture
|
|
445
|
+
bloom_mat.bind_groups_textures.push(nodeData.refs.bloom.data)
|
|
446
|
+
/*
|
|
447
|
+
bloom_mat.bind_groups_textures.push(createTexture(
|
|
448
|
+
device,
|
|
449
|
+
'bloom upsampler image',
|
|
450
|
+
cobalt.viewport.width / 2,
|
|
451
|
+
cobalt.viewport.height / 2,
|
|
452
|
+
BLOOM_MIP_COUNT,
|
|
453
|
+
'rgba16float',
|
|
454
|
+
GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
|
|
455
|
+
))
|
|
456
|
+
*/
|
|
457
|
+
|
|
458
|
+
set_all_bind_group(cobalt, bloom_mat, nodeData)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
function destroy (bloom_mat) {
|
|
463
|
+
for (const t of bloom_mat.bind_groups_textures)
|
|
464
|
+
t.texture.destroy()
|
|
465
|
+
|
|
466
|
+
bloom_mat.bind_groups_textures.length = 0
|
|
467
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Compute Shader
|
|
2
|
+
|
|
3
|
+
const BLOOM_MIP_COUNT: i32 = 7;
|
|
4
|
+
|
|
5
|
+
const MODE_PREFILTER: u32 = 0u;
|
|
6
|
+
const MODE_DOWNSAMPLE: u32 = 1u;
|
|
7
|
+
const MODE_UPSAMPLE_FIRST: u32 = 2u;
|
|
8
|
+
const MODE_UPSAMPLE: u32 = 3u;
|
|
9
|
+
|
|
10
|
+
const EPSILON: f32 = 1.0e-4;
|
|
11
|
+
|
|
12
|
+
struct bloom_param {
|
|
13
|
+
parameters: vec4<f32>, // (x) threshold, (y) threshold - knee, (z) knee * 2, (w) 0.25 / knee
|
|
14
|
+
combine_constant: f32,
|
|
15
|
+
doop: u32,
|
|
16
|
+
ferp: u32,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
struct mode_lod_param {
|
|
20
|
+
mode_lod: u32,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@group(0) @binding(0) var output_texture: texture_storage_2d<rgba16float, write>;
|
|
25
|
+
@group(0) @binding(1) var input_texture: texture_2d<f32>;
|
|
26
|
+
@group(0) @binding(2) var bloom_texture: texture_2d<f32>;
|
|
27
|
+
@group(0) @binding(3) var samp: sampler;
|
|
28
|
+
@group(0) @binding(4) var<uniform> param: bloom_param;
|
|
29
|
+
@group(0) @binding(5) var<uniform> pc: mode_lod_param;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// PushConstants don't work in webgpu in chrome because they've been removed from the spec for v1 :(
|
|
33
|
+
// might be added after v1 though https://github.com/gpuweb/gpuweb/issues/75
|
|
34
|
+
//var<push_constant> pc: PushConstants;
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
// Quadratic color thresholding
|
|
38
|
+
// curve = (threshold - knee, knee * 2, 0.25 / knee)
|
|
39
|
+
fn QuadraticThreshold(color: vec4<f32>, threshold: f32, curve: vec3<f32>) -> vec4<f32>
|
|
40
|
+
{
|
|
41
|
+
// Maximum pixel brightness
|
|
42
|
+
let brightness = max(max(color.r, color.g), color.b);
|
|
43
|
+
// Quadratic curve
|
|
44
|
+
var rq: f32 = clamp(brightness - curve.x, 0.0, curve.y);
|
|
45
|
+
rq = curve.z * (rq * rq);
|
|
46
|
+
let ret_color = color * max(rq, brightness - threshold) / max(brightness, EPSILON);
|
|
47
|
+
return ret_color;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn Prefilter(color: vec4<f32>, uv: vec2<f32>) -> vec4<f32>
|
|
51
|
+
{
|
|
52
|
+
let clamp_value = 20.0;
|
|
53
|
+
var c = min(vec4<f32>(clamp_value), color);
|
|
54
|
+
c = QuadraticThreshold(color, param.parameters.x, param.parameters.yzw);
|
|
55
|
+
return c;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn DownsampleBox13(tex: texture_2d<f32>, lod: f32, uv: vec2<f32>, tex_size: vec2<f32>) -> vec3<f32>
|
|
59
|
+
{
|
|
60
|
+
// Center
|
|
61
|
+
let A = textureSampleLevel(tex, samp, uv, lod).rgb;
|
|
62
|
+
|
|
63
|
+
let texel_size = tex_size * 0.5; // Sample from center of texels
|
|
64
|
+
|
|
65
|
+
// Inner box
|
|
66
|
+
let B = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(-1.0, -1.0), lod).rgb;
|
|
67
|
+
let C = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(-1.0, 1.0), lod).rgb;
|
|
68
|
+
let D = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(1.0, 1.0), lod).rgb;
|
|
69
|
+
let E = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(1.0, -1.0), lod).rgb;
|
|
70
|
+
|
|
71
|
+
// Outer box
|
|
72
|
+
let F = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(-2.0, -2.0), lod).rgb;
|
|
73
|
+
let G = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(-2.0, 0.0), lod).rgb;
|
|
74
|
+
let H = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(0.0, 2.0), lod).rgb;
|
|
75
|
+
let I = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(2.0, 2.0), lod).rgb;
|
|
76
|
+
let J = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(2.0, 2.0), lod).rgb;
|
|
77
|
+
let K = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(2.0, 0.0), lod).rgb;
|
|
78
|
+
let L = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(-2.0, -2.0), lod).rgb;
|
|
79
|
+
let M = textureSampleLevel(tex, samp, uv + texel_size * vec2<f32>(0.0, -2.0), lod).rgb;
|
|
80
|
+
|
|
81
|
+
// Weights
|
|
82
|
+
var result: vec3<f32> = vec3<f32>(0.0);
|
|
83
|
+
// Inner box
|
|
84
|
+
result = result + (B + C + D + E) * 0.5;
|
|
85
|
+
// Bottom-left box
|
|
86
|
+
result = result + (F + G + A + M) * 0.125;
|
|
87
|
+
// Top-left box
|
|
88
|
+
result = result + (G + H + I + A) * 0.125;
|
|
89
|
+
// Top-right box
|
|
90
|
+
result = result + (A + I + J + K) * 0.125;
|
|
91
|
+
// Bottom-right box
|
|
92
|
+
result = result + (M + A + K + L) * 0.125;
|
|
93
|
+
|
|
94
|
+
// 4 samples each
|
|
95
|
+
result = result * 0.25;
|
|
96
|
+
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fn UpsampleTent9(tex: texture_2d<f32>, lod: f32, uv: vec2<f32>, texel_size: vec2<f32>, radius: f32) -> vec3<f32>
|
|
101
|
+
{
|
|
102
|
+
let offset = texel_size.xyxy * vec4<f32>(1.0, 1.0, -1.0, 0.0) * radius;
|
|
103
|
+
|
|
104
|
+
// Center
|
|
105
|
+
var result: vec3<f32> = textureSampleLevel(tex, samp, uv, lod).rgb * 4.0;
|
|
106
|
+
|
|
107
|
+
result = result + textureSampleLevel(tex, samp, uv - offset.xy, lod).rgb;
|
|
108
|
+
result = result + textureSampleLevel(tex, samp, uv - offset.wy, lod).rgb * 2.0;
|
|
109
|
+
result = result + textureSampleLevel(tex, samp, uv - offset.zy, lod).rgb;
|
|
110
|
+
|
|
111
|
+
result = result + textureSampleLevel(tex, samp, uv + offset.zw, lod).rgb * 2.0;
|
|
112
|
+
result = result + textureSampleLevel(tex, samp, uv + offset.xw, lod).rgb * 2.0;
|
|
113
|
+
|
|
114
|
+
result = result + textureSampleLevel(tex, samp, uv + offset.zy, lod).rgb;
|
|
115
|
+
result = result + textureSampleLevel(tex, samp, uv + offset.wy, lod).rgb * 2.0;
|
|
116
|
+
result = result + textureSampleLevel(tex, samp, uv + offset.xy, lod).rgb;
|
|
117
|
+
|
|
118
|
+
return result * (1.0 / 16.0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn combine(ex_color: vec3<f32>, color_to_add: vec3<f32>, combine_constant: f32) -> vec3<f32>
|
|
122
|
+
{
|
|
123
|
+
let existing_color = ex_color + (-color_to_add);
|
|
124
|
+
let blended_color = (combine_constant * existing_color) + color_to_add;
|
|
125
|
+
return blended_color;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@compute @workgroup_size(8, 4, 1)
|
|
130
|
+
fn cs_main(@builtin(global_invocation_id) global_invocation_id: vec3<u32>)
|
|
131
|
+
{
|
|
132
|
+
let mode = pc.mode_lod >> 16u;
|
|
133
|
+
let lod = pc.mode_lod & 65535u;
|
|
134
|
+
|
|
135
|
+
let imgSize = textureDimensions(output_texture);
|
|
136
|
+
|
|
137
|
+
if (global_invocation_id.x < u32(imgSize.x) && global_invocation_id.y < u32(imgSize.y)) {
|
|
138
|
+
|
|
139
|
+
var texCoords: vec2<f32> = vec2<f32>(f32(global_invocation_id.x) / f32(imgSize.x), f32(global_invocation_id.y) / f32(imgSize.y));
|
|
140
|
+
texCoords = texCoords + (1.0 / vec2<f32>(imgSize)) * 0.5;
|
|
141
|
+
|
|
142
|
+
let texSize = vec2<f32>(textureDimensions(input_texture, i32(lod)));
|
|
143
|
+
var color: vec4<f32> = vec4<f32>(1.0);
|
|
144
|
+
|
|
145
|
+
if (mode == MODE_PREFILTER)
|
|
146
|
+
{
|
|
147
|
+
color = vec4<f32>(DownsampleBox13(input_texture, f32(lod), texCoords, 1.0 / texSize), 1.0);
|
|
148
|
+
color = Prefilter(color, texCoords);
|
|
149
|
+
}
|
|
150
|
+
else if (mode == MODE_DOWNSAMPLE)
|
|
151
|
+
{
|
|
152
|
+
color = vec4<f32>(DownsampleBox13(input_texture, f32(lod), texCoords, 1.0 / texSize), 1.0);
|
|
153
|
+
}
|
|
154
|
+
else if (mode == MODE_UPSAMPLE_FIRST)
|
|
155
|
+
{
|
|
156
|
+
let bloomTexSize = textureDimensions(input_texture, i32(lod) + 1);
|
|
157
|
+
let sampleScale = 1.0;
|
|
158
|
+
let upsampledTexture = UpsampleTent9(input_texture, f32(lod) + 1.0, texCoords, 1.0 / vec2<f32>(bloomTexSize), sampleScale);
|
|
159
|
+
|
|
160
|
+
let existing = textureSampleLevel(input_texture, samp, texCoords, f32(lod)).rgb;
|
|
161
|
+
color = vec4<f32>(combine(existing, upsampledTexture, param.combine_constant), 1.0);
|
|
162
|
+
}
|
|
163
|
+
else if (mode == MODE_UPSAMPLE)
|
|
164
|
+
{
|
|
165
|
+
let bloomTexSize = textureDimensions(bloom_texture, i32(lod) + 1);
|
|
166
|
+
let sampleScale = 1.0;
|
|
167
|
+
let upsampledTexture = UpsampleTent9(bloom_texture, f32(lod) + 1.0, texCoords, 1.0 / vec2<f32>(bloomTexSize), sampleScale);
|
|
168
|
+
|
|
169
|
+
let existing = textureSampleLevel(input_texture, samp, texCoords, f32(lod)).rgb;
|
|
170
|
+
color = vec4<f32>(combine(existing, upsampledTexture, param.combine_constant), 1.0);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
textureStore(output_texture, vec2<i32>(global_invocation_id.xy), color);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|