@expofp/renderer 3.45.9 → 3.45.11
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.
|
@@ -351,7 +351,9 @@ function closeScaled(scaled) {
|
|
|
351
351
|
*
|
|
352
352
|
* Mipmaps stay on. A floor plan is zoomed far out as often as it is zoomed far in, and without
|
|
353
353
|
* mipmaps every image shimmers under minification. The cost is that neighbors can bleed through
|
|
354
|
-
* the deeper mip levels
|
|
354
|
+
* the deeper mip levels. The packer's gap and `TextureMaterial`'s region clamp keep the first few
|
|
355
|
+
* levels clean (see `PADDING` in `pack-sources.ts`), and past those a strip stays about one screen
|
|
356
|
+
* pixel wide.
|
|
355
357
|
* @param image - The packed atlas image: a canvas, or a bitmap a worker transferred out of one.
|
|
356
358
|
* @returns The texture.
|
|
357
359
|
*/
|
|
@@ -114,12 +114,22 @@ export function packSources(sources, maxDimension, createCanvas = createAtlasCan
|
|
|
114
114
|
return { atlases, reports };
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
|
-
* The gap between neighboring images on an atlas. This gap stops
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
117
|
+
* The gap between neighboring images on an atlas. This gap stops filtering at one image's edge from
|
|
118
|
+
* reaching into the next image's pixels. The packer applies this gap *between* rects only, so atlas
|
|
119
|
+
* edges stay flush against the pixels. This is also why a tile exactly `maxDimension` across still
|
|
120
|
+
* fits in a bin.
|
|
121
|
+
*
|
|
122
|
+
* One pixel is enough for the full-size texture, but not for its mipmaps. Mip level L averages
|
|
123
|
+
* blocks of 2^L pixels, so a block at an image's edge takes in the gap and, once the gap is
|
|
124
|
+
* narrower than the block, the neighbor too. That shows as a thin strip of the neighbor's color
|
|
125
|
+
* along the edge when zoomed out. `TextureMaterial` clamps each sample inside its own region, and
|
|
126
|
+
* with that clamp a gap of 2^L keeps levels 0 to L clean. Sixteen pixels cover levels 0 to 4, where
|
|
127
|
+
* an image draws at a sixteenth of its pixel size or larger. Four pixels were measured too few: a
|
|
128
|
+
* 256 px booth logo zoomed out to about 40 screen pixels read level 3 and showed the strip. Past
|
|
129
|
+
* level 4, the strip can come back, still about one screen pixel wide, against an image under 16
|
|
130
|
+
* screen pixels for a 256 px logo.
|
|
121
131
|
*/
|
|
122
|
-
const PADDING =
|
|
132
|
+
const PADDING = 16;
|
|
123
133
|
/**
|
|
124
134
|
* Packs one kind of source, either all resizable or all fixed, into atlases. The function tags
|
|
125
135
|
* each atlas with the group's kind and records each source's tile count in `reports`.
|
|
@@ -24,7 +24,8 @@ export interface TextureMaterialProps {
|
|
|
24
24
|
* instanced/batched mesh can pack many regions into one atlas.
|
|
25
25
|
*
|
|
26
26
|
* When {@link TextureMaterialProps.uvOffset} is set, the patch overwrites `vMapUv` after
|
|
27
|
-
* `<uv_vertex>`, replacing the stock chunk's value
|
|
27
|
+
* `<uv_vertex>`, replacing the stock chunk's value, and samples the map clamped to that region (see
|
|
28
|
+
* {@link SAMPLE_REGION}). Instanced meshes wrap this `onBeforeCompile`
|
|
28
29
|
* and inject their uniform fetch at the top of `main()`, so the uniform is in scope here.
|
|
29
30
|
* @param props - The material's texture and optional uv-offset uniform name.
|
|
30
31
|
* @returns The material element.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { OneFactor } from 'three';
|
|
3
|
+
import { declareBeforeMain, replaceAnchor } from '../../core/shader-source';
|
|
3
4
|
import { useLayerOverlay } from '../layer/layer-context';
|
|
4
5
|
import { useDimPatch } from './effects/color-dim';
|
|
5
6
|
import { overlayParams, sharedParams } from './shared';
|
|
@@ -8,7 +9,8 @@ import { overlayParams, sharedParams } from './shared';
|
|
|
8
9
|
* instanced/batched mesh can pack many regions into one atlas.
|
|
9
10
|
*
|
|
10
11
|
* When {@link TextureMaterialProps.uvOffset} is set, the patch overwrites `vMapUv` after
|
|
11
|
-
* `<uv_vertex>`, replacing the stock chunk's value
|
|
12
|
+
* `<uv_vertex>`, replacing the stock chunk's value, and samples the map clamped to that region (see
|
|
13
|
+
* {@link SAMPLE_REGION}). Instanced meshes wrap this `onBeforeCompile`
|
|
12
14
|
* and inject their uniform fetch at the top of `main()`, so the uniform is in scope here.
|
|
13
15
|
* @param props - The material's texture and optional uv-offset uniform name.
|
|
14
16
|
* @returns The material element.
|
|
@@ -20,11 +22,15 @@ export function TextureMaterial(props) {
|
|
|
20
22
|
function onBeforeCompile(parameters) {
|
|
21
23
|
applyDim(parameters);
|
|
22
24
|
if (uvOffset) {
|
|
23
|
-
parameters.vertexShader = parameters.vertexShader.replace('#include <uv_vertex>',
|
|
25
|
+
parameters.vertexShader = declareBeforeMain(parameters.vertexShader, MAP_REGION_VARYING).replace('#include <uv_vertex>',
|
|
24
26
|
/*glsl*/ `
|
|
25
27
|
#include <uv_vertex>
|
|
26
28
|
vMapUv = uv * ${uvOffset}.zw + ${uvOffset}.xy;
|
|
29
|
+
vMapRegion = ${uvOffset};
|
|
27
30
|
`.trim());
|
|
31
|
+
// The region rides to the fragment shader as a varying, because the per-instance uniform
|
|
32
|
+
// fetch exists only in the vertex shader.
|
|
33
|
+
parameters.fragmentShader = replaceAnchor(declareBeforeMain(parameters.fragmentShader, `${MAP_REGION_VARYING}\n${SAMPLE_REGION}`), MAP_FRAGMENT, REGION_MAP_FRAGMENT);
|
|
28
34
|
}
|
|
29
35
|
if (opacityUniform) {
|
|
30
36
|
// All four channels: the blend is premultiplied, so rgb already carries the texel's alpha and
|
|
@@ -46,3 +52,55 @@ export function TextureMaterial(props) {
|
|
|
46
52
|
// premultiplied.
|
|
47
53
|
return (_jsx("meshBasicMaterial", { ...sharedParams, ...(overlay ? overlayParams : null), blendSrc: OneFactor, map: map, onBeforeCompile: onBeforeCompile }));
|
|
48
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The atlas region an instance samples, carried from the vertex shader's per-instance uniform to the
|
|
57
|
+
* fragment shader. `xy` is the region's origin in uv space and `zw` its size. Flat, because the value
|
|
58
|
+
* is one per instance and needs no interpolation.
|
|
59
|
+
*/
|
|
60
|
+
const MAP_REGION_VARYING = /* glsl */ `flat varying vec4 vMapRegion;`;
|
|
61
|
+
/**
|
|
62
|
+
* Samples an atlas without reading past the edge of one region.
|
|
63
|
+
*
|
|
64
|
+
* The GPU picks a mip level from how fast uv changes across the screen, and each level averages
|
|
65
|
+
* bigger blocks of the atlas. Near a region's edge, a sample can then take in the gap and the
|
|
66
|
+
* neighbor beyond it, which draws a thin strip of the neighbor's color. The function clamps uv to
|
|
67
|
+
* the region, inset by half a texel of the coarsest level the sample reads. Bilinear filtering at
|
|
68
|
+
* the clamped point then stays on blocks that hold this region's pixels. With trilinear filtering,
|
|
69
|
+
* the GPU blends the levels on both sides of the fractional level, so the inset uses the level
|
|
70
|
+
* above it.
|
|
71
|
+
*
|
|
72
|
+
* The clamp alone cannot keep a block from straddling the edge. That is what the packer's gap is
|
|
73
|
+
* for (see `PADDING` in `pack-sources.ts`), and the two together keep a level clean while the gap
|
|
74
|
+
* is at least one block wide.
|
|
75
|
+
*
|
|
76
|
+
* `textureGrad` takes the gradients of the unclamped uv. Gradients of the clamped uv fall to zero
|
|
77
|
+
* where the clamp holds, and the GPU would then read that edge at full resolution.
|
|
78
|
+
*/
|
|
79
|
+
const SAMPLE_REGION = /* glsl */ `
|
|
80
|
+
vec4 sampleMapRegion( sampler2D atlas, vec2 uv, vec4 region ) {
|
|
81
|
+
vec2 size = vec2( textureSize( atlas, 0 ) );
|
|
82
|
+
vec2 gradX = dFdx( uv );
|
|
83
|
+
vec2 gradY = dFdy( uv );
|
|
84
|
+
vec2 texelsX = gradX * size;
|
|
85
|
+
vec2 texelsY = gradY * size;
|
|
86
|
+
float level = 0.5 * log2( max( dot( texelsX, texelsX ), dot( texelsY, texelsY ) ) );
|
|
87
|
+
vec2 inset = 0.5 * exp2( ceil( max( level, 0.0 ) ) ) / size;
|
|
88
|
+
// A region narrower than two insets clamps to its middle, because clamp() with a lower bound
|
|
89
|
+
// above its upper bound is undefined in GLSL.
|
|
90
|
+
vec2 middle = region.xy + 0.5 * region.zw;
|
|
91
|
+
vec2 low = min( region.xy + inset, middle );
|
|
92
|
+
vec2 high = max( region.xy + region.zw - inset, middle );
|
|
93
|
+
return textureGrad( atlas, clamp( uv, low, high ), gradX, gradY );
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
/** Three's map chunk, which {@link REGION_MAP_FRAGMENT} takes the place of. */
|
|
97
|
+
const MAP_FRAGMENT = /* glsl */ `#include <map_fragment>`;
|
|
98
|
+
/**
|
|
99
|
+
* Three's `map_fragment`, with the sample taken through {@link SAMPLE_REGION}. The video decode
|
|
100
|
+
* branch is left out, because an atlas is drawn on a canvas and is never a video.
|
|
101
|
+
*/
|
|
102
|
+
const REGION_MAP_FRAGMENT = /* glsl */ `
|
|
103
|
+
#ifdef USE_MAP
|
|
104
|
+
diffuseColor *= sampleMapRegion( map, vMapUv, vMapRegion );
|
|
105
|
+
#endif
|
|
106
|
+
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expofp/renderer",
|
|
3
|
-
"version": "3.45.
|
|
3
|
+
"version": "3.45.11",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/expofp/efp-app.git",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"troika-three-text": "^0.52.5",
|
|
43
43
|
"tslib": "^2.3.0",
|
|
44
44
|
"webgl-memory": "^1.1.3",
|
|
45
|
-
"@expofp/geometry": "3.45.
|
|
45
|
+
"@expofp/geometry": "3.45.11"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@react-three/test-renderer": "^9.1.0",
|