@woosh/meep-engine 2.44.7 → 2.45.1
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/core/cache/Cache.js +1 -1
- package/core/collection/HashMap.d.ts +1 -1
- package/core/collection/HashMap.js +2 -1
- package/core/localization/LanguageMetadata.js +48 -0
- package/core/localization/LocaleDataset.js +37 -0
- package/core/localization/Localization.js +253 -0
- package/core/model/node-graph/DataType.js +1 -1
- package/core/model/node-graph/node/Port.js +4 -0
- package/core/process/task/Task.js +22 -5
- package/core/process/task/util/iteratorTask.js +29 -0
- package/editor/view/ecs/components/items/ItemContainerController.stories.js +1 -1
- package/engine/Engine.js +1 -1
- package/engine/asset/AssetManager.js +4 -0
- package/engine/ecs/foliage/ViewState.js +7 -1
- package/engine/ecs/speaker/VoiceSystem.js +1 -1
- package/engine/graphics/ecs/decal/v2/prototypeDecalSystem.js +17 -11
- package/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js +37 -0
- package/engine/graphics/ecs/mesh-v2/render/ShadedGeometryRendererContext.js +19 -0
- package/engine/graphics/ecs/mesh-v2/render/adapters/AbstractRenderAdapter.js +11 -2
- package/engine/graphics/render/forward_plus/LightManager.js +33 -123
- package/engine/graphics/render/forward_plus/SPECIFICATION.md +155 -0
- package/engine/graphics/render/forward_plus/assign_cluster.js +125 -0
- package/engine/graphics/render/forward_plus/model/Decal.js +6 -2
- package/engine/graphics/render/forward_plus/plugin/ForwardPlusRenderingPlugin.d.ts +5 -0
- package/engine/graphics/render/forward_plus/plugin/ForwardPlusRenderingPlugin.js +11 -4
- package/engine/graphics/render/forward_plus/prototype/prototypeLightManager.js +3 -3
- package/engine/graphics/render/forward_plus/query/query_bvh_frustum_from_texture.js +2 -2
- package/engine/graphics/render/forward_plus/sort_decal_data.js +102 -0
- package/engine/graphics/sh3/path_tracer/prototypePathTracer.js +1 -1
- package/engine/input/devices/InputDeviceButton.d.ts +7 -0
- package/engine/input/devices/InputDeviceButton.js +22 -0
- package/engine/input/devices/KeyboardDevice.d.ts +11 -0
- package/engine/input/devices/KeyboardDevice.js +17 -7
- package/package.json +1 -1
- package/core/Localization.js +0 -199
|
@@ -20,6 +20,7 @@ import TaskState from "../../../../core/process/task/TaskState.js";
|
|
|
20
20
|
import {
|
|
21
21
|
bvh_query_user_data_overlaps_frustum
|
|
22
22
|
} from "../../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_frustum.js";
|
|
23
|
+
import { iteratorTask } from "../../../../core/process/task/util/iteratorTask.js";
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -34,6 +35,31 @@ const scratch_point = new SurfacePoint3();
|
|
|
34
35
|
*/
|
|
35
36
|
const scratch_ray_0 = new Float32Array(6);
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @param {ShadedGeometrySystem} system
|
|
41
|
+
* @return {Generator}
|
|
42
|
+
*/
|
|
43
|
+
function* maintenance_generator(system) {
|
|
44
|
+
while (true) { //infinite loop
|
|
45
|
+
|
|
46
|
+
const contexts = system.__view_contexts;
|
|
47
|
+
|
|
48
|
+
for (const [key, value] of contexts) {
|
|
49
|
+
|
|
50
|
+
if (value === undefined) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
yield* value.maintain();
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// a small break between re-runs
|
|
59
|
+
yield;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
37
63
|
export class ShadedGeometrySystem extends System {
|
|
38
64
|
/**
|
|
39
65
|
*
|
|
@@ -100,8 +126,15 @@ export class ShadedGeometrySystem extends System {
|
|
|
100
126
|
}
|
|
101
127
|
}
|
|
102
128
|
});
|
|
129
|
+
|
|
130
|
+
this.__maintenance_task = iteratorTask(
|
|
131
|
+
'Maintenance',
|
|
132
|
+
maintenance_generator(this),
|
|
133
|
+
TaskSignal.Yield
|
|
134
|
+
);
|
|
103
135
|
}
|
|
104
136
|
|
|
137
|
+
|
|
105
138
|
/**
|
|
106
139
|
*
|
|
107
140
|
* @returns {ExplicitBinaryBoundingVolumeHierarchy}
|
|
@@ -222,6 +255,9 @@ export class ShadedGeometrySystem extends System {
|
|
|
222
255
|
this.__optimization_task.state.set(TaskState.INITIAL);
|
|
223
256
|
engine.executor.run(this.__optimization_task);
|
|
224
257
|
|
|
258
|
+
this.__maintenance_task.state.set(TaskState.INITIAL);
|
|
259
|
+
engine.executor.run(this.__maintenance_task);
|
|
260
|
+
|
|
225
261
|
readyCallback();
|
|
226
262
|
}
|
|
227
263
|
|
|
@@ -233,6 +269,7 @@ export class ShadedGeometrySystem extends System {
|
|
|
233
269
|
const view_list = graphics.views.elements;
|
|
234
270
|
|
|
235
271
|
engine.executor.removeTask(this.__optimization_task);
|
|
272
|
+
engine.executor.removeTask(this.__maintenance_task);
|
|
236
273
|
|
|
237
274
|
view_list.forEach(this.__handle_view_removed, this);
|
|
238
275
|
view_list.on.added.remove(this.__handle_view_added, this);
|
|
@@ -30,6 +30,25 @@ export class ShadedGeometryRendererContext {
|
|
|
30
30
|
];
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Performs maintenance on the context, mainly to check the hash tables for changes
|
|
35
|
+
* @return {Generator}
|
|
36
|
+
*/
|
|
37
|
+
* maintain() {
|
|
38
|
+
const adapters = this.adapters;
|
|
39
|
+
for (let i = 0; i < adapters.length; i++) {
|
|
40
|
+
const adapter = adapters[i];
|
|
41
|
+
|
|
42
|
+
if (adapter === undefined) {
|
|
43
|
+
// adapter may disappear between invocations, since this is a continuation-type function
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// delegate to adapter's maintenance
|
|
48
|
+
yield* adapter.maintain();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
33
52
|
/**
|
|
34
53
|
*
|
|
35
54
|
* @param {number} index
|
|
@@ -2,8 +2,8 @@ import { array_quick_sort_by_comparator } from "../../../../../../core/collectio
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
*
|
|
5
|
-
* @param {THREE.Object3D} a
|
|
6
|
-
* @param {THREE.Object3D} b
|
|
5
|
+
* @param {THREE.Object3D|{material?:Material}} a
|
|
6
|
+
* @param {THREE.Object3D|{material?:Material}} b
|
|
7
7
|
* @returns {number}
|
|
8
8
|
*/
|
|
9
9
|
function compare_by_material(a, b) {
|
|
@@ -96,6 +96,15 @@ export class AbstractRenderAdapter {
|
|
|
96
96
|
this.__object_count = 0;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Used to perform infrequent housekeeping on the adapter's internal data structures
|
|
101
|
+
* Returns a maintenance sequence
|
|
102
|
+
* @return {Generator}
|
|
103
|
+
*/
|
|
104
|
+
* maintain() {
|
|
105
|
+
// override as necessary
|
|
106
|
+
}
|
|
107
|
+
|
|
99
108
|
/**
|
|
100
109
|
* @returns {THREE.Object3D[]}
|
|
101
110
|
*/
|
|
@@ -17,7 +17,6 @@ import Vector3 from "../../../../core/geom/Vector3.js";
|
|
|
17
17
|
import { query_bvh_frustum_from_objects } from "./query/query_bvh_frustum_from_objects.js";
|
|
18
18
|
import { IncrementalDeltaSet } from "../visibility/IncrementalDeltaSet.js";
|
|
19
19
|
import { computeFrustumCorners } from "./computeFrustumCorners.js";
|
|
20
|
-
import { compute_light_data_hash_0 } from "./cluster/compute_light_data_hash_0.js";
|
|
21
20
|
import { read_plane_pair } from "./cluster/read_plane_pair.js";
|
|
22
21
|
import { read_frustum_planes_to_array } from "../../../../core/geom/3d/frustum/read_frustum_planes_to_array.js";
|
|
23
22
|
import { compute_cluster_planes_from_points } from "./cluster/compute_cluster_planes_from_points.js";
|
|
@@ -30,9 +29,7 @@ import { NumericType } from "./data/NumericType.js";
|
|
|
30
29
|
import { computeDataType } from "./data/computeDataType.js";
|
|
31
30
|
import { computeThreeTextureTypeFromDataType } from "./data/computeThreeTextureTypeFromDataType.js";
|
|
32
31
|
import { computeThreeTextureInternalFormatFromDataType } from "./data/computeThreeTextureInternalFormatFromDataType.js";
|
|
33
|
-
import { testClusterEquality } from "./testClusterEquality.js";
|
|
34
32
|
import { BinaryUint32BVH } from "../../../../core/bvh2/binary/2/BinaryUint32BVH.js";
|
|
35
|
-
import { query_bvh_frustum_from_texture } from "./query/query_bvh_frustum_from_texture.js";
|
|
36
33
|
import { mat4 } from "gl-matrix";
|
|
37
34
|
import { TextureAtlas } from "../../texture/atlas/TextureAtlas.js";
|
|
38
35
|
import { CachingTextureAtlas } from "../../texture/atlas/CachingTextureAtlas.js";
|
|
@@ -48,46 +45,8 @@ import { arraySwapElements } from "../../../../core/collection/array/arraySwapEl
|
|
|
48
45
|
import { compareObjectsByNumericId } from "../../../../core/model/object/compareObjectsByNumericId.js";
|
|
49
46
|
import { slice_frustum_linear_to_points } from "../../../../core/geom/3d/frustum/slice_frustum_linear_to_points.js";
|
|
50
47
|
import { read_cluster_frustum_corners } from "../../../../core/geom/3d/frustum/read_cluster_frustum_corners.js";
|
|
48
|
+
import { assign_cluster, LOOKUP_CACHE, scratch_corners, scratch_frustum_planes } from "./assign_cluster.js";
|
|
51
49
|
|
|
52
|
-
const LOOKUP_CACHE_SIZE = 1024; // must be power of two
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Using continuous buffer for related attributes to get better cache utilization
|
|
56
|
-
* @type {ArrayBuffer}
|
|
57
|
-
* @private
|
|
58
|
-
*/
|
|
59
|
-
const _scratch_array_buffer = new ArrayBuffer(
|
|
60
|
-
24 * 4
|
|
61
|
-
+ 24 * 4
|
|
62
|
-
+ LOOKUP_CACHE_SIZE * 4
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
*
|
|
67
|
-
* @type {number[]|Float32Array|Float64Array}
|
|
68
|
-
*/
|
|
69
|
-
const scratch_corners = new Float32Array(_scratch_array_buffer, 0, 24);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Linear access data structure with parameters of each plane of the frustum
|
|
74
|
-
* 6 planes, 4 values per plane
|
|
75
|
-
* Layout: [normal.x, normal.y, normal.z, constant]
|
|
76
|
-
* @type {number[]|Float32Array|Float64Array}
|
|
77
|
-
*/
|
|
78
|
-
const scratch_frustum_planes = new Float32Array(_scratch_array_buffer, 24 * 4, 24);
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Cache used to identify duplicate cells. Helps reduce memory usage and just point to equivalent existing cells instead of allocating new ones.
|
|
82
|
-
* @type {Uint16Array|Uint32Array}
|
|
83
|
-
*/
|
|
84
|
-
const LOOKUP_CACHE = new Uint32Array(_scratch_array_buffer, (24 + 24) * 4, LOOKUP_CACHE_SIZE);
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
*
|
|
88
|
-
* @type {number[]}
|
|
89
|
-
*/
|
|
90
|
-
const scratch_light_nodes = [];
|
|
91
50
|
|
|
92
51
|
/**
|
|
93
52
|
* How many bits are used to encode light type
|
|
@@ -121,84 +80,6 @@ function encode_light_descriptor(address, light) {
|
|
|
121
80
|
return light_type | (address << 2);
|
|
122
81
|
}
|
|
123
82
|
|
|
124
|
-
function assign_cluster(
|
|
125
|
-
tile_texture_data_offset,
|
|
126
|
-
light_bvh,
|
|
127
|
-
lookup_address_offset,
|
|
128
|
-
light_lookup_texture_data,
|
|
129
|
-
tile_texture_data,
|
|
130
|
-
light_source_data
|
|
131
|
-
) {
|
|
132
|
-
|
|
133
|
-
// at this point we have a fully constructed cluster, we can query lights within the cluster
|
|
134
|
-
const match_count = query_bvh_frustum_from_texture(
|
|
135
|
-
scratch_light_nodes,
|
|
136
|
-
0,
|
|
137
|
-
light_bvh,
|
|
138
|
-
light_source_data,
|
|
139
|
-
scratch_frustum_planes,
|
|
140
|
-
scratch_corners
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
// construct tile data
|
|
144
|
-
|
|
145
|
-
// write number of light entries
|
|
146
|
-
tile_texture_data[tile_texture_data_offset + 1] = match_count;
|
|
147
|
-
|
|
148
|
-
if (match_count === 0) {
|
|
149
|
-
// don't waste time on assignment, there are no lights in the cluster
|
|
150
|
-
return 0;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/*
|
|
154
|
-
We can sort lights by index to get higher cache utilization, but it turns out quite costly.
|
|
155
|
-
Through experimentation, sorting was taking around 20% of the whole assignment time
|
|
156
|
-
while improving number of cache hits by around 40% (8000 without sorting -> 11000 with sorting)
|
|
157
|
-
*/
|
|
158
|
-
const cluster_data_hash = compute_light_data_hash_0(scratch_light_nodes, match_count) & (LOOKUP_CACHE_SIZE - 1);
|
|
159
|
-
|
|
160
|
-
const existing_lookup_address = LOOKUP_CACHE[cluster_data_hash];
|
|
161
|
-
|
|
162
|
-
if (existing_lookup_address !== 0xFFFFFFFF) {
|
|
163
|
-
// found a hash match, validate equality
|
|
164
|
-
const existing_lookup_entry_match = testClusterEquality(
|
|
165
|
-
scratch_light_nodes,
|
|
166
|
-
match_count,
|
|
167
|
-
light_lookup_texture_data,
|
|
168
|
-
existing_lookup_address
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
if (existing_lookup_entry_match) {
|
|
172
|
-
tile_texture_data[tile_texture_data_offset] = existing_lookup_address;
|
|
173
|
-
|
|
174
|
-
//hash_reuse_count++;
|
|
175
|
-
|
|
176
|
-
return 0;
|
|
177
|
-
}
|
|
178
|
-
} else {
|
|
179
|
-
// no hash entry exists, create one
|
|
180
|
-
LOOKUP_CACHE[cluster_data_hash] = lookup_address_offset;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// if we get to this point, that means no hash match was found, or equality check was failed
|
|
184
|
-
|
|
185
|
-
// write address of the lookup table
|
|
186
|
-
tile_texture_data[tile_texture_data_offset] = lookup_address_offset;
|
|
187
|
-
|
|
188
|
-
for (let i = 0; i < match_count; i++) {
|
|
189
|
-
/**
|
|
190
|
-
*
|
|
191
|
-
* @type {number}
|
|
192
|
-
*/
|
|
193
|
-
const light_descriptor = scratch_light_nodes[i];
|
|
194
|
-
|
|
195
|
-
// construct lookup table
|
|
196
|
-
light_lookup_texture_data[lookup_address_offset + i] = light_descriptor;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return match_count;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
83
|
/**
|
|
203
84
|
*
|
|
204
85
|
* @param {IncrementalDeltaSet<PointLightData>} data_set
|
|
@@ -968,7 +849,7 @@ export class LightManager {
|
|
|
968
849
|
|
|
969
850
|
/**
|
|
970
851
|
*
|
|
971
|
-
* @type {number[]|
|
|
852
|
+
* @type {number[]|Float32Array}
|
|
972
853
|
*/
|
|
973
854
|
const light_source_data = this.__light_data.getTexture().image.data;
|
|
974
855
|
|
|
@@ -1033,7 +914,7 @@ export class LightManager {
|
|
|
1033
914
|
light_source_data
|
|
1034
915
|
);
|
|
1035
916
|
|
|
1036
|
-
|
|
917
|
+
const decal_count = assign_cluster(
|
|
1037
918
|
tile_data_offset + 2,
|
|
1038
919
|
bvh_decals,
|
|
1039
920
|
lookup_address_offset,
|
|
@@ -1041,6 +922,23 @@ export class LightManager {
|
|
|
1041
922
|
tile_texture_data,
|
|
1042
923
|
light_source_data
|
|
1043
924
|
);
|
|
925
|
+
|
|
926
|
+
/*
|
|
927
|
+
TODO fix decal sorting artifacts
|
|
928
|
+
|
|
929
|
+
if (decal_count > 1) { // only sort when there are 2 or more, sorting 1 object is pointless
|
|
930
|
+
// decals need to be sorted for correct blending result
|
|
931
|
+
sort_decal_data(
|
|
932
|
+
light_lookup_texture_data,
|
|
933
|
+
light_source_data,
|
|
934
|
+
lookup_address_offset,
|
|
935
|
+
decal_count
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
*/
|
|
940
|
+
|
|
941
|
+
lookup_address_offset += decal_count;
|
|
1044
942
|
}
|
|
1045
943
|
}
|
|
1046
944
|
}
|
|
@@ -1178,12 +1076,24 @@ export class LightManager {
|
|
|
1178
1076
|
}
|
|
1179
1077
|
|
|
1180
1078
|
/**
|
|
1181
|
-
*
|
|
1079
|
+
* Set resolution of the cluster texture, higher resolution will result in less load on the GPU, but will take up more RAM to represent and more time to build the clusters each frame
|
|
1182
1080
|
* @param {number} x
|
|
1183
1081
|
* @param {number} y
|
|
1184
1082
|
* @param {number} z
|
|
1185
1083
|
*/
|
|
1186
1084
|
setTileMapResolution(x, y, z) {
|
|
1085
|
+
assert.isNumber(x,'x');
|
|
1086
|
+
assert.isNonNegativeInteger(x,'x');
|
|
1087
|
+
assert.isFiniteNumber(x,'x');
|
|
1088
|
+
|
|
1089
|
+
assert.isNumber(y,'y');
|
|
1090
|
+
assert.isNonNegativeInteger(y,'y');
|
|
1091
|
+
assert.isFiniteNumber(y,'y');
|
|
1092
|
+
|
|
1093
|
+
assert.isNumber(z,'z');
|
|
1094
|
+
assert.isNonNegativeInteger(z,'z');
|
|
1095
|
+
assert.isFiniteNumber(z,'z');
|
|
1096
|
+
|
|
1187
1097
|
const r = this.__tiles_resolution;
|
|
1188
1098
|
|
|
1189
1099
|
if (x === r.x && y === r.y && z === r.z) {
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Clustered Lighting Specification
|
|
2
|
+
|
|
3
|
+
## 1. GPU Data structures
|
|
4
|
+
|
|
5
|
+
Data is organized into 3 main textures:
|
|
6
|
+
|
|
7
|
+
- Clusters. `type: UINT, format: RGBA, dimension: 3D, filtering: nearest(none)`
|
|
8
|
+
- R - Offset into lookup texture for lights
|
|
9
|
+
- G - number of lights
|
|
10
|
+
- B - Offset into lookup texture for decals
|
|
11
|
+
- A - number of decals
|
|
12
|
+
- Lookup. `type: UINT, format: R, dimensions: 2D, filtering: nearest(none)`
|
|
13
|
+
- R - encoded reference into data texture, where actual lighting information is stored
|
|
14
|
+
- Data. `type: FLOAT, format: RGBA, dimensions: 2D, filtering: nearest(none)`
|
|
15
|
+
- RGBA - single `word` of data, you can think of it as a collection of `VEC4`s
|
|
16
|
+
|
|
17
|
+
Below is a glsl snippet with initialization of uniforms for these textures:
|
|
18
|
+
```glsl
|
|
19
|
+
uniform usampler3D fp_t_light_clusters;
|
|
20
|
+
uniform usampler2D fp_t_light_lookup;
|
|
21
|
+
uniform sampler2D fp_t_light_data;
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 2. Light Data Format
|
|
25
|
+
|
|
26
|
+
Each light is represented as a collection of `VEC4`s inside the `Data` texture.
|
|
27
|
+
|
|
28
|
+
- Point Light:
|
|
29
|
+
- `[ position.x, position.y, position.z, radius ]`
|
|
30
|
+
- `[ color.r, color.g, color.b, intensity ]`
|
|
31
|
+
- Directional Light:
|
|
32
|
+
- `[ direction.x, direction.y, direction.z, UNUSED ]`
|
|
33
|
+
- `[ color.r, color.g, color.b, intensity ]`
|
|
34
|
+
|
|
35
|
+
## 3. Light reference encoding
|
|
36
|
+
|
|
37
|
+
Lookup texture contains integers that encode both the address of light data, and the type of light found at that address, this is done by encoding light type into lower 2 bits of a UINT32
|
|
38
|
+
|
|
39
|
+
Light type codes:
|
|
40
|
+
- 0 - PointLight
|
|
41
|
+
- 1 - Directional Light
|
|
42
|
+
- 2 - Spotlight
|
|
43
|
+
|
|
44
|
+
Encoding snippet in JavaScript:
|
|
45
|
+
```js
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @param {number} address
|
|
49
|
+
* @param {AbstractLight} light
|
|
50
|
+
* @returns {number}
|
|
51
|
+
*/
|
|
52
|
+
function encode_light_descriptor(address, light) {
|
|
53
|
+
const light_type = encode_light_type(light);
|
|
54
|
+
return light_type | (address << 2);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Decoding snippet in GLSL
|
|
59
|
+
```glsl
|
|
60
|
+
ivec2 address_to_data_texture_coordinates(uint address){
|
|
61
|
+
// Lookup texture has 128 width
|
|
62
|
+
uint lookup_index_x = address % 128u;
|
|
63
|
+
uint lookup_index_y = address >> 7;
|
|
64
|
+
|
|
65
|
+
return ivec2(int(lookup_index_x),int(lookup_index_y));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ...
|
|
69
|
+
uint light_descriptor = texelFetch(fp_t_light_lookup, address_to_data_texture_coordinates(lookup_index), 0 ).r;
|
|
70
|
+
|
|
71
|
+
uint type = (light_descriptor) & 0x3u;
|
|
72
|
+
uint light_address = light_descriptor >> 2;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 4. Auxiliary material
|
|
76
|
+
|
|
77
|
+
Convert projection-space depth to linear space
|
|
78
|
+
```glsl
|
|
79
|
+
float convert_depth_to_linear(in float d){
|
|
80
|
+
float d_n = 2.0*d - 1.0;
|
|
81
|
+
|
|
82
|
+
float f = fp_f_camera_far;
|
|
83
|
+
float n = fp_f_camera_near;
|
|
84
|
+
|
|
85
|
+
float fn = f*n;
|
|
86
|
+
|
|
87
|
+
float z_diff = f - n;
|
|
88
|
+
|
|
89
|
+
float denominator = (f + n - d_n * z_diff );
|
|
90
|
+
|
|
91
|
+
float z_view = (2.0*fn) / denominator;
|
|
92
|
+
|
|
93
|
+
return (z_view - n) / z_diff;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Fetching cluster information from fragment shader inside a forward pass
|
|
98
|
+
```glsl
|
|
99
|
+
// v3_cluster_resolution is resolution of our cluster 3d texture
|
|
100
|
+
ivec3 v3_cluster_resolution = textureSize(fp_t_light_tiles, 0);
|
|
101
|
+
ivec3 v3_cluster_position = ivec3( clip_v.x * float(v3_cluster_resolution.x), clip_v.y*float(v3_cluster_resolution.y), (clip_v.z)*float(v3_cluster_resolution.z) );
|
|
102
|
+
|
|
103
|
+
uvec4 fp_cluster_metadata = texelFetch( fp_t_light_tiles, v3_cluster_position, 0 ).rgba;
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Perform lighting (point-light only)
|
|
107
|
+
```glsl
|
|
108
|
+
// read light data
|
|
109
|
+
for(uint i=0u; i < fp_cluster_metadata.y; i++){
|
|
110
|
+
uint lookup_index = fp_cluster_metadata.x + i;
|
|
111
|
+
|
|
112
|
+
uint light_descriptor = texelFetch(fp_t_light_lookup, address_to_data_texture_coordinates(lookup_index), 0 ).r;
|
|
113
|
+
|
|
114
|
+
uint type = (light_descriptor) & 0x3u;
|
|
115
|
+
uint light_address = light_descriptor >> 2;
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if(type == 0u){
|
|
119
|
+
// point light
|
|
120
|
+
|
|
121
|
+
vec4 light_data_0 = texelFetch(fp_t_light_data, address_to_data_texture_coordinates(light_address), 0);
|
|
122
|
+
vec4 light_data_1 = texelFetch(fp_t_light_data, address_to_data_texture_coordinates(light_address+1u), 0);
|
|
123
|
+
|
|
124
|
+
vec3 light_position = light_data_0.xyz;
|
|
125
|
+
float light_radius = light_data_0.w;
|
|
126
|
+
|
|
127
|
+
vec3 light_color = light_data_1.xyz;
|
|
128
|
+
float light_intensity = light_data_1.w;
|
|
129
|
+
|
|
130
|
+
PointLight pointlight;
|
|
131
|
+
|
|
132
|
+
pointlight.position = light_position;
|
|
133
|
+
pointlight.distance = light_radius;
|
|
134
|
+
pointlight.color = light_color*light_intensity;
|
|
135
|
+
pointlight.decay = 1.0;
|
|
136
|
+
|
|
137
|
+
fp_getPointDirectLightIrradiance( pointlight, directLight, vViewPosition );
|
|
138
|
+
RE_Direct( directLight, geometry, material, reflectedLight );
|
|
139
|
+
|
|
140
|
+
} else if(type == 1u){
|
|
141
|
+
// directional light
|
|
142
|
+
|
|
143
|
+
// ...
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## References:
|
|
149
|
+
- [Dr.Strangelight or: How I learned to Stop Worrying and Love the Cluster](https://discourse.threejs.org/t/dr-strangelight-or-how-i-learned-to-stop-worrying-and-love-the-cluster/23104)
|
|
150
|
+
- [Practical Clustered Shading by Emil Persson](http://www.humus.name/Articles/PracticalClusteredShading.pdf)
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
Author: Alexander Goldring, Company Named Limited
|
|
154
|
+
|
|
155
|
+
Private and confidential, copyright (c) Company Named Limited, 2022
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { query_bvh_frustum_from_texture } from "./query/query_bvh_frustum_from_texture.js";
|
|
2
|
+
import { compute_light_data_hash_0 } from "./cluster/compute_light_data_hash_0.js";
|
|
3
|
+
import { testClusterEquality } from "./testClusterEquality.js";
|
|
4
|
+
|
|
5
|
+
const LOOKUP_CACHE_SIZE = 1024; // must be power of two
|
|
6
|
+
/**
|
|
7
|
+
* Using continuous buffer for related attributes to get better cache utilization
|
|
8
|
+
* @type {ArrayBuffer}
|
|
9
|
+
* @private
|
|
10
|
+
*/
|
|
11
|
+
const _scratch_array_buffer = new ArrayBuffer(
|
|
12
|
+
24 * 4
|
|
13
|
+
+ 24 * 4
|
|
14
|
+
+ LOOKUP_CACHE_SIZE * 4
|
|
15
|
+
);
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @type {number[]|Float32Array|Float64Array}
|
|
19
|
+
*/
|
|
20
|
+
export const scratch_corners = new Float32Array(_scratch_array_buffer, 0, 24);
|
|
21
|
+
/**
|
|
22
|
+
* Linear access data structure with parameters of each plane of the frustum
|
|
23
|
+
* 6 planes, 4 values per plane
|
|
24
|
+
* Layout: [normal.x, normal.y, normal.z, constant]
|
|
25
|
+
* @type {number[]|Float32Array|Float64Array}
|
|
26
|
+
*/
|
|
27
|
+
export const scratch_frustum_planes = new Float32Array(_scratch_array_buffer, 24 * 4, 24);
|
|
28
|
+
/**
|
|
29
|
+
* Cache used to identify duplicate cells. Helps reduce memory usage and just point to equivalent existing cells instead of allocating new ones.
|
|
30
|
+
* @type {Uint16Array|Uint32Array}
|
|
31
|
+
*/
|
|
32
|
+
export const LOOKUP_CACHE = new Uint32Array(_scratch_array_buffer, (24 + 24) * 4, LOOKUP_CACHE_SIZE);
|
|
33
|
+
/**
|
|
34
|
+
*
|
|
35
|
+
* @type {number[]}
|
|
36
|
+
*/
|
|
37
|
+
const scratch_light_nodes = [];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @param {number} tile_texture_data_offset
|
|
42
|
+
* @param {BinaryUint32BVH} bvh
|
|
43
|
+
* @param {number} lookup_address_offset
|
|
44
|
+
* @param {number[]|ArrayLike<number>|Uint8ClampedArray|Uint8Array|Uint16Array|Uint32Array} lookup_data
|
|
45
|
+
* @param {number[]|ArrayLike<number>|Uint8Array|Uint8ClampedArray|Uint16Array|Uint32Array} cluster_data
|
|
46
|
+
* @param {number[]|ArrayLike<number>|Float32Array} source_data Light/Decal source information is stored in here, such as position, color etc.
|
|
47
|
+
* @return {number}
|
|
48
|
+
*/
|
|
49
|
+
export function assign_cluster(
|
|
50
|
+
tile_texture_data_offset,
|
|
51
|
+
bvh,
|
|
52
|
+
lookup_address_offset,
|
|
53
|
+
lookup_data,
|
|
54
|
+
cluster_data,
|
|
55
|
+
source_data
|
|
56
|
+
) {
|
|
57
|
+
|
|
58
|
+
// at this point we have a fully constructed cluster, we can query lights within the cluster
|
|
59
|
+
const match_count = query_bvh_frustum_from_texture(
|
|
60
|
+
scratch_light_nodes,
|
|
61
|
+
0,
|
|
62
|
+
bvh,
|
|
63
|
+
source_data,
|
|
64
|
+
scratch_frustum_planes,
|
|
65
|
+
scratch_corners
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// construct tile data
|
|
69
|
+
|
|
70
|
+
// write number of light entries
|
|
71
|
+
cluster_data[tile_texture_data_offset + 1] = match_count;
|
|
72
|
+
|
|
73
|
+
if (match_count === 0) {
|
|
74
|
+
// don't waste time on assignment, there are no lights in the cluster
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
We can sort lights by index to get higher cache utilization, but it turns out quite costly.
|
|
80
|
+
Through experimentation, sorting was taking around 20% of the whole assignment time
|
|
81
|
+
while improving number of cache hits by around 40% (8000 without sorting -> 11000 with sorting)
|
|
82
|
+
*/
|
|
83
|
+
const cluster_data_hash = compute_light_data_hash_0(scratch_light_nodes, match_count) & (LOOKUP_CACHE_SIZE - 1);
|
|
84
|
+
|
|
85
|
+
const existing_lookup_address = LOOKUP_CACHE[cluster_data_hash];
|
|
86
|
+
|
|
87
|
+
if (existing_lookup_address !== 0xFFFFFFFF) {
|
|
88
|
+
// found a hash match, validate equality
|
|
89
|
+
const existing_lookup_entry_match = testClusterEquality(
|
|
90
|
+
scratch_light_nodes,
|
|
91
|
+
match_count,
|
|
92
|
+
lookup_data,
|
|
93
|
+
existing_lookup_address
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (existing_lookup_entry_match) {
|
|
97
|
+
cluster_data[tile_texture_data_offset] = existing_lookup_address;
|
|
98
|
+
|
|
99
|
+
//hash_reuse_count++;
|
|
100
|
+
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
// no hash entry exists, create one
|
|
105
|
+
LOOKUP_CACHE[cluster_data_hash] = lookup_address_offset;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// if we get to this point, that means no hash match was found, or equality check was failed
|
|
109
|
+
|
|
110
|
+
// write address of the lookup table
|
|
111
|
+
cluster_data[tile_texture_data_offset] = lookup_address_offset;
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < match_count; i++) {
|
|
114
|
+
/**
|
|
115
|
+
*
|
|
116
|
+
* @type {number}
|
|
117
|
+
*/
|
|
118
|
+
const light_descriptor = scratch_light_nodes[i];
|
|
119
|
+
|
|
120
|
+
// construct lookup table
|
|
121
|
+
lookup_data[lookup_address_offset + i] = light_descriptor;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return match_count;
|
|
125
|
+
}
|
|
@@ -100,7 +100,11 @@ export class Decal extends AbstractLight {
|
|
|
100
100
|
|
|
101
101
|
array_copy(this.uv, 0, destination, address + 16, 4);
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
// required for sorting
|
|
104
|
+
destination[address + 20] = this.draw_priority;
|
|
105
|
+
destination[address + 21] = this.id;
|
|
106
|
+
|
|
107
|
+
return 24;
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
|
|
@@ -110,4 +114,4 @@ export class Decal extends AbstractLight {
|
|
|
110
114
|
*/
|
|
111
115
|
Decal.prototype.isDecal = true;
|
|
112
116
|
|
|
113
|
-
Decal.prototype.ENCODED_SLOT_COUNT =
|
|
117
|
+
Decal.prototype.ENCODED_SLOT_COUNT = 24;
|
|
@@ -9,6 +9,7 @@ export class ForwardPlusRenderingPlugin extends EnginePlugin {
|
|
|
9
9
|
super();
|
|
10
10
|
|
|
11
11
|
this.__light_manager = new LightManager();
|
|
12
|
+
lm.setTileMapResolution(16, 8, 8);
|
|
12
13
|
|
|
13
14
|
this.__resolution = new ThreeVector2();
|
|
14
15
|
|
|
@@ -19,6 +20,16 @@ export class ForwardPlusRenderingPlugin extends EnginePlugin {
|
|
|
19
20
|
});
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Set resolution of the cluster texture, higher resolution will result in less load on the GPU, but will take up more RAM to represent and more time to build the clusters each frame
|
|
25
|
+
* @param {number} x
|
|
26
|
+
* @param {number} y
|
|
27
|
+
* @param {number} z
|
|
28
|
+
*/
|
|
29
|
+
setClusterResolution(x, y, z) {
|
|
30
|
+
this.__light_manager.setTileMapResolution(x, y, z);
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
/**
|
|
23
34
|
*
|
|
24
35
|
* @param {CameraView} view
|
|
@@ -37,10 +48,6 @@ export class ForwardPlusRenderingPlugin extends EnginePlugin {
|
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
async startup() {
|
|
40
|
-
const lm = this.__light_manager;
|
|
41
|
-
|
|
42
|
-
lm.setTileMapResolution(16, 8, 8);
|
|
43
|
-
|
|
44
51
|
const graphics = this.engine.graphics;
|
|
45
52
|
|
|
46
53
|
graphics.getMaterialManager().addCompileStep(this.__material_transformer);
|