@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
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
class LightsBuffer {
|
|
2
|
+
static structs = {
|
|
3
|
+
definition: `
|
|
2
4
|
struct Light { // align(16) size(48)
|
|
3
5
|
color: vec3<f32>, // offset(0) align(16) size(12)
|
|
4
6
|
radius: f32, // offset(12) align(4) size(4)
|
|
@@ -13,59 +15,61 @@ struct LightsBuffer { // align(16)
|
|
|
13
15
|
// padding
|
|
14
16
|
lights: array<Light>, // offset(16) align(16)
|
|
15
17
|
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
18
|
+
`,
|
|
19
|
+
light: {
|
|
20
|
+
radius: { offset: 12 },
|
|
21
|
+
position: { offset: 16 },
|
|
22
|
+
},
|
|
23
|
+
lightsBuffer: {
|
|
24
|
+
lights: { offset: 16, stride: 48 },
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
device;
|
|
28
|
+
maxLightsCount;
|
|
29
|
+
currentLightsCount = 0;
|
|
30
|
+
buffer;
|
|
31
|
+
get gpuBuffer() {
|
|
32
|
+
return this.buffer.bufferGpu;
|
|
33
|
+
}
|
|
34
|
+
constructor(device, maxLightsCount) {
|
|
35
|
+
this.device = device;
|
|
36
|
+
this.maxLightsCount = maxLightsCount;
|
|
37
|
+
const bufferCpu = new ArrayBuffer(LightsBuffer.computeBufferBytesLength(maxLightsCount));
|
|
38
|
+
const bufferGpu = device.createBuffer({
|
|
39
|
+
label: "LightsBuffer buffer",
|
|
40
|
+
size: bufferCpu.byteLength,
|
|
41
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.VERTEX,
|
|
42
|
+
});
|
|
43
|
+
this.buffer = { bufferCpu, bufferGpu };
|
|
44
|
+
this.setLights([]);
|
|
45
|
+
}
|
|
46
|
+
setLights(lights) {
|
|
47
|
+
if (lights.length > this.maxLightsCount) {
|
|
48
|
+
throw new Error(`Too many lights "${lights.length}", max is "${this.maxLightsCount}".`);
|
|
49
|
+
}
|
|
50
|
+
const newBufferLength = LightsBuffer.computeBufferBytesLength(lights.length);
|
|
51
|
+
new Uint32Array(this.buffer.bufferCpu, 0, 1).set([lights.length]);
|
|
52
|
+
lights.forEach((light, index) => {
|
|
53
|
+
new Float32Array(this.buffer.bufferCpu, LightsBuffer.structs.lightsBuffer.lights.offset + LightsBuffer.structs.lightsBuffer.lights.stride * index, 9).set([
|
|
54
|
+
...light.color,
|
|
55
|
+
light.radius,
|
|
56
|
+
...light.position,
|
|
57
|
+
light.intensity,
|
|
58
|
+
light.attenuationLinear,
|
|
59
|
+
light.attenuationExp
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
this.device.queue.writeBuffer(this.buffer.bufferGpu, 0, this.buffer.bufferCpu, 0, newBufferLength);
|
|
63
|
+
this.currentLightsCount = lights.length;
|
|
64
|
+
}
|
|
65
|
+
get lightsCount() {
|
|
66
|
+
return this.currentLightsCount;
|
|
67
|
+
}
|
|
68
|
+
destroy() {
|
|
69
|
+
this.buffer.bufferGpu.destroy();
|
|
70
|
+
}
|
|
71
|
+
static computeBufferBytesLength(lightsCount) {
|
|
72
|
+
return LightsBuffer.structs.lightsBuffer.lights.offset + LightsBuffer.structs.lightsBuffer.lights.stride * lightsCount;
|
|
50
73
|
}
|
|
51
|
-
const { lightsBufferCpu, lightsBufferGpu } = data
|
|
52
|
-
const { offset, stride } = LIGHTS_BUFFER_STRUCTS.lightsBuffer.lights
|
|
53
|
-
new Uint32Array(lightsBufferCpu, 0, 1).set([lights.length])
|
|
54
|
-
lights.forEach((light, index) => {
|
|
55
|
-
new Float32Array(lightsBufferCpu, offset + stride * index, 9).set([
|
|
56
|
-
...light.color,
|
|
57
|
-
light.radius,
|
|
58
|
-
...light.position,
|
|
59
|
-
light.intensity,
|
|
60
|
-
light.attenuationLinear,
|
|
61
|
-
light.attenuationExp,
|
|
62
|
-
])
|
|
63
|
-
})
|
|
64
|
-
const newBufferLength = computeLightsBufferByteLength(lights.length)
|
|
65
|
-
device.queue.writeBuffer(lightsBufferGpu, 0, lightsBufferCpu, 0, newBufferLength)
|
|
66
|
-
data.lightsCount = lights.length
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function destroyLightsBuffer(data) {
|
|
70
|
-
data.lightsBufferGpu.destroy()
|
|
71
74
|
}
|
|
75
|
+
export { LightsBuffer };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { LightsBuffer } from "./lights-buffer.js";
|
|
2
|
+
import { LightsTexture } from "./texture/lights-texture.js";
|
|
3
|
+
|
|
4
|
+
class LightsRenderer {
|
|
5
|
+
device;
|
|
6
|
+
ambientLight = [0.2, 0.2, 0.2];
|
|
7
|
+
targetTexture;
|
|
8
|
+
renderPipeline;
|
|
9
|
+
uniformsBufferGpu;
|
|
10
|
+
bindgroup0;
|
|
11
|
+
bindgroup1;
|
|
12
|
+
renderBundle;
|
|
13
|
+
lightsBuffer;
|
|
14
|
+
lightsTexture;
|
|
15
|
+
constructor(params) {
|
|
16
|
+
this.device = params.device;
|
|
17
|
+
this.targetTexture = params.targetTexture;
|
|
18
|
+
this.lightsBuffer = params.lightsBuffer;
|
|
19
|
+
this.lightsTexture = new LightsTexture(params.device, params.lightsBuffer, params.lightsTextureProperties);
|
|
20
|
+
this.uniformsBufferGpu = params.device.createBuffer({
|
|
21
|
+
label: "LightsRenderer uniforms buffer",
|
|
22
|
+
size: 80,
|
|
23
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
24
|
+
});
|
|
25
|
+
const shaderModule = params.device.createShaderModule({
|
|
26
|
+
label: "LightsRenderer shader module",
|
|
27
|
+
code: `
|
|
28
|
+
struct Uniforms { // align(16) size(80)
|
|
29
|
+
invertViewMatrix: mat4x4<f32>, // offset(0) align(16) size(64)
|
|
30
|
+
ambientLight: vec3<f32>, // offset(64) align(16) size(12)
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
${LightsBuffer.structs.definition}
|
|
34
|
+
|
|
35
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
36
|
+
@group(0) @binding(1) var<storage,read> lightsBuffer: LightsBuffer;
|
|
37
|
+
@group(0) @binding(2) var lightsTexture: texture_2d<f32>;
|
|
38
|
+
@group(0) @binding(3) var lightsTextureSampler: sampler;
|
|
39
|
+
|
|
40
|
+
@group(1) @binding(0) var albedoTexture: texture_2d<f32>;
|
|
41
|
+
@group(1) @binding(1) var albedoSampler: sampler;
|
|
42
|
+
|
|
43
|
+
struct VertexIn {
|
|
44
|
+
@builtin(vertex_index) vertexIndex: u32,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
struct VertexOut {
|
|
48
|
+
@builtin(position) position: vec4<f32>,
|
|
49
|
+
@location(0) worldPosition: vec2<f32>,
|
|
50
|
+
@location(1) uv: vec2<f32>,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
@vertex
|
|
54
|
+
fn main_vertex(in: VertexIn) -> VertexOut {
|
|
55
|
+
const corners = array<vec2<f32>, 4>(
|
|
56
|
+
vec2<f32>(-1, -1),
|
|
57
|
+
vec2<f32>(1, -1),
|
|
58
|
+
vec2<f32>(-1, 1),
|
|
59
|
+
vec2<f32>(1, 1),
|
|
60
|
+
);
|
|
61
|
+
let screenPosition = corners[in.vertexIndex];
|
|
62
|
+
|
|
63
|
+
var out: VertexOut;
|
|
64
|
+
out.position = vec4<f32>(screenPosition, 0.0, 1.0);
|
|
65
|
+
out.worldPosition = (uniforms.invertViewMatrix * out.position).xy;
|
|
66
|
+
out.uv = 0.5 + 0.5 * screenPosition * vec2<f32>(1.0, -1.0);
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
struct FragmentOut {
|
|
71
|
+
@location(0) color: vec4<f32>,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const cellsGridSizeU = vec2<u32>(${this.lightsTexture.gridSize.x}, ${this.lightsTexture.gridSize.y});
|
|
75
|
+
const cellsGridSizeF = vec2<f32>(${this.lightsTexture.gridSize.x}, ${this.lightsTexture.gridSize.y});
|
|
76
|
+
|
|
77
|
+
fn sampleLightBaseIntensity(lightId: u32, localUv: vec2<f32>) -> f32 {
|
|
78
|
+
let cellIndex = lightId / 4u;
|
|
79
|
+
let indexInCell = lightId % 4u;
|
|
80
|
+
|
|
81
|
+
let cellIdU = vec2<u32>(
|
|
82
|
+
cellIndex % cellsGridSizeU.x,
|
|
83
|
+
cellIndex / cellsGridSizeU.x,
|
|
84
|
+
);
|
|
85
|
+
let cellIdF = vec2<f32>(cellIdU);
|
|
86
|
+
let uv = (cellIdF + localUv) / cellsGridSizeF;
|
|
87
|
+
let uvYInverted = vec2<f32>(uv.x, 1.0 - uv.y);
|
|
88
|
+
let sample = textureSampleLevel(lightsTexture, lightsTextureSampler, uvYInverted, 0.0);
|
|
89
|
+
let channel = vec4<f32>(
|
|
90
|
+
vec4<u32>(indexInCell) == vec4<u32>(0u, 1u, 2u, 3u),
|
|
91
|
+
);
|
|
92
|
+
return dot(sample, channel);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fn compute_lights(worldPosition: vec2<f32>) -> vec3<f32> {
|
|
96
|
+
var color = vec3<f32>(uniforms.ambientLight);
|
|
97
|
+
|
|
98
|
+
const maxUvDistance = f32(${1 - 2 / params.lightsTextureProperties.resolutionPerLight});
|
|
99
|
+
|
|
100
|
+
let lightsCount = lightsBuffer.count;
|
|
101
|
+
for (var iLight = 0u; iLight < lightsCount; iLight++) {
|
|
102
|
+
let light = lightsBuffer.lights[iLight];
|
|
103
|
+
let lightSize = f32(${params.lightsTextureProperties.resolutionPerLight});
|
|
104
|
+
let relativePosition = (worldPosition - light.position) / lightSize;
|
|
105
|
+
if (max(abs(relativePosition.x), abs(relativePosition.y)) < maxUvDistance) {
|
|
106
|
+
let localUv = 0.5 + 0.5 * relativePosition;
|
|
107
|
+
let lightIntensity = light.intensity * sampleLightBaseIntensity(iLight, localUv);
|
|
108
|
+
color += lightIntensity * light.color;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return color;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@fragment
|
|
116
|
+
fn main_fragment(in: VertexOut) -> FragmentOut {
|
|
117
|
+
let light = compute_lights(in.worldPosition);
|
|
118
|
+
let albedo = textureSample(albedoTexture, albedoSampler, in.uv);
|
|
119
|
+
let color = albedo.rgb * light;
|
|
120
|
+
|
|
121
|
+
var out: FragmentOut;
|
|
122
|
+
out.color = vec4<f32>(color, 1.0);
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
`,
|
|
126
|
+
});
|
|
127
|
+
this.renderPipeline = params.device.createRenderPipeline({
|
|
128
|
+
label: "LightsRenderer renderpipeline",
|
|
129
|
+
layout: "auto",
|
|
130
|
+
vertex: {
|
|
131
|
+
module: shaderModule,
|
|
132
|
+
entryPoint: "main_vertex",
|
|
133
|
+
},
|
|
134
|
+
fragment: {
|
|
135
|
+
module: shaderModule,
|
|
136
|
+
entryPoint: "main_fragment",
|
|
137
|
+
targets: [{
|
|
138
|
+
format: this.targetTexture.format,
|
|
139
|
+
}],
|
|
140
|
+
},
|
|
141
|
+
primitive: {
|
|
142
|
+
cullMode: "none",
|
|
143
|
+
topology: "triangle-strip",
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
const bindgroupLayout = this.renderPipeline.getBindGroupLayout(0);
|
|
147
|
+
this.bindgroup0 = params.device.createBindGroup({
|
|
148
|
+
label: "LightsRenderer bindgroup 0",
|
|
149
|
+
layout: bindgroupLayout,
|
|
150
|
+
entries: [
|
|
151
|
+
{
|
|
152
|
+
binding: 0,
|
|
153
|
+
resource: { buffer: this.uniformsBufferGpu },
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
binding: 1,
|
|
157
|
+
resource: { buffer: this.lightsBuffer.gpuBuffer },
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
binding: 2,
|
|
161
|
+
resource: this.lightsTexture.texture.createView({ label: "LightsRenderer lightsTexture view" }),
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
binding: 3,
|
|
165
|
+
resource: params.device.createSampler({
|
|
166
|
+
label: "LightsRenderer sampler",
|
|
167
|
+
addressModeU: "clamp-to-edge",
|
|
168
|
+
addressModeV: "clamp-to-edge",
|
|
169
|
+
magFilter: params.lightsTextureProperties.filtering,
|
|
170
|
+
minFilter: params.lightsTextureProperties.filtering,
|
|
171
|
+
}),
|
|
172
|
+
},
|
|
173
|
+
]
|
|
174
|
+
});
|
|
175
|
+
this.bindgroup1 = this.buildBindgroup1(params.albedo);
|
|
176
|
+
this.renderBundle = this.buildRenderBundle();
|
|
177
|
+
}
|
|
178
|
+
computeLightsTexture(commandEncoder) {
|
|
179
|
+
this.lightsTexture.update(commandEncoder);
|
|
180
|
+
}
|
|
181
|
+
render(renderpassEncoder, invertVpMatrix) {
|
|
182
|
+
const uniformsBufferCpu = new ArrayBuffer(80);
|
|
183
|
+
new Float32Array(uniformsBufferCpu, 0, 16).set(invertVpMatrix);
|
|
184
|
+
new Float32Array(uniformsBufferCpu, 64, 3).set(this.ambientLight);
|
|
185
|
+
this.device.queue.writeBuffer(this.uniformsBufferGpu, 0, uniformsBufferCpu);
|
|
186
|
+
renderpassEncoder.executeBundles([this.renderBundle]);
|
|
187
|
+
}
|
|
188
|
+
setAlbedo(albedo) {
|
|
189
|
+
this.bindgroup1 = this.buildBindgroup1(albedo);
|
|
190
|
+
this.renderBundle = this.buildRenderBundle();
|
|
191
|
+
}
|
|
192
|
+
setAmbientLight(color) {
|
|
193
|
+
this.ambientLight = [...color];
|
|
194
|
+
}
|
|
195
|
+
setObstacles(segments) {
|
|
196
|
+
this.lightsTexture.setObstacles(segments);
|
|
197
|
+
}
|
|
198
|
+
destroy() {
|
|
199
|
+
this.uniformsBufferGpu.destroy();
|
|
200
|
+
this.lightsTexture.destroy();
|
|
201
|
+
}
|
|
202
|
+
buildBindgroup1(albedo) {
|
|
203
|
+
return this.device.createBindGroup({
|
|
204
|
+
label: "LightsRenderer bindgroup 1",
|
|
205
|
+
layout: this.renderPipeline.getBindGroupLayout(1),
|
|
206
|
+
entries: [
|
|
207
|
+
{
|
|
208
|
+
binding: 0,
|
|
209
|
+
resource: albedo.view,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
binding: 1,
|
|
213
|
+
resource: albedo.sampler,
|
|
214
|
+
},
|
|
215
|
+
]
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
buildRenderBundle() {
|
|
219
|
+
const renderBundleEncoder = this.device.createRenderBundleEncoder({
|
|
220
|
+
label: "LightsRenderer renderbundle encoder",
|
|
221
|
+
colorFormats: [this.targetTexture.format],
|
|
222
|
+
});
|
|
223
|
+
renderBundleEncoder.setPipeline(this.renderPipeline);
|
|
224
|
+
renderBundleEncoder.setBindGroup(0, this.bindgroup0);
|
|
225
|
+
renderBundleEncoder.setBindGroup(1, this.bindgroup1);
|
|
226
|
+
renderBundleEncoder.draw(4);
|
|
227
|
+
return renderBundleEncoder.finish({ label: "LightsRenderer renderbundle" });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
export { LightsRenderer };
|
package/src/light/public-api.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { vec2 } from 'wgpu-matrix'
|
|
2
|
+
import uuid from '../uuid.js'
|
|
2
3
|
|
|
3
4
|
// public API to interact with a lighting/shadows node.
|
|
4
5
|
|
|
@@ -8,10 +9,10 @@ export function setLights(cobalt, node, lights) {
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export function setAmbientLight(cobalt, node, color) {
|
|
11
|
-
node.data.
|
|
12
|
+
node.data.lightsRenderer.setAmbientLight(color)
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export function setOccluders(cobalt, node, segmentsList) {
|
|
15
|
-
|
|
16
|
+
node.data.lightsRenderer.setObstacles(segmentsList)
|
|
16
17
|
node.data.lightsTextureNeedsUpdate = true
|
|
17
18
|
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { LightsBuffer } from "../lights-buffer.js";
|
|
2
|
+
|
|
3
|
+
class LightsTextureInitializer {
|
|
4
|
+
lightsBuffer;
|
|
5
|
+
renderPipeline;
|
|
6
|
+
bindgroup;
|
|
7
|
+
renderBundle;
|
|
8
|
+
constructor(device, lightsBuffer, lightsTexture, maxLightSize) {
|
|
9
|
+
this.lightsBuffer = lightsBuffer;
|
|
10
|
+
const shaderModule = device.createShaderModule({
|
|
11
|
+
label: "LightsTextureInitializer shader module",
|
|
12
|
+
code: `
|
|
13
|
+
${LightsBuffer.structs.definition}
|
|
14
|
+
|
|
15
|
+
@group(0) @binding(0) var<storage,read> lightsBuffer: LightsBuffer;
|
|
16
|
+
|
|
17
|
+
struct VertexIn {
|
|
18
|
+
@builtin(vertex_index) vertexIndex: u32,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
struct VertexOut {
|
|
22
|
+
@builtin(position) position: vec4<f32>,
|
|
23
|
+
@location(0) uv: vec2<f32>,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const cellsGridSizeU = vec2<u32>(${lightsTexture.gridSize.x}, ${lightsTexture.gridSize.y});
|
|
27
|
+
const cellsGridSizeF = vec2<f32>(${lightsTexture.gridSize.x}, ${lightsTexture.gridSize.y});
|
|
28
|
+
|
|
29
|
+
@vertex
|
|
30
|
+
fn main_vertex(in: VertexIn) -> VertexOut {
|
|
31
|
+
const corners = array<vec2<f32>, 4>(
|
|
32
|
+
vec2<f32>(-1, -1),
|
|
33
|
+
vec2<f32>(1, -1),
|
|
34
|
+
vec2<f32>(-1, 1),
|
|
35
|
+
vec2<f32>(1, 1),
|
|
36
|
+
);
|
|
37
|
+
let screenPosition = corners[in.vertexIndex];
|
|
38
|
+
|
|
39
|
+
var out: VertexOut;
|
|
40
|
+
out.position = vec4<f32>(screenPosition, 0.0, 1.0);
|
|
41
|
+
out.uv = (0.5 + 0.5 * screenPosition) * cellsGridSizeF;
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
struct FragmentOut {
|
|
46
|
+
@location(0) color: vec4<f32>,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
struct LightProperties {
|
|
50
|
+
radius: f32,
|
|
51
|
+
intensity: f32,
|
|
52
|
+
attenuationLinear: f32,
|
|
53
|
+
attenuationExp: f32,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
fn get_light_properties(lightId: u32) -> LightProperties {
|
|
57
|
+
var lightProperties: LightProperties;
|
|
58
|
+
if (lightId < lightsBuffer.count) {
|
|
59
|
+
let light = lightsBuffer.lights[lightId];
|
|
60
|
+
lightProperties.radius = light.radius;
|
|
61
|
+
lightProperties.intensity = 1.0;
|
|
62
|
+
lightProperties.attenuationLinear = light.attenuationLinear;
|
|
63
|
+
lightProperties.attenuationExp = light.attenuationExp;
|
|
64
|
+
} else {
|
|
65
|
+
lightProperties.radius = 0.0;
|
|
66
|
+
lightProperties.intensity = 0.0;
|
|
67
|
+
lightProperties.attenuationLinear = 0.0;
|
|
68
|
+
lightProperties.attenuationExp = 0.0;
|
|
69
|
+
}
|
|
70
|
+
return lightProperties;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@fragment
|
|
74
|
+
fn main_fragment(in: VertexOut) -> FragmentOut {
|
|
75
|
+
let cellId = vec2<u32>(in.uv);
|
|
76
|
+
|
|
77
|
+
let lightIdFrom = 4u * (cellId.x + cellId.y * cellsGridSizeU.x);
|
|
78
|
+
let lightProperties = array<LightProperties, 4>(
|
|
79
|
+
get_light_properties(lightIdFrom + 0u),
|
|
80
|
+
get_light_properties(lightIdFrom + 1u),
|
|
81
|
+
get_light_properties(lightIdFrom + 2u),
|
|
82
|
+
get_light_properties(lightIdFrom + 3u),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
let sizes = vec4<f32>(
|
|
86
|
+
lightProperties[0].radius,
|
|
87
|
+
lightProperties[1].radius,
|
|
88
|
+
lightProperties[2].radius,
|
|
89
|
+
lightProperties[3].radius,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
let localUv = fract(in.uv);
|
|
93
|
+
let fromCenter = 2.0 * localUv - 1.0;
|
|
94
|
+
let uvDistanceFromCenter = distance(vec2<f32>(0,0), fromCenter);
|
|
95
|
+
let distancesFromCenter = vec4<f32>(uvDistanceFromCenter / sizes * f32(${maxLightSize}));
|
|
96
|
+
|
|
97
|
+
let intensities = vec4<f32>(
|
|
98
|
+
lightProperties[0].intensity * (1.0 + 1.0 * step(uvDistanceFromCenter, 0.01)),
|
|
99
|
+
lightProperties[1].intensity * (1.0 + 1.0 * step(uvDistanceFromCenter, 0.01)),
|
|
100
|
+
lightProperties[2].intensity * (1.0 + 1.0 * step(uvDistanceFromCenter, 0.01)),
|
|
101
|
+
lightProperties[3].intensity * (1.0 + 1.0 * step(uvDistanceFromCenter, 0.01)),
|
|
102
|
+
);
|
|
103
|
+
let attenuationsLinear = vec4<f32>(
|
|
104
|
+
lightProperties[0].attenuationLinear,
|
|
105
|
+
lightProperties[1].attenuationLinear,
|
|
106
|
+
lightProperties[2].attenuationLinear,
|
|
107
|
+
lightProperties[3].attenuationLinear,
|
|
108
|
+
);
|
|
109
|
+
let attenuationsExp = vec4<f32>(
|
|
110
|
+
lightProperties[0].attenuationExp,
|
|
111
|
+
lightProperties[1].attenuationExp,
|
|
112
|
+
lightProperties[2].attenuationExp,
|
|
113
|
+
lightProperties[3].attenuationExp,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
var lightIntensities = intensities / (1.0 + distancesFromCenter * (attenuationsLinear + distancesFromCenter * attenuationsExp)); // base intensity equation
|
|
117
|
+
lightIntensities *= cos(distancesFromCenter * ${Math.PI / 2}); // soft limit;
|
|
118
|
+
lightIntensities *= step(distancesFromCenter, vec4<f32>(1.0)); // hard limit
|
|
119
|
+
|
|
120
|
+
var out: FragmentOut;
|
|
121
|
+
out.color = lightIntensities;
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
`,
|
|
125
|
+
});
|
|
126
|
+
this.renderPipeline = device.createRenderPipeline({
|
|
127
|
+
label: "LightsTextureInitializer renderpipeline",
|
|
128
|
+
layout: "auto",
|
|
129
|
+
vertex: {
|
|
130
|
+
module: shaderModule,
|
|
131
|
+
entryPoint: "main_vertex",
|
|
132
|
+
},
|
|
133
|
+
fragment: {
|
|
134
|
+
module: shaderModule,
|
|
135
|
+
entryPoint: "main_fragment",
|
|
136
|
+
targets: [{
|
|
137
|
+
format: lightsTexture.format,
|
|
138
|
+
}],
|
|
139
|
+
},
|
|
140
|
+
primitive: {
|
|
141
|
+
cullMode: "none",
|
|
142
|
+
topology: "triangle-strip",
|
|
143
|
+
},
|
|
144
|
+
multisample: {
|
|
145
|
+
count: lightsTexture.sampleCount,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
this.bindgroup = device.createBindGroup({
|
|
149
|
+
label: "LightsTextureInitializer bindgroup 0",
|
|
150
|
+
layout: this.renderPipeline.getBindGroupLayout(0),
|
|
151
|
+
entries: [
|
|
152
|
+
{
|
|
153
|
+
binding: 0,
|
|
154
|
+
resource: { buffer: this.lightsBuffer.gpuBuffer },
|
|
155
|
+
},
|
|
156
|
+
]
|
|
157
|
+
});
|
|
158
|
+
const renderBundleEncoder = device.createRenderBundleEncoder({
|
|
159
|
+
label: "LightsTextureInitializer renderbundle encoder",
|
|
160
|
+
colorFormats: [lightsTexture.format],
|
|
161
|
+
sampleCount: lightsTexture.sampleCount,
|
|
162
|
+
});
|
|
163
|
+
renderBundleEncoder.setPipeline(this.renderPipeline);
|
|
164
|
+
renderBundleEncoder.setBindGroup(0, this.bindgroup);
|
|
165
|
+
renderBundleEncoder.draw(4);
|
|
166
|
+
this.renderBundle = renderBundleEncoder.finish({ label: "LightsTextureInitializer renderbundle" });
|
|
167
|
+
}
|
|
168
|
+
getRenderBundle() {
|
|
169
|
+
return this.renderBundle;
|
|
170
|
+
}
|
|
171
|
+
destroy() {
|
|
172
|
+
// nothing to do
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export { LightsTextureInitializer };
|