@footgun/cobalt 0.10.0 → 0.11.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/CHANGELOG.md +6 -0
- package/bundle.js +117 -91
- package/examples/03-tiles/index.html +3 -3
- package/examples/04-overlay/index.html +2 -102
- package/examples/06-displacement/index.html +2 -2
- package/examples/08-light/index.html +10 -70
- package/package.json +1 -1
- package/src/cobalt.js +3 -3
- package/src/create-texture.js +13 -4
- package/src/fb-texture/fb-texture.js +1 -0
- package/src/light/light.js +62 -500
- package/src/light/lights-buffer.js +59 -55
- package/src/light/lights-renderer.js +230 -0
- package/src/light/public-api.js +4 -3
- package/src/light/texture/lights-texture-initializer.js +175 -0
- package/src/light/texture/lights-texture-mask.js +167 -187
- package/src/light/texture/lights-texture.js +85 -0
- package/src/light/viewport.js +40 -0
- package/src/sprite/sprite.js +2 -2
- package/src/spritesheet/spritesheet.js +17 -14
- package/src/{tile-hdr → tile}/tile.js +1 -1
- /package/src/{tile-hdr → tile}/atlas.js +0 -0
- /package/src/{tile-hdr → tile}/tile.wgsl +0 -0
package/src/light/light.js
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
import * as wgpuMatrix from 'wgpu-matrix'
|
|
2
1
|
import getPreferredFormat from '../get-preferred-format.js'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
destroyLightsBuffer,
|
|
6
|
-
LIGHTS_STRUCT_DEFINITION,
|
|
7
|
-
writeLightsBuffer,
|
|
8
|
-
} from './lights-buffer.js'
|
|
2
|
+
import { LightsBuffer } from './lights-buffer.js'
|
|
3
|
+
import { LightsRenderer } from './lights-renderer.js'
|
|
9
4
|
import * as publicAPI from './public-api.js'
|
|
10
|
-
import {
|
|
11
|
-
createLightsTextureMask,
|
|
12
|
-
destroyMask,
|
|
13
|
-
setMaskLightsCount,
|
|
14
|
-
setMaskObstacles,
|
|
15
|
-
} from './texture/lights-texture-mask.js'
|
|
5
|
+
import { Viewport } from './viewport.js'
|
|
16
6
|
|
|
17
7
|
/**
|
|
18
8
|
* 2D lighting and Shadows
|
|
@@ -24,34 +14,38 @@ import {
|
|
|
24
14
|
export default {
|
|
25
15
|
type: 'cobalt:light',
|
|
26
16
|
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
// cobalt event handling functions
|
|
18
|
+
|
|
19
|
+
// @params Object cobalt renderer world object
|
|
20
|
+
// @params Object options optional data passed when initing this node
|
|
21
|
+
onInit: async function (cobalt, options = {}) {
|
|
22
|
+
return init(cobalt, options)
|
|
29
23
|
},
|
|
30
24
|
|
|
31
25
|
onRun: function (cobalt, node, webGpuCommandEncoder) {
|
|
26
|
+
// do whatever you need for this node. webgpu renderpasses, etc.
|
|
32
27
|
draw(cobalt, node, webGpuCommandEncoder)
|
|
33
28
|
},
|
|
34
29
|
|
|
35
30
|
onDestroy: function (cobalt, node) {
|
|
31
|
+
// any cleanup for your node should go here (releasing textures, etc.)
|
|
36
32
|
destroy(node)
|
|
37
33
|
},
|
|
38
34
|
|
|
39
35
|
onResize: function (cobalt, node) {
|
|
36
|
+
// runs when the viewport size changes (handle resizing textures, etc.)
|
|
40
37
|
resize(cobalt, node)
|
|
41
38
|
},
|
|
42
39
|
|
|
43
40
|
onViewportPosition: function (cobalt, node) {
|
|
44
|
-
|
|
45
|
-
node.data.
|
|
46
|
-
node.data.invViewProjectionMatrix = computeInvertViewProjectionMatrix(
|
|
47
|
-
node.data.viewportWidth,
|
|
48
|
-
node.data.viewportHeight,
|
|
49
|
-
node.data.viewportZoom,
|
|
50
|
-
node.data.viewportTopLeft,
|
|
51
|
-
)
|
|
41
|
+
// runs when the viewport position changes
|
|
42
|
+
node.data.viewport.setTopLeft(...cobalt.viewport.position)
|
|
52
43
|
},
|
|
53
44
|
|
|
54
|
-
|
|
45
|
+
// optional
|
|
46
|
+
customFunctions: {
|
|
47
|
+
...publicAPI,
|
|
48
|
+
},
|
|
55
49
|
}
|
|
56
50
|
|
|
57
51
|
async function init(cobalt, node) {
|
|
@@ -60,427 +54,61 @@ async function init(cobalt, node) {
|
|
|
60
54
|
// a 2048x2048 light texture with 4 channels (rgba) with each light lighting a 256x256 region can hold 256 lights
|
|
61
55
|
const MAX_LIGHT_COUNT = 256
|
|
62
56
|
const MAX_LIGHT_SIZE = 256
|
|
63
|
-
const
|
|
64
|
-
const resolutionPerLight = MAX_LIGHT_SIZE
|
|
65
|
-
const filtering = 'nearest'
|
|
66
|
-
|
|
67
|
-
// --- lights storage buffer ---
|
|
68
|
-
const lightsBufferData = createLightsBuffer(device, MAX_LIGHT_COUNT)
|
|
69
|
-
|
|
70
|
-
// --- lights atlas texture ---
|
|
71
|
-
const cellsCount = MAX_LIGHT_COUNT / 4
|
|
72
|
-
const gridSize = {
|
|
73
|
-
x: Math.ceil(Math.sqrt(cellsCount)),
|
|
74
|
-
y: 0,
|
|
75
|
-
}
|
|
76
|
-
gridSize.y = Math.ceil(cellsCount / gridSize.x)
|
|
77
|
-
|
|
78
|
-
const lightTextureSize = {
|
|
79
|
-
width: gridSize.x * resolutionPerLight,
|
|
80
|
-
height: gridSize.y * resolutionPerLight,
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const lightsTexture = device.createTexture({
|
|
84
|
-
label: 'LightsTexture texture',
|
|
85
|
-
size: [lightTextureSize.width, lightTextureSize.height],
|
|
86
|
-
format: textureFormat,
|
|
87
|
-
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
const lightsTextureRenderpassDescriptor = {
|
|
91
|
-
label: 'lights-renderer render to texture renderpass',
|
|
92
|
-
colorAttachments: [
|
|
93
|
-
{
|
|
94
|
-
view: lightsTexture.createView(),
|
|
95
|
-
clearValue: [0, 0, 0, 1],
|
|
96
|
-
loadOp: 'load',
|
|
97
|
-
storeOp: 'store',
|
|
98
|
-
},
|
|
99
|
-
],
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// descriptor object passed to initializer + mask (metadata only, no GPU resources)
|
|
103
|
-
const lightsTextureDescriptor = {
|
|
104
|
-
gridSize,
|
|
105
|
-
format: textureFormat,
|
|
106
|
-
sampleCount: 1,
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// --- initializer sub-pass (radial attenuation pre-pass, baked render bundle) ---
|
|
110
|
-
const initializerShaderModule = device.createShaderModule({
|
|
111
|
-
label: 'LightsTextureInitializer shader module',
|
|
112
|
-
code: `
|
|
113
|
-
${LIGHTS_STRUCT_DEFINITION}
|
|
114
|
-
|
|
115
|
-
@group(0) @binding(0) var<storage,read> lightsBuffer: LightsBuffer;
|
|
116
|
-
|
|
117
|
-
struct VertexIn {
|
|
118
|
-
@builtin(vertex_index) vertexIndex: u32,
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
struct VertexOut {
|
|
122
|
-
@builtin(position) position: vec4<f32>,
|
|
123
|
-
@location(0) uv: vec2<f32>,
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const cellsGridSizeU = vec2<u32>(${gridSize.x}, ${gridSize.y});
|
|
127
|
-
const cellsGridSizeF = vec2<f32>(${gridSize.x}, ${gridSize.y});
|
|
128
|
-
|
|
129
|
-
@vertex
|
|
130
|
-
fn main_vertex(in: VertexIn) -> VertexOut {
|
|
131
|
-
const corners = array<vec2<f32>, 4>(
|
|
132
|
-
vec2<f32>(-1, -1),
|
|
133
|
-
vec2<f32>(1, -1),
|
|
134
|
-
vec2<f32>(-1, 1),
|
|
135
|
-
vec2<f32>(1, 1),
|
|
136
|
-
);
|
|
137
|
-
let screenPosition = corners[in.vertexIndex];
|
|
138
|
-
|
|
139
|
-
var out: VertexOut;
|
|
140
|
-
out.position = vec4<f32>(screenPosition, 0.0, 1.0);
|
|
141
|
-
out.uv = (0.5 + 0.5 * screenPosition) * cellsGridSizeF;
|
|
142
|
-
return out;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
struct FragmentOut {
|
|
146
|
-
@location(0) color: vec4<f32>,
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
struct LightProperties {
|
|
150
|
-
radius: f32,
|
|
151
|
-
intensity: f32,
|
|
152
|
-
attenuationLinear: f32,
|
|
153
|
-
attenuationExp: f32,
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
fn get_light_properties(lightId: u32) -> LightProperties {
|
|
157
|
-
var p: LightProperties;
|
|
158
|
-
if (lightId < lightsBuffer.count) {
|
|
159
|
-
let light = lightsBuffer.lights[lightId];
|
|
160
|
-
p.radius = light.radius;
|
|
161
|
-
p.intensity = 1.0;
|
|
162
|
-
p.attenuationLinear = light.attenuationLinear;
|
|
163
|
-
p.attenuationExp = light.attenuationExp;
|
|
164
|
-
}
|
|
165
|
-
return p;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
@fragment
|
|
169
|
-
fn main_fragment(in: VertexOut) -> FragmentOut {
|
|
170
|
-
let cellId = vec2<u32>(in.uv);
|
|
171
|
-
let lightIdFrom = 4u * (cellId.x + cellId.y * cellsGridSizeU.x);
|
|
172
|
-
let p = array<LightProperties, 4>(
|
|
173
|
-
get_light_properties(lightIdFrom + 0u),
|
|
174
|
-
get_light_properties(lightIdFrom + 1u),
|
|
175
|
-
get_light_properties(lightIdFrom + 2u),
|
|
176
|
-
get_light_properties(lightIdFrom + 3u),
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
let localUv = fract(in.uv);
|
|
180
|
-
let fromCenter = 2.0 * localUv - 1.0;
|
|
181
|
-
let uvDist = distance(vec2<f32>(0.0, 0.0), fromCenter);
|
|
182
|
-
let sizes = vec4<f32>(p[0].radius, p[1].radius, p[2].radius, p[3].radius);
|
|
183
|
-
let d = vec4<f32>(uvDist / sizes * f32(${MAX_LIGHT_SIZE}));
|
|
184
|
-
|
|
185
|
-
let intensities = vec4<f32>(p[0].intensity, p[1].intensity, p[2].intensity, p[3].intensity)
|
|
186
|
-
* (1.0 + step(uvDist, 0.01));
|
|
187
|
-
let attenuationL = vec4<f32>(p[0].attenuationLinear, p[1].attenuationLinear, p[2].attenuationLinear, p[3].attenuationLinear);
|
|
188
|
-
let attenuationE = vec4<f32>(p[0].attenuationExp, p[1].attenuationExp, p[2].attenuationExp, p[3].attenuationExp);
|
|
189
|
-
|
|
190
|
-
var lightIntensities = intensities / (1.0 + d * (attenuationL + d * attenuationE));
|
|
191
|
-
lightIntensities *= cos(d * ${Math.PI / 2});
|
|
192
|
-
lightIntensities *= step(d, vec4<f32>(1.0));
|
|
193
|
-
|
|
194
|
-
var out: FragmentOut;
|
|
195
|
-
out.color = lightIntensities;
|
|
196
|
-
return out;
|
|
197
|
-
}
|
|
198
|
-
`,
|
|
199
|
-
})
|
|
57
|
+
const lightsBuffer = new LightsBuffer(device, MAX_LIGHT_COUNT)
|
|
200
58
|
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
fragment: {
|
|
206
|
-
module: initializerShaderModule,
|
|
207
|
-
entryPoint: 'main_fragment',
|
|
208
|
-
targets: [{ format: textureFormat }],
|
|
59
|
+
const viewport = new Viewport({
|
|
60
|
+
viewportSize: {
|
|
61
|
+
width: cobalt.viewport.width,
|
|
62
|
+
height: cobalt.viewport.height,
|
|
209
63
|
},
|
|
210
|
-
|
|
64
|
+
center: cobalt.viewport.position,
|
|
65
|
+
zoom: cobalt.viewport.zoom,
|
|
211
66
|
})
|
|
212
67
|
|
|
213
|
-
const
|
|
214
|
-
label: 'LightsTextureInitializer bindgroup',
|
|
215
|
-
layout: initializerPipeline.getBindGroupLayout(0),
|
|
216
|
-
entries: [{ binding: 0, resource: { buffer: lightsBufferData.lightsBufferGpu } }],
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
const initializerBundleEncoder = device.createRenderBundleEncoder({
|
|
220
|
-
label: 'LightsTextureInitializer renderbundle encoder',
|
|
221
|
-
colorFormats: [textureFormat],
|
|
222
|
-
})
|
|
223
|
-
initializerBundleEncoder.setPipeline(initializerPipeline)
|
|
224
|
-
initializerBundleEncoder.setBindGroup(0, initializerBindgroup)
|
|
225
|
-
initializerBundleEncoder.draw(4)
|
|
226
|
-
const initializerRenderBundle = initializerBundleEncoder.finish({
|
|
227
|
-
label: 'LightsTextureInitializer renderbundle',
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
// --- mask sub-pass (shadow/occlusion geometry) ---
|
|
231
|
-
const maskData = createLightsTextureMask(
|
|
68
|
+
const lightsRenderer = new LightsRenderer({
|
|
232
69
|
device,
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
// --- final composite render pipeline ---
|
|
239
|
-
const rendererTargetFormat = node.refs.out.data.texture.format
|
|
240
|
-
|
|
241
|
-
const rendererUniformsBufferGpu = device.createBuffer({
|
|
242
|
-
label: 'LightsRenderer uniforms buffer',
|
|
243
|
-
size: 80,
|
|
244
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
const rendererShaderModule = device.createShaderModule({
|
|
248
|
-
label: 'LightsRenderer shader module',
|
|
249
|
-
code: `
|
|
250
|
-
struct Uniforms { // align(16) size(80)
|
|
251
|
-
invertViewMatrix: mat4x4<f32>, // offset(0) align(16) size(64)
|
|
252
|
-
ambientLight: vec3<f32>, // offset(64) align(16) size(12)
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
${LIGHTS_STRUCT_DEFINITION}
|
|
256
|
-
|
|
257
|
-
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
258
|
-
@group(0) @binding(1) var<storage,read> lightsBuffer: LightsBuffer;
|
|
259
|
-
@group(0) @binding(2) var lightsTexture: texture_2d<f32>;
|
|
260
|
-
@group(0) @binding(3) var lightsTextureSampler: sampler;
|
|
261
|
-
|
|
262
|
-
@group(1) @binding(0) var albedoTexture: texture_2d<f32>;
|
|
263
|
-
@group(1) @binding(1) var albedoSampler: sampler;
|
|
264
|
-
|
|
265
|
-
struct VertexIn {
|
|
266
|
-
@builtin(vertex_index) vertexIndex: u32,
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
struct VertexOut {
|
|
270
|
-
@builtin(position) position: vec4<f32>,
|
|
271
|
-
@location(0) worldPosition: vec2<f32>,
|
|
272
|
-
@location(1) uv: vec2<f32>,
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
@vertex
|
|
276
|
-
fn main_vertex(in: VertexIn) -> VertexOut {
|
|
277
|
-
const corners = array<vec2<f32>, 4>(
|
|
278
|
-
vec2<f32>(-1, -1),
|
|
279
|
-
vec2<f32>(1, -1),
|
|
280
|
-
vec2<f32>(-1, 1),
|
|
281
|
-
vec2<f32>(1, 1),
|
|
282
|
-
);
|
|
283
|
-
let screenPosition = corners[in.vertexIndex];
|
|
284
|
-
|
|
285
|
-
var out: VertexOut;
|
|
286
|
-
out.position = vec4<f32>(screenPosition, 0.0, 1.0);
|
|
287
|
-
out.worldPosition = (uniforms.invertViewMatrix * out.position).xy;
|
|
288
|
-
out.uv = 0.5 + 0.5 * screenPosition * vec2<f32>(1.0, -1.0);
|
|
289
|
-
return out;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
struct FragmentOut {
|
|
293
|
-
@location(0) color: vec4<f32>,
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
const cellsGridSizeU = vec2<u32>(${gridSize.x}, ${gridSize.y});
|
|
297
|
-
const cellsGridSizeF = vec2<f32>(${gridSize.x}, ${gridSize.y});
|
|
298
|
-
|
|
299
|
-
fn sampleLightBaseIntensity(lightId: u32, localUv: vec2<f32>) -> f32 {
|
|
300
|
-
let cellIndex = lightId / 4u;
|
|
301
|
-
let indexInCell = lightId % 4u;
|
|
302
|
-
|
|
303
|
-
let cellIdU = vec2<u32>(
|
|
304
|
-
cellIndex % cellsGridSizeU.x,
|
|
305
|
-
cellIndex / cellsGridSizeU.x,
|
|
306
|
-
);
|
|
307
|
-
let cellIdF = vec2<f32>(cellIdU);
|
|
308
|
-
let uv = (cellIdF + localUv) / cellsGridSizeF;
|
|
309
|
-
let uvYInverted = vec2<f32>(uv.x, 1.0 - uv.y);
|
|
310
|
-
let sample = textureSampleLevel(lightsTexture, lightsTextureSampler, uvYInverted, 0.0);
|
|
311
|
-
let channel = vec4<f32>(
|
|
312
|
-
vec4<u32>(indexInCell) == vec4<u32>(0u, 1u, 2u, 3u),
|
|
313
|
-
);
|
|
314
|
-
return dot(sample, channel);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
fn compute_lights(worldPosition: vec2<f32>) -> vec3<f32> {
|
|
318
|
-
var color = vec3<f32>(uniforms.ambientLight);
|
|
319
|
-
|
|
320
|
-
const maxUvDistance = f32(${1 - 2 / resolutionPerLight});
|
|
321
|
-
|
|
322
|
-
let lightsCount = lightsBuffer.count;
|
|
323
|
-
for (var iLight = 0u; iLight < lightsCount; iLight++) {
|
|
324
|
-
let light = lightsBuffer.lights[iLight];
|
|
325
|
-
let lightSize = f32(${resolutionPerLight});
|
|
326
|
-
let relativePosition = (worldPosition - light.position) / lightSize;
|
|
327
|
-
if (max(abs(relativePosition.x), abs(relativePosition.y)) < maxUvDistance) {
|
|
328
|
-
let localUv = 0.5 + 0.5 * relativePosition;
|
|
329
|
-
let lightIntensity = light.intensity * sampleLightBaseIntensity(iLight, localUv);
|
|
330
|
-
color += lightIntensity * light.color;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return color;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
@fragment
|
|
338
|
-
fn main_fragment(in: VertexOut) -> FragmentOut {
|
|
339
|
-
let light = clamp(compute_lights(in.worldPosition), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
340
|
-
let albedo = textureSample(albedoTexture, albedoSampler, in.uv);
|
|
341
|
-
let color = albedo.rgb * light;
|
|
342
|
-
|
|
343
|
-
var out: FragmentOut;
|
|
344
|
-
out.color = vec4<f32>(color, 1.0);
|
|
345
|
-
return out;
|
|
346
|
-
}
|
|
347
|
-
`,
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
const rendererPipeline = device.createRenderPipeline({
|
|
351
|
-
label: 'LightsRenderer renderpipeline',
|
|
352
|
-
layout: 'auto',
|
|
353
|
-
vertex: {
|
|
354
|
-
module: rendererShaderModule,
|
|
355
|
-
entryPoint: 'main_vertex',
|
|
70
|
+
albedo: {
|
|
71
|
+
view: node.refs.in.data.view,
|
|
72
|
+
sampler: node.refs.in.data.sampler,
|
|
356
73
|
},
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
74
|
+
targetTexture: node.refs.out.data.texture,
|
|
75
|
+
lightsBuffer,
|
|
76
|
+
lightsTextureProperties: {
|
|
77
|
+
resolutionPerLight: MAX_LIGHT_SIZE,
|
|
78
|
+
maxLightSize: MAX_LIGHT_SIZE,
|
|
79
|
+
antialiased: false,
|
|
80
|
+
filtering: 'nearest',
|
|
81
|
+
textureFormat: getPreferredFormat(cobalt),
|
|
365
82
|
},
|
|
366
83
|
})
|
|
367
84
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
entries: [
|
|
372
|
-
{
|
|
373
|
-
binding: 0,
|
|
374
|
-
resource: { buffer: rendererUniformsBufferGpu },
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
binding: 1,
|
|
378
|
-
resource: { buffer: lightsBufferData.lightsBufferGpu },
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
binding: 2,
|
|
382
|
-
resource: lightsTexture.createView({ label: 'LightsRenderer lightsTexture view' }),
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
binding: 3,
|
|
386
|
-
resource: device.createSampler({
|
|
387
|
-
label: 'LightsRenderer sampler',
|
|
388
|
-
addressModeU: 'clamp-to-edge',
|
|
389
|
-
addressModeV: 'clamp-to-edge',
|
|
390
|
-
magFilter: filtering,
|
|
391
|
-
minFilter: filtering,
|
|
392
|
-
}),
|
|
393
|
-
},
|
|
394
|
-
],
|
|
395
|
-
})
|
|
396
|
-
|
|
397
|
-
const rendererBindgroup1 = buildRendererBindgroup1(device, rendererPipeline, {
|
|
398
|
-
view: node.refs.in.data.view,
|
|
399
|
-
sampler: node.refs.in.data.sampler,
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
// viewport
|
|
403
|
-
const viewportTopLeft = [...cobalt.viewport.position]
|
|
404
|
-
const invViewProjectionMatrix = computeInvertViewProjectionMatrix(
|
|
405
|
-
cobalt.viewport.width,
|
|
406
|
-
cobalt.viewport.height,
|
|
407
|
-
cobalt.viewport.zoom,
|
|
408
|
-
viewportTopLeft,
|
|
409
|
-
)
|
|
410
|
-
|
|
411
|
-
const data = {
|
|
412
|
-
// lights storage buffer
|
|
413
|
-
...lightsBufferData,
|
|
414
|
-
|
|
415
|
-
// lights atlas texture
|
|
416
|
-
lightsTexture,
|
|
417
|
-
lightsTextureRenderpassDescriptor,
|
|
418
|
-
gridSize,
|
|
419
|
-
|
|
420
|
-
// initializer sub-pass
|
|
421
|
-
initializerPipeline,
|
|
422
|
-
initializerBindgroup,
|
|
423
|
-
initializerRenderBundle,
|
|
424
|
-
|
|
425
|
-
// mask sub-pass
|
|
426
|
-
...maskData,
|
|
85
|
+
return {
|
|
86
|
+
lightsBuffer,
|
|
87
|
+
lightsBufferNeedsUpdate: true,
|
|
427
88
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
rendererPipeline,
|
|
431
|
-
rendererUniformsBufferGpu,
|
|
432
|
-
rendererBindgroup0,
|
|
433
|
-
rendererBindgroup1,
|
|
434
|
-
rendererRenderBundle: null, // set below after data is fully constructed
|
|
89
|
+
lightsTextureNeedsUpdate: true,
|
|
90
|
+
lightsRenderer,
|
|
435
91
|
|
|
436
|
-
|
|
437
|
-
invViewProjectionMatrix,
|
|
438
|
-
viewportWidth: cobalt.viewport.width,
|
|
439
|
-
viewportHeight: cobalt.viewport.height,
|
|
440
|
-
viewportZoom: cobalt.viewport.zoom,
|
|
441
|
-
viewportTopLeft,
|
|
92
|
+
viewport,
|
|
442
93
|
|
|
443
|
-
// logical state
|
|
444
94
|
lights: [],
|
|
445
|
-
ambientLight: [0.2, 0.2, 0.2],
|
|
446
|
-
lightsBufferNeedsUpdate: true,
|
|
447
|
-
lightsTextureNeedsUpdate: true,
|
|
448
95
|
}
|
|
449
|
-
|
|
450
|
-
data.rendererRenderBundle = buildRendererRenderBundle(device, data)
|
|
451
|
-
|
|
452
|
-
return data
|
|
453
96
|
}
|
|
454
97
|
|
|
455
98
|
function draw(cobalt, node, commandEncoder) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
d.lightsBufferNeedsUpdate = false
|
|
462
|
-
d.lightsTextureNeedsUpdate = true
|
|
99
|
+
if (node.data.lightsBufferNeedsUpdate) {
|
|
100
|
+
const lightsBuffer = node.data.lightsBuffer
|
|
101
|
+
lightsBuffer.setLights(node.data.lights)
|
|
102
|
+
node.data.lightsBufferNeedsUpdate = false
|
|
103
|
+
node.data.lightsTextureNeedsUpdate = true
|
|
463
104
|
}
|
|
464
105
|
|
|
465
|
-
|
|
466
|
-
computeLightsTexture(device, d, commandEncoder)
|
|
467
|
-
d.lightsTextureNeedsUpdate = false
|
|
468
|
-
}
|
|
106
|
+
const lightsRenderer = node.data.lightsRenderer
|
|
469
107
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
d.viewportHeight,
|
|
475
|
-
d.viewportZoom,
|
|
476
|
-
d.viewportTopLeft,
|
|
477
|
-
)
|
|
478
|
-
|
|
479
|
-
// write uniforms: invertViewMatrix (64 bytes) + ambientLight (12 bytes)
|
|
480
|
-
const uniformsCpu = new ArrayBuffer(80)
|
|
481
|
-
new Float32Array(uniformsCpu, 0, 16).set(d.invViewProjectionMatrix)
|
|
482
|
-
new Float32Array(uniformsCpu, 64, 3).set(d.ambientLight)
|
|
483
|
-
device.queue.writeBuffer(d.rendererUniformsBufferGpu, 0, uniformsCpu)
|
|
108
|
+
if (node.data.lightsTextureNeedsUpdate) {
|
|
109
|
+
lightsRenderer.computeLightsTexture(commandEncoder)
|
|
110
|
+
node.data.lightsTextureNeedsUpdate = false
|
|
111
|
+
}
|
|
484
112
|
|
|
485
113
|
const renderpass = commandEncoder.beginRenderPass({
|
|
486
114
|
label: 'light',
|
|
@@ -493,90 +121,24 @@ function draw(cobalt, node, commandEncoder) {
|
|
|
493
121
|
},
|
|
494
122
|
],
|
|
495
123
|
})
|
|
496
|
-
|
|
124
|
+
|
|
125
|
+
node.data.viewport.setZoom(cobalt.viewport.zoom)
|
|
126
|
+
const invertVpMatrix = node.data.viewport.invertViewProjectionMatrix
|
|
127
|
+
lightsRenderer.render(renderpass, invertVpMatrix)
|
|
128
|
+
|
|
497
129
|
renderpass.end()
|
|
498
130
|
}
|
|
499
131
|
|
|
500
132
|
function destroy(node) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
d.lightsTexture.destroy()
|
|
504
|
-
d.rendererUniformsBufferGpu.destroy()
|
|
505
|
-
destroyMask(d)
|
|
133
|
+
node.data.lightsBuffer.destroy()
|
|
134
|
+
node.data.lightsRenderer.destroy()
|
|
506
135
|
}
|
|
507
136
|
|
|
508
137
|
function resize(cobalt, node) {
|
|
509
|
-
|
|
510
|
-
const { device } = cobalt
|
|
511
|
-
|
|
512
|
-
d.viewportWidth = cobalt.viewport.width
|
|
513
|
-
d.viewportHeight = cobalt.viewport.height
|
|
514
|
-
d.invViewProjectionMatrix = computeInvertViewProjectionMatrix(
|
|
515
|
-
d.viewportWidth,
|
|
516
|
-
d.viewportHeight,
|
|
517
|
-
d.viewportZoom,
|
|
518
|
-
d.viewportTopLeft,
|
|
519
|
-
)
|
|
520
|
-
|
|
521
|
-
// rebuild bindgroup1 with the new albedo texture view/sampler from the resized input node
|
|
522
|
-
d.rendererBindgroup1 = buildRendererBindgroup1(device, d.rendererPipeline, {
|
|
138
|
+
node.data.lightsRenderer.setAlbedo({
|
|
523
139
|
view: node.refs.in.data.view,
|
|
524
140
|
sampler: node.refs.in.data.sampler,
|
|
525
141
|
})
|
|
526
|
-
d.rendererRenderBundle = buildRendererRenderBundle(device, d)
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// ---- helpers ----
|
|
530
|
-
|
|
531
|
-
function computeLightsTexture(device, data, commandEncoder) {
|
|
532
|
-
setMaskLightsCount(device, data, data.lightsCount)
|
|
533
|
-
const renderpassEncoder = commandEncoder.beginRenderPass(data.lightsTextureRenderpassDescriptor)
|
|
534
|
-
const w = data.lightsTexture.width
|
|
535
|
-
const h = data.lightsTexture.height
|
|
536
|
-
renderpassEncoder.setViewport(0, 0, w, h, 0, 1)
|
|
537
|
-
renderpassEncoder.setScissorRect(0, 0, w, h)
|
|
538
|
-
renderpassEncoder.executeBundles([data.initializerRenderBundle, data.maskRenderBundle])
|
|
539
|
-
renderpassEncoder.end()
|
|
540
|
-
}
|
|
541
142
|
|
|
542
|
-
|
|
543
|
-
return device.createBindGroup({
|
|
544
|
-
label: 'LightsRenderer bindgroup 1',
|
|
545
|
-
layout: pipeline.getBindGroupLayout(1),
|
|
546
|
-
entries: [
|
|
547
|
-
{
|
|
548
|
-
binding: 0,
|
|
549
|
-
resource: albedo.view,
|
|
550
|
-
},
|
|
551
|
-
{
|
|
552
|
-
binding: 1,
|
|
553
|
-
resource: albedo.sampler,
|
|
554
|
-
},
|
|
555
|
-
],
|
|
556
|
-
})
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function computeInvertViewProjectionMatrix(width, height, zoom, topLeft) {
|
|
560
|
-
const m = wgpuMatrix.mat4.identity()
|
|
561
|
-
wgpuMatrix.mat4.multiply(wgpuMatrix.mat4.scaling([1, -1, 0]), m, m)
|
|
562
|
-
wgpuMatrix.mat4.multiply(wgpuMatrix.mat4.translation([1, 1, 0]), m, m)
|
|
563
|
-
wgpuMatrix.mat4.multiply(
|
|
564
|
-
wgpuMatrix.mat4.scaling([(0.5 * width) / zoom, (0.5 * height) / zoom, 0]),
|
|
565
|
-
m,
|
|
566
|
-
m,
|
|
567
|
-
)
|
|
568
|
-
wgpuMatrix.mat4.multiply(wgpuMatrix.mat4.translation([topLeft[0], topLeft[1], 0]), m, m)
|
|
569
|
-
return m
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
function buildRendererRenderBundle(device, data) {
|
|
573
|
-
const renderBundleEncoder = device.createRenderBundleEncoder({
|
|
574
|
-
label: 'LightsRenderer renderbundle encoder',
|
|
575
|
-
colorFormats: [data.rendererTargetFormat],
|
|
576
|
-
})
|
|
577
|
-
renderBundleEncoder.setPipeline(data.rendererPipeline)
|
|
578
|
-
renderBundleEncoder.setBindGroup(0, data.rendererBindgroup0)
|
|
579
|
-
renderBundleEncoder.setBindGroup(1, data.rendererBindgroup1)
|
|
580
|
-
renderBundleEncoder.draw(4)
|
|
581
|
-
return renderBundleEncoder.finish({ label: 'LightsRenderer renderbundle' })
|
|
143
|
+
node.data.viewport.setViewportSize(cobalt.viewport.width, cobalt.viewport.height)
|
|
582
144
|
}
|