@driftengine/core 4.13.2 → 4.13.3
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.json +11 -0
- package/README.md +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/render/backend/webgl2/renderer.d.ts +12 -0
- package/dist/render/backend/webgl2/renderer.js +47 -2
- package/dist/render/backend/webgpu/renderer.d.ts +9 -0
- package/dist/render/backend/webgpu/renderer.js +33 -1
- package/dist/render/shaders/flat/detail.d.ts +21 -0
- package/dist/render/shaders/flat/detail.js +74 -0
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +1 -0
- package/dist/render/shaders/flat/index.d.ts +5 -0
- package/dist/render/shaders/flat/index.js +3 -0
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +2 -0
- package/dist/render/shaders/generated/flat.wgsl.d.ts +144 -16
- package/dist/render/shaders/generated/flat.wgsl.js +183 -55
- package/dist/render/surfaceDetail.d.ts +79 -0
- package/dist/render/surfaceDetail.js +86 -0
- package/dist/render/surfaceTexture.d.ts +9 -0
- package/package.json +6 -6
- package/src/index.ts +2 -0
- package/src/render/backend/webgl2/renderer.ts +58 -2
- package/src/render/backend/webgpu/renderer.ts +42 -1
- package/src/render/shaders/flat/detail.ts +74 -0
- package/src/render/shaders/flat/glassTint.ts +3 -1
- package/src/render/shaders/flat/index.ts +8 -0
- package/src/render/shaders/flat/main.ts +2 -0
- package/src/render/shaders/generated/flat.wgsl.ts +183 -55
- package/src/render/surfaceDetail.ts +136 -0
- package/src/render/surfaceTexture.ts +9 -0
package/CHANGELOG.json
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "4.13.3",
|
|
4
|
+
"date": "2026-10-12",
|
|
5
|
+
"entries": [
|
|
6
|
+
{
|
|
7
|
+
"kind": "added",
|
|
8
|
+
"short": "A material can lay fine grain over its maps, a region at a time: up to sixteen detail normals and occlusions at their own repeats, chosen per texel: `SurfaceMaterial.detail`.",
|
|
9
|
+
"text": "`SurfaceMaterial.detail` lays a surface's finest grain over its base maps: up to sixteen regions, each a tangent-space detail normal map, and an occlusion, at its own repeat, offset and strengths, and a region map choosing which one each texel wears. A surface's grain, leather, a weave, brushed metal, a face's pores, is a map of a few hundred texels repeated tens of times across it, and baked into the base maps it is lost: a 256-texel detail tiled forty times over a 2048-texel map would want some ten thousand texels a side, so a consumer's characters looked smooth beside the surfaces they were authored with. Each texel reads its own region's detail alone, turned through a frame built on the normal the base maps made, and multiplies its occlusion into the surface's; the reads take explicit gradients, the base coordinates' times the region's repeat, since the region is the pixel's own. **Where the maps are, and why**: the lit stage had no texture binding to spare on either backend, so the detail maps are layers of the array read where a shading model's map is, and a material with a `modelMap` puts its detail layers in that array; the region map is a layer of the material's ORM array, one index a texel in its red, read exactly so a region's edge is sharp, which wants an uncompressed array. A material whose model-map slot holds another array, or that is placed by the world, is drawn without its detail, said once. Sixteen regions are thirty-three vectors: WebGL2 compiles them in only when a material first asks (`SURFACE_DETAIL`) and refuses them, said once, on a part with no room; on WebGPU every material's block grows from 1,024 bytes to 1,536. Measured on an RX 9070 XT (`demo/dev/detail.html`): where a material reads region 0 it draws exactly what one region with no region map draws, on WebGPU and on WebGL2 over Vulkan and over OpenGL; its other regions differ from it by 166,558 pixels; and the backends part by at most one level in a channel."
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
},
|
|
2
13
|
{
|
|
3
14
|
"version": "4.13.2",
|
|
4
15
|
"date": "2026-10-11",
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ WebGL2 everywhere else, behind one API, and carries the fixed-step loop, the ren
|
|
|
6
6
|
input, the scene graph, geometry, text and the whole of `@driftengine/physics`, which it re-exports.
|
|
7
7
|
Everything else is a separate package a game adds when it reaches for it.
|
|
8
8
|
|
|
9
|
-
**Cost:
|
|
9
|
+
**Cost: 929.4 KB gzipped, importing `createRenderer`.** Measured by `scripts/size-gate.test.mjs`,
|
|
10
10
|
which fails the build if the figure drifts by more than 3%.
|
|
11
11
|
|
|
12
12
|
The manual is at **[driftengine.dev/docs](https://driftengine.dev/docs)**, with an example running
|
package/dist/index.d.ts
CHANGED
|
@@ -231,6 +231,8 @@ export { SurfaceTexture } from './render/surfaceTexture.ts';
|
|
|
231
231
|
export type { SurfaceMaterial, SurfaceTextureOptions } from './render/surfaceTexture.ts';
|
|
232
232
|
export type { SurfaceProjection } from './render/surfaceProjection.ts';
|
|
233
233
|
export type { SurfaceLayers } from './render/surfaceLayers.ts';
|
|
234
|
+
export type { SurfaceDetail, SurfaceDetailRegion } from './render/surfaceDetail.ts';
|
|
235
|
+
export { MAX_DETAIL_REGIONS } from './render/surfaceDetail.ts';
|
|
234
236
|
export type { SurfaceLayerLook } from './render/surfaceLayerLooks.ts';
|
|
235
237
|
export { compressedFormatName, isCompressedSource, levelBytes, uploadsCompressed, } from './render/compressedSource.ts';
|
|
236
238
|
export type { BlockFormat, CompressedTextureFormat, CompressedTextureSource, SurfaceSource, } from './render/compressedSource.ts';
|
package/dist/index.js
CHANGED
|
@@ -287,6 +287,7 @@ export { MAX_POINT_LIGHTS, SURFACE_TEXTURE_UNIT } from './render/lightBudget.js'
|
|
|
287
287
|
/* The width a light buffer can usefully be with `clusteredLights` on; `createPointLightBuffer`. */
|
|
288
288
|
export { MAX_CLUSTERED_LIGHTS } from './render/clusteredLights.js';
|
|
289
289
|
export { SurfaceTexture } from './render/surfaceTexture.js';
|
|
290
|
+
export { MAX_DETAIL_REGIONS } from './render/surfaceDetail.js';
|
|
290
291
|
/*
|
|
291
292
|
* Compressed textures — BC, ETC2 and EAC, ASTC: the source `createSurfaceTexture` takes as blocks,
|
|
292
293
|
* the one question to ask before handing one over — whether this device takes its format
|
|
@@ -1300,6 +1300,13 @@ export declare class WebGL2Renderer implements RendererApi {
|
|
|
1300
1300
|
/** Whether it has room for the layers' looks beside them, and whether the refusal was said. */
|
|
1301
1301
|
private looksRoom;
|
|
1302
1302
|
private warnedLooksRoom;
|
|
1303
|
+
/** `uDetail`, the material's tiled detail packed by `packSurfaceDetail`. */
|
|
1304
|
+
private readonly detailFloats;
|
|
1305
|
+
/** Whether the lit stage has room for detail on this part, asked once. See `detailOfMaterial`. */
|
|
1306
|
+
private detailRoom;
|
|
1307
|
+
private warnedDetailRoom;
|
|
1308
|
+
/** The refusals already said, each once. */
|
|
1309
|
+
private readonly warnedDetail;
|
|
1303
1310
|
private warnedLayersModelMap;
|
|
1304
1311
|
private warnedLayersTriplanar;
|
|
1305
1312
|
private warnedOverlayRoom;
|
|
@@ -2199,6 +2206,11 @@ export declare class WebGL2Renderer implements RendererApi {
|
|
|
2199
2206
|
* as they are, said once — which on a material sharing its arrays reads the wrong textures, so
|
|
2200
2207
|
* the sentence names the way out.
|
|
2201
2208
|
*/
|
|
2209
|
+
/**
|
|
2210
|
+
* A material's detail, or null where it is refused: its slot holds another array, it is placed by
|
|
2211
|
+
* the world, or the lit stage has no room for its thirty-three vectors. Each said once.
|
|
2212
|
+
*/
|
|
2213
|
+
private detailOfMaterial;
|
|
2202
2214
|
private looksFit;
|
|
2203
2215
|
/** Says once that a layered material's triplanar projection is laid on the horizontal plane. */
|
|
2204
2216
|
private warnLayersTriplanar;
|
|
@@ -41,6 +41,7 @@ import { resolveTransmission } from '../../transmission.js';
|
|
|
41
41
|
import { resolveOcclusionFade } from '../../occlusionFade.js';
|
|
42
42
|
import { orthographicDepthSpan } from '../../lightMatrix.js';
|
|
43
43
|
import { LAYER_FLOATS, layerMaskMap, layeredProjection, layersFitBeside, packSurfaceLayers, } from '../../surfaceLayers.js';
|
|
44
|
+
import { DETAIL_FLOATS, detailOf, detailRefusal, detailRefusalWarning, packSurfaceDetail, } from '../../surfaceDetail.js';
|
|
44
45
|
import { LOOK_FLOATS, asksLayerLooks, packLayerLooks } from '../../surfaceLayerLooks.js';
|
|
45
46
|
import { FILM_FRAG, FILM_VERT } from '../../shaders/film.js';
|
|
46
47
|
import { SceneTarget } from '../../sceneTarget.js';
|
|
@@ -976,6 +977,13 @@ export class WebGL2Renderer {
|
|
|
976
977
|
/** Whether it has room for the layers' looks beside them, and whether the refusal was said. */
|
|
977
978
|
looksRoom = null;
|
|
978
979
|
warnedLooksRoom = false;
|
|
980
|
+
/** `uDetail`, the material's tiled detail packed by `packSurfaceDetail`. */
|
|
981
|
+
detailFloats = new Float32Array(DETAIL_FLOATS);
|
|
982
|
+
/** Whether the lit stage has room for detail on this part, asked once. See `detailOfMaterial`. */
|
|
983
|
+
detailRoom = null;
|
|
984
|
+
warnedDetailRoom = false;
|
|
985
|
+
/** The refusals already said, each once. */
|
|
986
|
+
warnedDetail = new Set();
|
|
979
987
|
warnedLayersModelMap = false;
|
|
980
988
|
warnedLayersTriplanar = false;
|
|
981
989
|
warnedOverlayRoom = false;
|
|
@@ -3307,11 +3315,18 @@ export class WebGL2Renderer {
|
|
|
3307
3315
|
const modelMap = material?.modelMap ?? null;
|
|
3308
3316
|
/* A layered material's mask, where it is a map, goes where a model's would: surfaceLayers.ts. */
|
|
3309
3317
|
const layers = this.layersOf(material);
|
|
3310
|
-
const
|
|
3318
|
+
const taken = modelMap ?? layerMaskMap(layers);
|
|
3319
|
+
/* And its detail's array, where nothing else is read there: surfaceDetail.ts. */
|
|
3320
|
+
const detail = this.detailOfMaterial(material, taken);
|
|
3321
|
+
const boundMap = taken ?? detail?.maps ?? null;
|
|
3311
3322
|
gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
|
|
3312
3323
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3313
3324
|
if (boundMap !== null)
|
|
3314
3325
|
boundMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
|
|
3326
|
+
packSurfaceDetail(detail, (material?.orm ?? null) !== null, this.detailFloats);
|
|
3327
|
+
if (detail !== null)
|
|
3328
|
+
this.askLit('SURFACE_DETAIL');
|
|
3329
|
+
gl.uniform4fv(u['uDetail'] ?? null, this.detailFloats);
|
|
3315
3330
|
packSurfaceLayers(layers, this.layerFloats);
|
|
3316
3331
|
if (layers !== null)
|
|
3317
3332
|
this.askLit('LAYERED');
|
|
@@ -3598,6 +3613,33 @@ export class WebGL2Renderer {
|
|
|
3598
3613
|
* as they are, said once — which on a material sharing its arrays reads the wrong textures, so
|
|
3599
3614
|
* the sentence names the way out.
|
|
3600
3615
|
*/
|
|
3616
|
+
/**
|
|
3617
|
+
* A material's detail, or null where it is refused: its slot holds another array, it is placed by
|
|
3618
|
+
* the world, or the lit stage has no room for its thirty-three vectors. Each said once.
|
|
3619
|
+
*/
|
|
3620
|
+
detailOfMaterial(material, taken) {
|
|
3621
|
+
const detail = detailOf(material);
|
|
3622
|
+
if (detail === null)
|
|
3623
|
+
return null;
|
|
3624
|
+
const refusal = detailRefusal(detail, taken, (material?.projection ?? null) !== null);
|
|
3625
|
+
if (refusal !== null) {
|
|
3626
|
+
if (!this.warnedDetail.has(refusal)) {
|
|
3627
|
+
this.warnedDetail.add(refusal);
|
|
3628
|
+
console.warn(detailRefusalWarning(refusal));
|
|
3629
|
+
}
|
|
3630
|
+
return null;
|
|
3631
|
+
}
|
|
3632
|
+
this.detailRoom ??= this.switchFits('SURFACE_DETAIL');
|
|
3633
|
+
if (this.detailRoom)
|
|
3634
|
+
return detail;
|
|
3635
|
+
if (!this.warnedDetailRoom) {
|
|
3636
|
+
this.warnedDetailRoom = true;
|
|
3637
|
+
console.warn("Renderer: a material's detail is refused on this device. It takes thirty-three fragment " +
|
|
3638
|
+
'uniform vectors the lit shader does not have, so the material is drawn without it. A ' +
|
|
3639
|
+
'lower `maxLights` makes the room, or WebGPU has it.');
|
|
3640
|
+
}
|
|
3641
|
+
return null;
|
|
3642
|
+
}
|
|
3601
3643
|
looksFit(layers) {
|
|
3602
3644
|
if (!asksLayerLooks(layers))
|
|
3603
3645
|
return false;
|
|
@@ -5595,6 +5637,7 @@ export class WebGL2Renderer {
|
|
|
5595
5637
|
movingSun: asIf.MOVING_SUN ?? this.litOn.MOVING_SUN,
|
|
5596
5638
|
layered: asIf.LAYERED ?? this.litOn.LAYERED,
|
|
5597
5639
|
layerLooks: asIf.LAYER_LOOKS ?? this.litOn.LAYER_LOOKS,
|
|
5640
|
+
surfaceDetail: asIf.SURFACE_DETAIL ?? this.litOn.SURFACE_DETAIL,
|
|
5598
5641
|
maxLights: budget.maxLights,
|
|
5599
5642
|
maxAreaLights: budget.maxAreaLights,
|
|
5600
5643
|
});
|
|
@@ -7454,9 +7497,10 @@ export class WebGL2Renderer {
|
|
|
7454
7497
|
this.projection.fill(0);
|
|
7455
7498
|
/* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
|
|
7456
7499
|
resolveTransmission(null, this.transmission);
|
|
7457
|
-
/* Nor layers: one layer until a material asks for more. */
|
|
7500
|
+
/* Nor layers: one layer until a material asks for more. Nor detail. */
|
|
7458
7501
|
this.layerFloats.fill(0);
|
|
7459
7502
|
this.lookFloats.fill(0);
|
|
7503
|
+
this.detailFloats.fill(0);
|
|
7460
7504
|
this.overlayMaps = null;
|
|
7461
7505
|
this.overlayTexture = null;
|
|
7462
7506
|
/* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
|
|
@@ -7601,6 +7645,7 @@ export class WebGL2Renderer {
|
|
|
7601
7645
|
gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
|
|
7602
7646
|
gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
|
|
7603
7647
|
gl.uniform4fv(u['uLayerLooks'] ?? null, this.lookFloats);
|
|
7648
|
+
gl.uniform4fv(u['uDetail'] ?? null, this.detailFloats);
|
|
7604
7649
|
/* Neither refracting nor glass, which is every draw that does not say otherwise. See
|
|
7605
7650
|
`bindSeeThrough` for the four numbers in the one vector. */
|
|
7606
7651
|
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|
|
@@ -1208,6 +1208,10 @@ export declare class WebGPURenderer implements RendererApi {
|
|
|
1208
1208
|
*/
|
|
1209
1209
|
private readonly transmission;
|
|
1210
1210
|
private warnedLayersModelMap;
|
|
1211
|
+
/** `uDetail`, the material's tiled detail packed by `packSurfaceDetail`. */
|
|
1212
|
+
private readonly detailFloats;
|
|
1213
|
+
/** The detail refusals already said, each once. See `detailOfMaterial`. */
|
|
1214
|
+
private readonly warnedDetail;
|
|
1211
1215
|
private warnedLayersTriplanar;
|
|
1212
1216
|
/** Its numbers, `uModelParams`, packed by `packModel`. */
|
|
1213
1217
|
private readonly modelParams;
|
|
@@ -2112,6 +2116,11 @@ export declare class WebGPURenderer implements RendererApi {
|
|
|
2112
2116
|
* A material's layers where they can be drawn: none asked, or a model's map in the mask's place,
|
|
2113
2117
|
* said once, leave one layer.
|
|
2114
2118
|
*/
|
|
2119
|
+
/**
|
|
2120
|
+
* A material's detail, or null where it is refused: its slot holds another array, or it is placed
|
|
2121
|
+
* by the world. Each said once. This backend has room for its vectors wherever it runs.
|
|
2122
|
+
*/
|
|
2123
|
+
private detailOfMaterial;
|
|
2115
2124
|
private layersOf;
|
|
2116
2125
|
/** Says once that a layered material's triplanar projection is laid on the horizontal plane. */
|
|
2117
2126
|
private warnLayersTriplanar;
|
|
@@ -70,6 +70,7 @@ import { resolveTransmission } from '../../transmission.js';
|
|
|
70
70
|
import { resolveOcclusionFade } from '../../occlusionFade.js';
|
|
71
71
|
import { orthographicDepthSpan } from '../../lightMatrix.js';
|
|
72
72
|
import { LAYER_FLOATS, layerMaskMap, layeredProjection, layersFitBeside, packSurfaceLayers, } from '../../surfaceLayers.js';
|
|
73
|
+
import { DETAIL_FLOATS, detailOf, detailRefusal, detailRefusalWarning, packSurfaceDetail, } from '../../surfaceDetail.js';
|
|
73
74
|
import { LOOK_FLOATS, asksLayerLooks, packLayerLooks } from '../../surfaceLayerLooks.js';
|
|
74
75
|
import { frameReflectionsRefused, resolveFrameReflections, } from '../../frameReflections.js';
|
|
75
76
|
import { REFLECTION_SURFACE_FORMAT, ReflectionSurfacePass } from './reflectionSurfacePass.js';
|
|
@@ -2284,6 +2285,10 @@ export class WebGPURenderer {
|
|
|
2284
2285
|
*/
|
|
2285
2286
|
transmission = new Float32Array([-1, -1, -1, 0]);
|
|
2286
2287
|
warnedLayersModelMap = false;
|
|
2288
|
+
/** `uDetail`, the material's tiled detail packed by `packSurfaceDetail`. */
|
|
2289
|
+
detailFloats = new Float32Array(DETAIL_FLOATS);
|
|
2290
|
+
/** The detail refusals already said, each once. See `detailOfMaterial`. */
|
|
2291
|
+
warnedDetail = new Set();
|
|
2287
2292
|
warnedLayersTriplanar = false;
|
|
2288
2293
|
/** Its numbers, `uModelParams`, packed by `packModel`. */
|
|
2289
2294
|
modelParams = new Float32Array(MODEL_PARAM_FLOATS);
|
|
@@ -2688,7 +2693,10 @@ export class WebGPURenderer {
|
|
|
2688
2693
|
const ownModelMap = (material?.modelMap ?? null);
|
|
2689
2694
|
/* A layered material's mask, where it is a map, goes where a model's would: surfaceLayers.ts. */
|
|
2690
2695
|
const layers = this.layersOf(material);
|
|
2691
|
-
const
|
|
2696
|
+
const taken = ownModelMap ?? layerMaskMap(layers);
|
|
2697
|
+
/* And its detail's array, where nothing else is read there: surfaceDetail.ts. */
|
|
2698
|
+
const detail = this.detailOfMaterial(material, taken);
|
|
2699
|
+
const modelMap = taken ?? detail?.maps ?? null;
|
|
2692
2700
|
this.materialModel = material?.model ?? null;
|
|
2693
2701
|
const reflectivity = material?.reflectivity;
|
|
2694
2702
|
const environmentGain = material?.environmentGain;
|
|
@@ -2772,6 +2780,11 @@ export class WebGPURenderer {
|
|
|
2772
2780
|
f.set(this.lookFloats, this.materialField('uLayerLooks'));
|
|
2773
2781
|
if (asksLayerLooks(layers))
|
|
2774
2782
|
this.enableLit('LAYER_LOOKS');
|
|
2783
|
+
/* And its tiled detail, the first time a material lays any: detail.ts. */
|
|
2784
|
+
packSurfaceDetail(detail, orm !== null, this.detailFloats);
|
|
2785
|
+
f.set(this.detailFloats, this.materialField('uDetail'));
|
|
2786
|
+
if (detail !== null)
|
|
2787
|
+
this.enableLit('SURFACE_DETAIL');
|
|
2775
2788
|
f.set(this.modelParams, this.materialField('uModelParams'));
|
|
2776
2789
|
});
|
|
2777
2790
|
/*
|
|
@@ -5142,6 +5155,23 @@ export class WebGPURenderer {
|
|
|
5142
5155
|
* A material's layers where they can be drawn: none asked, or a model's map in the mask's place,
|
|
5143
5156
|
* said once, leave one layer.
|
|
5144
5157
|
*/
|
|
5158
|
+
/**
|
|
5159
|
+
* A material's detail, or null where it is refused: its slot holds another array, or it is placed
|
|
5160
|
+
* by the world. Each said once. This backend has room for its vectors wherever it runs.
|
|
5161
|
+
*/
|
|
5162
|
+
detailOfMaterial(material, taken) {
|
|
5163
|
+
const detail = detailOf(material);
|
|
5164
|
+
if (detail === null)
|
|
5165
|
+
return null;
|
|
5166
|
+
const refusal = detailRefusal(detail, taken, (material?.projection ?? null) !== null);
|
|
5167
|
+
if (refusal === null)
|
|
5168
|
+
return detail;
|
|
5169
|
+
if (!this.warnedDetail.has(refusal)) {
|
|
5170
|
+
this.warnedDetail.add(refusal);
|
|
5171
|
+
console.warn(detailRefusalWarning(refusal));
|
|
5172
|
+
}
|
|
5173
|
+
return null;
|
|
5174
|
+
}
|
|
5145
5175
|
layersOf(material) {
|
|
5146
5176
|
const layers = material?.layers ?? null;
|
|
5147
5177
|
if (layers === null || layers.repeats.length === 0)
|
|
@@ -7312,6 +7342,8 @@ export class WebGPURenderer {
|
|
|
7312
7342
|
/* Nor layers: one layer until a material asks for more. */
|
|
7313
7343
|
f.fill(0, at('uLayers'), at('uLayers') + LAYER_FLOATS);
|
|
7314
7344
|
f.fill(0, at('uLayerLooks'), at('uLayerLooks') + LOOK_FLOATS);
|
|
7345
|
+
/* Nor detail. */
|
|
7346
|
+
f.fill(0, at('uDetail'), at('uDetail') + DETAIL_FLOATS);
|
|
7315
7347
|
f[at('uCutout')] = 0;
|
|
7316
7348
|
f[at('uCutout') + 1] = 0;
|
|
7317
7349
|
this.cutoutResolveStaged = 'hard';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's tiled detail over whatever its maps made: `SurfaceMaterial.detail`, packed into
|
|
3
|
+
* `uDetail` by `surfaceDetail.ts`.
|
|
4
|
+
*
|
|
5
|
+
* **Each texel wears its own region's detail alone.** The region is read exactly from the ORM
|
|
6
|
+
* array's layer the material names, at the base coordinates, so an index is never blended with its
|
|
7
|
+
* neighbour's at a region's edge; then that region's normal map is read at its repeat and offset,
|
|
8
|
+
* turned through a frame built on the normal the base maps made — the construction a layered
|
|
9
|
+
* material lays its layers over a mesh normal with — and its occlusion multiplied into the
|
|
10
|
+
* surface's. A strength scales the detail's slopes as a layer's look does.
|
|
11
|
+
*
|
|
12
|
+
* **Read with explicit gradients**, the base coordinates' times the region's repeat, because the
|
|
13
|
+
* region is the pixel's own: a read whose level is implicit is legal only in uniform control flow
|
|
14
|
+
* (AGENTS.md 2026-08-07), and across a region's edge the repeated coordinates jump, which would
|
|
15
|
+
* read a level as coarse as the jump. The frame and the gradients are taken first, under the
|
|
16
|
+
* uniform branch that calls this.
|
|
17
|
+
*
|
|
18
|
+
* **A lit switch, `SURFACE_DETAIL`**, off until a material first asks, declared after `LAYERED` and
|
|
19
|
+
* `LAYER_LOOKS` so each keeps its id. Compiled out, its thirty-three vectors are not declared.
|
|
20
|
+
*/
|
|
21
|
+
export declare function detailGlsl(on: boolean): string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's tiled detail over whatever its maps made: `SurfaceMaterial.detail`, packed into
|
|
3
|
+
* `uDetail` by `surfaceDetail.ts`.
|
|
4
|
+
*
|
|
5
|
+
* **Each texel wears its own region's detail alone.** The region is read exactly from the ORM
|
|
6
|
+
* array's layer the material names, at the base coordinates, so an index is never blended with its
|
|
7
|
+
* neighbour's at a region's edge; then that region's normal map is read at its repeat and offset,
|
|
8
|
+
* turned through a frame built on the normal the base maps made — the construction a layered
|
|
9
|
+
* material lays its layers over a mesh normal with — and its occlusion multiplied into the
|
|
10
|
+
* surface's. A strength scales the detail's slopes as a layer's look does.
|
|
11
|
+
*
|
|
12
|
+
* **Read with explicit gradients**, the base coordinates' times the region's repeat, because the
|
|
13
|
+
* region is the pixel's own: a read whose level is implicit is legal only in uniform control flow
|
|
14
|
+
* (AGENTS.md 2026-08-07), and across a region's edge the repeated coordinates jump, which would
|
|
15
|
+
* read a level as coarse as the jump. The frame and the gradients are taken first, under the
|
|
16
|
+
* uniform branch that calls this.
|
|
17
|
+
*
|
|
18
|
+
* **A lit switch, `SURFACE_DETAIL`**, off until a material first asks, declared after `LAYERED` and
|
|
19
|
+
* `LAYER_LOOKS` so each keeps its id. Compiled out, its thirty-three vectors are not declared.
|
|
20
|
+
*/
|
|
21
|
+
export function detailGlsl(on) {
|
|
22
|
+
if (!on) {
|
|
23
|
+
return /* glsl */ `
|
|
24
|
+
const bool SURFACE_DETAIL = false; // wgsl:override
|
|
25
|
+
bool detailed() { return false; }
|
|
26
|
+
void surfaceDetail(inout vec3 n, inout float occlusion, vec2 uv) {}
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
return /* glsl */ `
|
|
30
|
+
/* Whether this program lays a material's detail. Off but where one has asked: detail.ts. */
|
|
31
|
+
const bool SURFACE_DETAIL = true; // wgsl:override
|
|
32
|
+
/*
|
|
33
|
+
* Thirty-three vectors, as surfaceDetail.ts packs them: the region count (x) and the ORM array's
|
|
34
|
+
* layer holding the region map, or -1 for none (y); then two a region, the normal map's layer (x),
|
|
35
|
+
* the occlusion's or -1 (y), the normal strength (z) and the occlusion strength (w); its repeats
|
|
36
|
+
* across and down (x y) and its offset (z w).
|
|
37
|
+
*/
|
|
38
|
+
uniform vec4 uDetail[33]; // wgsl:material
|
|
39
|
+
|
|
40
|
+
bool detailed() { return uDetail[0].x > 0.0; }
|
|
41
|
+
|
|
42
|
+
/* The region a texel of the base maps is in, read exactly, held to the regions there are. */
|
|
43
|
+
int detailRegion(vec2 uv) {
|
|
44
|
+
int last = int(uDetail[0].x) - 1;
|
|
45
|
+
if (uDetail[0].y < 0.0) return 0;
|
|
46
|
+
ivec2 size = textureSize(uOrmMap, 0).xy;
|
|
47
|
+
ivec2 texel = clamp(ivec2(floor(fract(uv) * vec2(size))), ivec2(0), size - 1);
|
|
48
|
+
int region = int(texelFetch(uOrmMap, ivec3(texel, int(uDetail[0].y)), 0).r * 255.0 + 0.5);
|
|
49
|
+
return clamp(region, 0, last);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
void surfaceDetail(inout vec3 n, inout float occlusion, vec2 uv) {
|
|
53
|
+
/* In uniform control flow, before anything that depends on the pixel's region. */
|
|
54
|
+
mat3 frame = tangentFrame(n, vWorldPos, uv, vTangent, vHasTangents);
|
|
55
|
+
vec2 dx = dFdx(uv);
|
|
56
|
+
vec2 dy = dFdy(uv);
|
|
57
|
+
int region = detailRegion(uv);
|
|
58
|
+
vec4 picks = uDetail[1 + region * 2];
|
|
59
|
+
vec4 place = uDetail[2 + region * 2];
|
|
60
|
+
vec2 at = uv * place.xy + place.zw;
|
|
61
|
+
vec2 gx = dx * place.xy;
|
|
62
|
+
vec2 gy = dy * place.xy;
|
|
63
|
+
vec3 d = textureGrad(uModelMap, vec3(at, picks.x), gx, gy).xyz * 2.0 - 1.0;
|
|
64
|
+
/* z rebuilt where none is stored, as the base map's is: normalMap.ts. */
|
|
65
|
+
d.z = d.z > 0.0 ? d.z : sqrt(max(0.0, 1.0 - dot(d.xy, d.xy)));
|
|
66
|
+
d = normalize(vec3(d.xy * picks.z, max(d.z, 1e-4)));
|
|
67
|
+
n = normalize(frame * d);
|
|
68
|
+
if (picks.y >= 0.0) {
|
|
69
|
+
float held = textureGrad(uModelMap, vec3(at, picks.y), gx, gy).r;
|
|
70
|
+
occlusion *= mix(1.0, held, picks.w);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
@@ -8,7 +8,7 @@ export declare function glassTintGlsl(glassShadows: boolean): string;
|
|
|
8
8
|
* The lit stage's switches, by the name of the constant each is: the decision both backends make
|
|
9
9
|
* about which optional code a lit program carries. See `litSwitchesGlsl`.
|
|
10
10
|
*/
|
|
11
|
-
export type LitSwitch = 'GLASS_SHADOWS' | 'CLUSTERED_LIGHTS' | 'LIGHT_FIXTURES' | 'SURFACE_EFFECTS' | 'DRIFT_LIGHT' | 'PHYSICAL_SPECULAR' | 'SURFACE_OVERLAY' | 'REFLECTION_MAPS' | 'WORLD_UVS' | 'MOVING_SUN' | 'LAYERED' | 'LAYER_LOOKS';
|
|
11
|
+
export type LitSwitch = 'GLASS_SHADOWS' | 'CLUSTERED_LIGHTS' | 'LIGHT_FIXTURES' | 'SURFACE_EFFECTS' | 'DRIFT_LIGHT' | 'PHYSICAL_SPECULAR' | 'SURFACE_OVERLAY' | 'REFLECTION_MAPS' | 'WORLD_UVS' | 'MOVING_SUN' | 'LAYERED' | 'LAYER_LOOKS' | 'SURFACE_DETAIL';
|
|
12
12
|
/** Every switch off: what a renderer starts from before its profile and its content say otherwise. */
|
|
13
13
|
export declare function noLitSwitches(): Record<LitSwitch, boolean>;
|
|
14
14
|
/** Which of the lit stage's optional features a build reads. See `litSwitchesGlsl`. */
|
|
@@ -145,6 +145,11 @@ export interface FlatShaderOptions {
|
|
|
145
145
|
* \`uLayerLooks\` and all. True when absent; nothing without \`layered\`. See \`layerLooksGlsl\`.
|
|
146
146
|
*/
|
|
147
147
|
readonly layerLooks?: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Whether it lays a material's tiled detail over its maps, \`uDetail\` and all
|
|
150
|
+
* (\`SurfaceMaterial.detail\`). True when absent. See \`detailGlsl\`.
|
|
151
|
+
*/
|
|
152
|
+
readonly surfaceDetail?: boolean;
|
|
148
153
|
/**
|
|
149
154
|
* How many point lights this build declares room for. `MAX_POINT_LIGHTS` when absent.
|
|
150
155
|
*
|
|
@@ -24,6 +24,7 @@ import { overlayGlsl } from './overlay.js';
|
|
|
24
24
|
import { reflectionSurfaceGlsl } from './reflectionSurface.js';
|
|
25
25
|
import { worldUvGlsl } from './worldUv.js';
|
|
26
26
|
import { movingSunGlsl } from './movingSun.js';
|
|
27
|
+
import { detailGlsl } from './detail.js';
|
|
27
28
|
import { layeredGlsl } from './layered.js';
|
|
28
29
|
import { MAP_READS_GLSL } from './mapReads.js';
|
|
29
30
|
import { CHANNEL_BEND, LIT_CHANNEL_ATTRIBUTE } from '../vertexChannel.js';
|
|
@@ -514,6 +515,8 @@ export function flatFrag(options) {
|
|
|
514
515
|
worldUvGlsl(options.worldUvs ?? true),
|
|
515
516
|
/* After the projection, so its switch takes the next id; the normal map below calls it. */
|
|
516
517
|
layeredGlsl(options.layered ?? true, options.layerLooks ?? true),
|
|
518
|
+
/* After the layers, so its switch takes the next id; main calls it. See detail.ts. */
|
|
519
|
+
detailGlsl(options.surfaceDetail ?? true),
|
|
517
520
|
/* Where each map is read, after both of the ways it can be read otherwise. See mapReads.ts. */
|
|
518
521
|
MAP_READS_GLSL,
|
|
519
522
|
/* After the frame and the projection it calls, unconditional for the same reason as the
|