@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
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's tiled detail: small normal maps, and occlusion, repeated many times over the base
|
|
3
|
+
* maps, a region map choosing which one each texel wears. `SurfaceMaterial.detail`, packed into
|
|
4
|
+
* `uDetail` for `shaders/flat/detail.ts`.
|
|
5
|
+
*
|
|
6
|
+
* **What it is for.** A surface's finest grain — leather, a weave, brushed metal, a face's pores —
|
|
7
|
+
* is a map of a few hundred texels repeated tens of times across it. Baked into the base maps it is
|
|
8
|
+
* lost: a 256-texel detail tiled forty times over a 2048-texel map would want some ten thousand
|
|
9
|
+
* texels a side. So the base maps carry the shape and a detail map the grain, each garment region
|
|
10
|
+
* with its own grain at its own repeat, which is how such surfaces are authored.
|
|
11
|
+
*
|
|
12
|
+
* **Where the maps are.** The lit stage has no texture binding to spare on either backend, so the
|
|
13
|
+
* detail maps are layers of the array read where a shading model's map is (`uModelMap`), and the
|
|
14
|
+
* region map is a layer of the material's own ORM array, one index a texel in its red. What that
|
|
15
|
+
* gives up, said where each is refused: a material whose shading model has a map must put its
|
|
16
|
+
* detail layers in that same array; a layered material whose mask is a map cannot have detail; and
|
|
17
|
+
* the region map shares the ORM array's size and format, which must keep an index exactly, so an
|
|
18
|
+
* uncompressed one.
|
|
19
|
+
*
|
|
20
|
+
* **Sixteen regions, two vectors each**, behind one vector saying how many and where the region
|
|
21
|
+
* map is: thirty-three in all, which is why WebGL2 compiles them in only when a material first asks
|
|
22
|
+
* and refuses them on a part with no room for them.
|
|
23
|
+
*/
|
|
24
|
+
/** Regions a material may name. */
|
|
25
|
+
export declare const MAX_DETAIL_REGIONS = 16;
|
|
26
|
+
/** Floats `uDetail` holds: a header vector, then two vectors a region. */
|
|
27
|
+
export declare const DETAIL_FLOATS: number;
|
|
28
|
+
/** One region's detail. */
|
|
29
|
+
export interface SurfaceDetailRegion {
|
|
30
|
+
/** The layer of the detail array holding this region's tangent-space normal map. */
|
|
31
|
+
readonly normalLayer: number;
|
|
32
|
+
/** The layer holding its occlusion, in red; absent, the region adds none. */
|
|
33
|
+
readonly occlusionLayer?: number;
|
|
34
|
+
/** Repeats across the base coordinates, the same both ways or across and down apart. */
|
|
35
|
+
readonly repeat: number | readonly [number, number];
|
|
36
|
+
/** Added to the repeated coordinates, so neighbouring regions need not line up. Default none. */
|
|
37
|
+
readonly offset?: readonly [number, number];
|
|
38
|
+
/** How far the detail turns the normal: 0 lies flat, 1 as drawn, more steeper. Default 1. */
|
|
39
|
+
readonly normalStrength?: number;
|
|
40
|
+
/** How much of its occlusion is laid on, from 0 to 1. Default 1. */
|
|
41
|
+
readonly occlusionStrength?: number;
|
|
42
|
+
}
|
|
43
|
+
/** A material's tiled detail. */
|
|
44
|
+
export interface SurfaceDetail<Texture> {
|
|
45
|
+
/**
|
|
46
|
+
* The array whose layers the regions name, read where a shading model's map is. A material with
|
|
47
|
+
* a `modelMap` puts its detail layers in that array and names it here too.
|
|
48
|
+
*/
|
|
49
|
+
readonly maps: Texture;
|
|
50
|
+
/**
|
|
51
|
+
* The layer of the material's ORM array holding the region map: in its red, each texel's region
|
|
52
|
+
* as an index from 0, stored as `index / 255`, read exactly at the base coordinates. Absent, every
|
|
53
|
+
* texel is region 0, which is a surface with one detail all over: a face's pores.
|
|
54
|
+
*/
|
|
55
|
+
readonly regionLayer?: number;
|
|
56
|
+
/** Up to sixteen, by index. A texel naming a region past the last wears the last. */
|
|
57
|
+
readonly regions: readonly SurfaceDetailRegion[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Pack a material's detail into `out`, `DETAIL_FLOATS` long; zeros for none, which the shader reads
|
|
61
|
+
* as no detail. `hasOrm` says whether the material carries the ORM array a region map is a layer
|
|
62
|
+
* of; without one every texel is region 0. Allocates nothing.
|
|
63
|
+
*/
|
|
64
|
+
export declare function packSurfaceDetail(detail: SurfaceDetail<unknown> | null, hasOrm: boolean, out: Float32Array): void;
|
|
65
|
+
/** Why a material's detail is drawn without, or null where nothing stands in its way. */
|
|
66
|
+
export type DetailRefusal = 'slot' | 'projection';
|
|
67
|
+
/**
|
|
68
|
+
* Whether a material's detail can be drawn, given what else wants the slot its maps are read
|
|
69
|
+
* through: a shading model's own map, or a layered material's mask where it is a map. Either must
|
|
70
|
+
* be the detail's array itself. And a material placed by the world has no base coordinates for a
|
|
71
|
+
* region map to be read at, so its detail is refused too.
|
|
72
|
+
*/
|
|
73
|
+
export declare function detailRefusal(detail: SurfaceDetail<unknown>, slotTakenBy: unknown, projected: boolean): DetailRefusal | null;
|
|
74
|
+
/** A material's detail, or null where it names none or no region. */
|
|
75
|
+
export declare function detailOf<Texture>(material: {
|
|
76
|
+
readonly detail?: SurfaceDetail<Texture> | null;
|
|
77
|
+
} | null): SurfaceDetail<Texture> | null;
|
|
78
|
+
/** The sentence each backend says once when it draws a material without its detail. */
|
|
79
|
+
export declare function detailRefusalWarning(refusal: DetailRefusal): string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A material's tiled detail: small normal maps, and occlusion, repeated many times over the base
|
|
3
|
+
* maps, a region map choosing which one each texel wears. `SurfaceMaterial.detail`, packed into
|
|
4
|
+
* `uDetail` for `shaders/flat/detail.ts`.
|
|
5
|
+
*
|
|
6
|
+
* **What it is for.** A surface's finest grain — leather, a weave, brushed metal, a face's pores —
|
|
7
|
+
* is a map of a few hundred texels repeated tens of times across it. Baked into the base maps it is
|
|
8
|
+
* lost: a 256-texel detail tiled forty times over a 2048-texel map would want some ten thousand
|
|
9
|
+
* texels a side. So the base maps carry the shape and a detail map the grain, each garment region
|
|
10
|
+
* with its own grain at its own repeat, which is how such surfaces are authored.
|
|
11
|
+
*
|
|
12
|
+
* **Where the maps are.** The lit stage has no texture binding to spare on either backend, so the
|
|
13
|
+
* detail maps are layers of the array read where a shading model's map is (`uModelMap`), and the
|
|
14
|
+
* region map is a layer of the material's own ORM array, one index a texel in its red. What that
|
|
15
|
+
* gives up, said where each is refused: a material whose shading model has a map must put its
|
|
16
|
+
* detail layers in that same array; a layered material whose mask is a map cannot have detail; and
|
|
17
|
+
* the region map shares the ORM array's size and format, which must keep an index exactly, so an
|
|
18
|
+
* uncompressed one.
|
|
19
|
+
*
|
|
20
|
+
* **Sixteen regions, two vectors each**, behind one vector saying how many and where the region
|
|
21
|
+
* map is: thirty-three in all, which is why WebGL2 compiles them in only when a material first asks
|
|
22
|
+
* and refuses them on a part with no room for them.
|
|
23
|
+
*/
|
|
24
|
+
/** Regions a material may name. */
|
|
25
|
+
export const MAX_DETAIL_REGIONS = 16;
|
|
26
|
+
/** Floats `uDetail` holds: a header vector, then two vectors a region. */
|
|
27
|
+
export const DETAIL_FLOATS = 4 + MAX_DETAIL_REGIONS * 8;
|
|
28
|
+
/**
|
|
29
|
+
* Pack a material's detail into `out`, `DETAIL_FLOATS` long; zeros for none, which the shader reads
|
|
30
|
+
* as no detail. `hasOrm` says whether the material carries the ORM array a region map is a layer
|
|
31
|
+
* of; without one every texel is region 0. Allocates nothing.
|
|
32
|
+
*/
|
|
33
|
+
export function packSurfaceDetail(detail, hasOrm, out) {
|
|
34
|
+
out.fill(0);
|
|
35
|
+
if (detail === null)
|
|
36
|
+
return;
|
|
37
|
+
const count = Math.min(MAX_DETAIL_REGIONS, detail.regions.length);
|
|
38
|
+
out[0] = count;
|
|
39
|
+
const regionLayer = detail.regionLayer;
|
|
40
|
+
out[1] = hasOrm && regionLayer !== undefined ? Math.max(0, Math.floor(regionLayer)) : -1;
|
|
41
|
+
for (let i = 0; i < count; i++) {
|
|
42
|
+
const region = detail.regions[i];
|
|
43
|
+
const at = 4 + i * 8;
|
|
44
|
+
const repeat = region.repeat;
|
|
45
|
+
const across = typeof repeat === 'number' ? repeat : repeat[0];
|
|
46
|
+
const down = typeof repeat === 'number' ? repeat : repeat[1];
|
|
47
|
+
out[at] = Math.max(0, Math.floor(region.normalLayer));
|
|
48
|
+
out[at + 1] =
|
|
49
|
+
region.occlusionLayer === undefined ? -1 : Math.max(0, Math.floor(region.occlusionLayer));
|
|
50
|
+
out[at + 2] = Math.max(0, region.normalStrength ?? 1);
|
|
51
|
+
out[at + 3] = Math.min(1, Math.max(0, region.occlusionStrength ?? 1));
|
|
52
|
+
/* A repeat at or below zero has no grain to show, so it is one rather than a mirror or nothing. */
|
|
53
|
+
out[at + 4] = across > 0 ? across : 1;
|
|
54
|
+
out[at + 5] = down > 0 ? down : 1;
|
|
55
|
+
out[at + 6] = region.offset?.[0] ?? 0;
|
|
56
|
+
out[at + 7] = region.offset?.[1] ?? 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Whether a material's detail can be drawn, given what else wants the slot its maps are read
|
|
61
|
+
* through: a shading model's own map, or a layered material's mask where it is a map. Either must
|
|
62
|
+
* be the detail's array itself. And a material placed by the world has no base coordinates for a
|
|
63
|
+
* region map to be read at, so its detail is refused too.
|
|
64
|
+
*/
|
|
65
|
+
export function detailRefusal(detail, slotTakenBy, projected) {
|
|
66
|
+
if (projected)
|
|
67
|
+
return 'projection';
|
|
68
|
+
if (slotTakenBy !== null && slotTakenBy !== undefined && slotTakenBy !== detail.maps) {
|
|
69
|
+
return 'slot';
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
/** A material's detail, or null where it names none or no region. */
|
|
74
|
+
export function detailOf(material) {
|
|
75
|
+
const detail = material?.detail ?? null;
|
|
76
|
+
return detail === null || detail.regions.length === 0 ? null : detail;
|
|
77
|
+
}
|
|
78
|
+
/** The sentence each backend says once when it draws a material without its detail. */
|
|
79
|
+
export function detailRefusalWarning(refusal) {
|
|
80
|
+
return refusal === 'slot'
|
|
81
|
+
? "Renderer: a material's detail is drawn without: its maps are read where a shading model's " +
|
|
82
|
+
"map goes, and this material binds another array there, its `modelMap` or its layers' " +
|
|
83
|
+
'mask. Put the detail layers in the `modelMap` array and name that array as `detail.maps`.'
|
|
84
|
+
: "Renderer: a material's detail is drawn without: it is placed by the world (`projection`), " +
|
|
85
|
+
'so there are no base coordinates for its regions and repeats to be read at.';
|
|
86
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* array samples exactly as a 2D texture does. A mesh names its layer per vertex (`MeshData.layers`),
|
|
9
9
|
* so a merged block wearing forty facades is one draw.
|
|
10
10
|
*/
|
|
11
|
+
import type { SurfaceDetail } from './surfaceDetail.ts';
|
|
11
12
|
import type { SurfaceLayers } from './surfaceLayers.ts';
|
|
12
13
|
import type { SurfaceProjection } from './surfaceProjection.ts';
|
|
13
14
|
import type { SurfaceModel } from './surfaceModel.ts';
|
|
@@ -235,6 +236,14 @@ export interface SurfaceMaterial<Texture = SurfaceTexture> {
|
|
|
235
236
|
* vertices. See `surfaceLayers.ts` for the rest and what it gives up.
|
|
236
237
|
*/
|
|
237
238
|
layers?: SurfaceLayers<Texture> | null;
|
|
239
|
+
/**
|
|
240
|
+
* Fine grain tiled over the base maps: up to sixteen regions, each a detail normal map, and an
|
|
241
|
+
* occlusion, at its own repeat and strength, a region map in the ORM array choosing which one each
|
|
242
|
+
* texel wears. The detail maps are layers of an array read where `modelMap` goes, so a material
|
|
243
|
+
* with a shading model's map puts them in that array. **A lit switch**, compiled in the first time
|
|
244
|
+
* a material asks. See `surfaceDetail.ts` for the rest and what it gives up.
|
|
245
|
+
*/
|
|
246
|
+
detail?: SurfaceDetail<Texture> | null;
|
|
238
247
|
}
|
|
239
248
|
export interface SurfaceTextureOptions {
|
|
240
249
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@driftengine/core",
|
|
3
|
-
"version": "4.13.
|
|
3
|
+
"version": "4.13.3",
|
|
4
4
|
"description": "The runtime of DriftEngine, a WebGPU game engine with a WebGL2 fallback: renderer, loop, input, cameras, scene graph and physics",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@driftengine/drft": "4.13.
|
|
33
|
-
"@driftengine/physics": "4.13.
|
|
32
|
+
"@driftengine/drft": "4.13.3",
|
|
33
|
+
"@driftengine/physics": "4.13.3",
|
|
34
34
|
"gl-matrix": "^3.4.3"
|
|
35
35
|
},
|
|
36
36
|
"author": "Drift Technologies",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"prepack": "node ../../scripts/build.mjs @driftengine/core"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@driftengine/nav": "4.13.
|
|
71
|
-
"@driftengine/network": "4.13.
|
|
72
|
-
"@driftengine/texture": "4.13.
|
|
70
|
+
"@driftengine/nav": "4.13.3",
|
|
71
|
+
"@driftengine/network": "4.13.3",
|
|
72
|
+
"@driftengine/texture": "4.13.3"
|
|
73
73
|
}
|
|
74
74
|
}
|
package/src/index.ts
CHANGED
|
@@ -677,6 +677,8 @@ export { SurfaceTexture } from './render/surfaceTexture.ts';
|
|
|
677
677
|
export type { SurfaceMaterial, SurfaceTextureOptions } from './render/surfaceTexture.ts';
|
|
678
678
|
export type { SurfaceProjection } from './render/surfaceProjection.ts';
|
|
679
679
|
export type { SurfaceLayers } from './render/surfaceLayers.ts';
|
|
680
|
+
export type { SurfaceDetail, SurfaceDetailRegion } from './render/surfaceDetail.ts';
|
|
681
|
+
export { MAX_DETAIL_REGIONS } from './render/surfaceDetail.ts';
|
|
680
682
|
export type { SurfaceLayerLook } from './render/surfaceLayerLooks.ts';
|
|
681
683
|
/*
|
|
682
684
|
* Compressed textures — BC, ETC2 and EAC, ASTC: the source `createSurfaceTexture` takes as blocks,
|
|
@@ -117,6 +117,15 @@ import {
|
|
|
117
117
|
packSurfaceLayers,
|
|
118
118
|
} from '../../surfaceLayers.ts';
|
|
119
119
|
import type { SurfaceLayers } from '../../surfaceLayers.ts';
|
|
120
|
+
import {
|
|
121
|
+
DETAIL_FLOATS,
|
|
122
|
+
detailOf,
|
|
123
|
+
detailRefusal,
|
|
124
|
+
detailRefusalWarning,
|
|
125
|
+
packSurfaceDetail,
|
|
126
|
+
type DetailRefusal,
|
|
127
|
+
type SurfaceDetail,
|
|
128
|
+
} from '../../surfaceDetail.ts';
|
|
120
129
|
import { LOOK_FLOATS, asksLayerLooks, packLayerLooks } from '../../surfaceLayerLooks.ts';
|
|
121
130
|
import type { SurfaceProjection } from '../../surfaceProjection.ts';
|
|
122
131
|
import { FILM_FRAG, FILM_VERT } from '../../shaders/film.ts';
|
|
@@ -1801,6 +1810,13 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
1801
1810
|
/** Whether it has room for the layers' looks beside them, and whether the refusal was said. */
|
|
1802
1811
|
private looksRoom: boolean | null = null;
|
|
1803
1812
|
private warnedLooksRoom = false;
|
|
1813
|
+
/** `uDetail`, the material's tiled detail packed by `packSurfaceDetail`. */
|
|
1814
|
+
private readonly detailFloats = new Float32Array(DETAIL_FLOATS);
|
|
1815
|
+
/** Whether the lit stage has room for detail on this part, asked once. See `detailOfMaterial`. */
|
|
1816
|
+
private detailRoom: boolean | null = null;
|
|
1817
|
+
private warnedDetailRoom = false;
|
|
1818
|
+
/** The refusals already said, each once. */
|
|
1819
|
+
private readonly warnedDetail = new Set<DetailRefusal>();
|
|
1804
1820
|
private warnedLayersModelMap = false;
|
|
1805
1821
|
private warnedLayersTriplanar = false;
|
|
1806
1822
|
private warnedOverlayRoom = false;
|
|
@@ -4462,10 +4478,16 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4462
4478
|
const modelMap = material?.modelMap ?? null;
|
|
4463
4479
|
/* A layered material's mask, where it is a map, goes where a model's would: surfaceLayers.ts. */
|
|
4464
4480
|
const layers = this.layersOf(material);
|
|
4465
|
-
const
|
|
4481
|
+
const taken = modelMap ?? layerMaskMap(layers);
|
|
4482
|
+
/* And its detail's array, where nothing else is read there: surfaceDetail.ts. */
|
|
4483
|
+
const detail = this.detailOfMaterial(material, taken);
|
|
4484
|
+
const boundMap = taken ?? detail?.maps ?? null;
|
|
4466
4485
|
gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
|
|
4467
4486
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
4468
4487
|
if (boundMap !== null) boundMap.bind(gl, MODEL_MAP_TEXTURE_UNIT);
|
|
4488
|
+
packSurfaceDetail(detail, (material?.orm ?? null) !== null, this.detailFloats);
|
|
4489
|
+
if (detail !== null) this.askLit('SURFACE_DETAIL');
|
|
4490
|
+
gl.uniform4fv(u['uDetail'] ?? null, this.detailFloats);
|
|
4469
4491
|
packSurfaceLayers(layers, this.layerFloats);
|
|
4470
4492
|
if (layers !== null) this.askLit('LAYERED');
|
|
4471
4493
|
gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
|
|
@@ -4771,6 +4793,37 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
4771
4793
|
* as they are, said once — which on a material sharing its arrays reads the wrong textures, so
|
|
4772
4794
|
* the sentence names the way out.
|
|
4773
4795
|
*/
|
|
4796
|
+
/**
|
|
4797
|
+
* A material's detail, or null where it is refused: its slot holds another array, it is placed by
|
|
4798
|
+
* the world, or the lit stage has no room for its thirty-three vectors. Each said once.
|
|
4799
|
+
*/
|
|
4800
|
+
private detailOfMaterial(
|
|
4801
|
+
material: SurfaceMaterial | null,
|
|
4802
|
+
taken: SurfaceTexture | null,
|
|
4803
|
+
): SurfaceDetail<SurfaceTexture> | null {
|
|
4804
|
+
const detail = detailOf(material) as SurfaceDetail<SurfaceTexture> | null;
|
|
4805
|
+
if (detail === null) return null;
|
|
4806
|
+
const refusal = detailRefusal(detail, taken, (material?.projection ?? null) !== null);
|
|
4807
|
+
if (refusal !== null) {
|
|
4808
|
+
if (!this.warnedDetail.has(refusal)) {
|
|
4809
|
+
this.warnedDetail.add(refusal);
|
|
4810
|
+
console.warn(detailRefusalWarning(refusal));
|
|
4811
|
+
}
|
|
4812
|
+
return null;
|
|
4813
|
+
}
|
|
4814
|
+
this.detailRoom ??= this.switchFits('SURFACE_DETAIL');
|
|
4815
|
+
if (this.detailRoom) return detail;
|
|
4816
|
+
if (!this.warnedDetailRoom) {
|
|
4817
|
+
this.warnedDetailRoom = true;
|
|
4818
|
+
console.warn(
|
|
4819
|
+
"Renderer: a material's detail is refused on this device. It takes thirty-three fragment " +
|
|
4820
|
+
'uniform vectors the lit shader does not have, so the material is drawn without it. A ' +
|
|
4821
|
+
'lower `maxLights` makes the room, or WebGPU has it.',
|
|
4822
|
+
);
|
|
4823
|
+
}
|
|
4824
|
+
return null;
|
|
4825
|
+
}
|
|
4826
|
+
|
|
4774
4827
|
private looksFit(layers: SurfaceLayers<SurfaceTexture> | null): boolean {
|
|
4775
4828
|
if (!asksLayerLooks(layers)) return false;
|
|
4776
4829
|
this.looksRoom ??= this.switchFits('LAYER_LOOKS', 'LAYERED');
|
|
@@ -7121,6 +7174,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
7121
7174
|
movingSun: asIf.MOVING_SUN ?? this.litOn.MOVING_SUN,
|
|
7122
7175
|
layered: asIf.LAYERED ?? this.litOn.LAYERED,
|
|
7123
7176
|
layerLooks: asIf.LAYER_LOOKS ?? this.litOn.LAYER_LOOKS,
|
|
7177
|
+
surfaceDetail: asIf.SURFACE_DETAIL ?? this.litOn.SURFACE_DETAIL,
|
|
7124
7178
|
maxLights: budget.maxLights,
|
|
7125
7179
|
maxAreaLights: budget.maxAreaLights,
|
|
7126
7180
|
});
|
|
@@ -9287,9 +9341,10 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9287
9341
|
this.projection.fill(0);
|
|
9288
9342
|
/* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
|
|
9289
9343
|
resolveTransmission(null, this.transmission);
|
|
9290
|
-
/* Nor layers: one layer until a material asks for more. */
|
|
9344
|
+
/* Nor layers: one layer until a material asks for more. Nor detail. */
|
|
9291
9345
|
this.layerFloats.fill(0);
|
|
9292
9346
|
this.lookFloats.fill(0);
|
|
9347
|
+
this.detailFloats.fill(0);
|
|
9293
9348
|
this.overlayMaps = null;
|
|
9294
9349
|
this.overlayTexture = null;
|
|
9295
9350
|
/* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
|
|
@@ -9465,6 +9520,7 @@ export class WebGL2Renderer implements RendererApi {
|
|
|
9465
9520
|
gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
|
|
9466
9521
|
gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
|
|
9467
9522
|
gl.uniform4fv(u['uLayerLooks'] ?? null, this.lookFloats);
|
|
9523
|
+
gl.uniform4fv(u['uDetail'] ?? null, this.detailFloats);
|
|
9468
9524
|
/* Neither refracting nor glass, which is every draw that does not say otherwise. See
|
|
9469
9525
|
`bindSeeThrough` for the four numbers in the one vector. */
|
|
9470
9526
|
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|
|
@@ -216,6 +216,15 @@ import {
|
|
|
216
216
|
packSurfaceLayers,
|
|
217
217
|
} from '../../surfaceLayers.ts';
|
|
218
218
|
import type { SurfaceLayers } from '../../surfaceLayers.ts';
|
|
219
|
+
import {
|
|
220
|
+
DETAIL_FLOATS,
|
|
221
|
+
detailOf,
|
|
222
|
+
detailRefusal,
|
|
223
|
+
detailRefusalWarning,
|
|
224
|
+
packSurfaceDetail,
|
|
225
|
+
type DetailRefusal,
|
|
226
|
+
type SurfaceDetail,
|
|
227
|
+
} from '../../surfaceDetail.ts';
|
|
219
228
|
import { LOOK_FLOATS, asksLayerLooks, packLayerLooks } from '../../surfaceLayerLooks.ts';
|
|
220
229
|
import {
|
|
221
230
|
frameReflectionsRefused,
|
|
@@ -3038,6 +3047,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3038
3047
|
*/
|
|
3039
3048
|
private readonly transmission = new Float32Array([-1, -1, -1, 0]);
|
|
3040
3049
|
private warnedLayersModelMap = false;
|
|
3050
|
+
/** `uDetail`, the material's tiled detail packed by `packSurfaceDetail`. */
|
|
3051
|
+
private readonly detailFloats = new Float32Array(DETAIL_FLOATS);
|
|
3052
|
+
/** The detail refusals already said, each once. See `detailOfMaterial`. */
|
|
3053
|
+
private readonly warnedDetail = new Set<DetailRefusal>();
|
|
3041
3054
|
private warnedLayersTriplanar = false;
|
|
3042
3055
|
/** Its numbers, `uModelParams`, packed by `packModel`. */
|
|
3043
3056
|
private readonly modelParams = new Float32Array(MODEL_PARAM_FLOATS);
|
|
@@ -3516,7 +3529,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3516
3529
|
const ownModelMap = (material?.modelMap ?? null) as GpuSurfaceTexture | null;
|
|
3517
3530
|
/* A layered material's mask, where it is a map, goes where a model's would: surfaceLayers.ts. */
|
|
3518
3531
|
const layers = this.layersOf(material);
|
|
3519
|
-
const
|
|
3532
|
+
const taken = ownModelMap ?? layerMaskMap(layers);
|
|
3533
|
+
/* And its detail's array, where nothing else is read there: surfaceDetail.ts. */
|
|
3534
|
+
const detail = this.detailOfMaterial(material, taken);
|
|
3535
|
+
const modelMap = taken ?? detail?.maps ?? null;
|
|
3520
3536
|
this.materialModel = material?.model ?? null;
|
|
3521
3537
|
const reflectivity = material?.reflectivity;
|
|
3522
3538
|
const environmentGain = material?.environmentGain;
|
|
@@ -3609,6 +3625,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3609
3625
|
packLayerLooks(layers, this.lookFloats);
|
|
3610
3626
|
f.set(this.lookFloats, this.materialField('uLayerLooks'));
|
|
3611
3627
|
if (asksLayerLooks(layers)) this.enableLit('LAYER_LOOKS');
|
|
3628
|
+
/* And its tiled detail, the first time a material lays any: detail.ts. */
|
|
3629
|
+
packSurfaceDetail(detail, orm !== null, this.detailFloats);
|
|
3630
|
+
f.set(this.detailFloats, this.materialField('uDetail'));
|
|
3631
|
+
if (detail !== null) this.enableLit('SURFACE_DETAIL');
|
|
3612
3632
|
f.set(this.modelParams, this.materialField('uModelParams'));
|
|
3613
3633
|
});
|
|
3614
3634
|
/*
|
|
@@ -6395,6 +6415,25 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6395
6415
|
* A material's layers where they can be drawn: none asked, or a model's map in the mask's place,
|
|
6396
6416
|
* said once, leave one layer.
|
|
6397
6417
|
*/
|
|
6418
|
+
/**
|
|
6419
|
+
* A material's detail, or null where it is refused: its slot holds another array, or it is placed
|
|
6420
|
+
* by the world. Each said once. This backend has room for its vectors wherever it runs.
|
|
6421
|
+
*/
|
|
6422
|
+
private detailOfMaterial(
|
|
6423
|
+
material: SurfaceMaterial<GpuSurfaceTexture> | null,
|
|
6424
|
+
taken: GpuSurfaceTexture | null,
|
|
6425
|
+
): SurfaceDetail<GpuSurfaceTexture> | null {
|
|
6426
|
+
const detail = detailOf(material);
|
|
6427
|
+
if (detail === null) return null;
|
|
6428
|
+
const refusal = detailRefusal(detail, taken, (material?.projection ?? null) !== null);
|
|
6429
|
+
if (refusal === null) return detail;
|
|
6430
|
+
if (!this.warnedDetail.has(refusal)) {
|
|
6431
|
+
this.warnedDetail.add(refusal);
|
|
6432
|
+
console.warn(detailRefusalWarning(refusal));
|
|
6433
|
+
}
|
|
6434
|
+
return null;
|
|
6435
|
+
}
|
|
6436
|
+
|
|
6398
6437
|
private layersOf(
|
|
6399
6438
|
material: SurfaceMaterial<GpuSurfaceTexture> | null,
|
|
6400
6439
|
): SurfaceLayers<GpuSurfaceTexture> | null {
|
|
@@ -9110,6 +9149,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9110
9149
|
/* Nor layers: one layer until a material asks for more. */
|
|
9111
9150
|
f.fill(0, at('uLayers'), at('uLayers') + LAYER_FLOATS);
|
|
9112
9151
|
f.fill(0, at('uLayerLooks'), at('uLayerLooks') + LOOK_FLOATS);
|
|
9152
|
+
/* Nor detail. */
|
|
9153
|
+
f.fill(0, at('uDetail'), at('uDetail') + DETAIL_FLOATS);
|
|
9113
9154
|
f[at('uCutout')] = 0;
|
|
9114
9155
|
f[at('uCutout') + 1] = 0;
|
|
9115
9156
|
this.cutoutResolveStaged = 'hard';
|
|
@@ -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: boolean): string {
|
|
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
|
+
}
|
|
@@ -40,7 +40,8 @@ export type LitSwitch =
|
|
|
40
40
|
| 'WORLD_UVS'
|
|
41
41
|
| 'MOVING_SUN'
|
|
42
42
|
| 'LAYERED'
|
|
43
|
-
| 'LAYER_LOOKS'
|
|
43
|
+
| 'LAYER_LOOKS'
|
|
44
|
+
| 'SURFACE_DETAIL';
|
|
44
45
|
|
|
45
46
|
/** Every switch off: what a renderer starts from before its profile and its content say otherwise. */
|
|
46
47
|
export function noLitSwitches(): Record<LitSwitch, boolean> {
|
|
@@ -57,6 +58,7 @@ export function noLitSwitches(): Record<LitSwitch, boolean> {
|
|
|
57
58
|
MOVING_SUN: false,
|
|
58
59
|
LAYERED: false,
|
|
59
60
|
LAYER_LOOKS: false,
|
|
61
|
+
SURFACE_DETAIL: false,
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -24,6 +24,7 @@ import { overlayGlsl } from './overlay.ts';
|
|
|
24
24
|
import { reflectionSurfaceGlsl } from './reflectionSurface.ts';
|
|
25
25
|
import { worldUvGlsl } from './worldUv.ts';
|
|
26
26
|
import { movingSunGlsl } from './movingSun.ts';
|
|
27
|
+
import { detailGlsl } from './detail.ts';
|
|
27
28
|
import { layeredGlsl } from './layered.ts';
|
|
28
29
|
import { MAP_READS_GLSL } from './mapReads.ts';
|
|
29
30
|
import { CHANNEL_BEND, LIT_CHANNEL_ATTRIBUTE } from '../vertexChannel.ts';
|
|
@@ -539,6 +540,11 @@ export interface FlatShaderOptions {
|
|
|
539
540
|
* \`uLayerLooks\` and all. True when absent; nothing without \`layered\`. See \`layerLooksGlsl\`.
|
|
540
541
|
*/
|
|
541
542
|
readonly layerLooks?: boolean;
|
|
543
|
+
/**
|
|
544
|
+
* Whether it lays a material's tiled detail over its maps, \`uDetail\` and all
|
|
545
|
+
* (\`SurfaceMaterial.detail\`). True when absent. See \`detailGlsl\`.
|
|
546
|
+
*/
|
|
547
|
+
readonly surfaceDetail?: boolean;
|
|
542
548
|
/**
|
|
543
549
|
* How many point lights this build declares room for. `MAX_POINT_LIGHTS` when absent.
|
|
544
550
|
*
|
|
@@ -692,6 +698,8 @@ export function flatFrag(options: FlatShaderOptions): string {
|
|
|
692
698
|
worldUvGlsl(options.worldUvs ?? true),
|
|
693
699
|
/* After the projection, so its switch takes the next id; the normal map below calls it. */
|
|
694
700
|
layeredGlsl(options.layered ?? true, options.layerLooks ?? true),
|
|
701
|
+
/* After the layers, so its switch takes the next id; main calls it. See detail.ts. */
|
|
702
|
+
detailGlsl(options.surfaceDetail ?? true),
|
|
695
703
|
/* Where each map is read, after both of the ways it can be read otherwise. See mapReads.ts. */
|
|
696
704
|
MAP_READS_GLSL,
|
|
697
705
|
/* After the frame and the projection it calls, unconditional for the same reason as the
|
|
@@ -227,6 +227,8 @@ export const MAIN_GLSL = `void main() {
|
|
|
227
227
|
if (uNormalStrength > 0.0) n = normalMapped(n, surfaceAt, frameAt, frameTangents);
|
|
228
228
|
/* A draw's wrinkles, by region, over whatever the map made: overlay.ts. */
|
|
229
229
|
if (SURFACE_OVERLAY) n = overlayWrinkle(n, unmapped, surfaceAt.xy);
|
|
230
|
+
/* The material's tiled detail over whatever the maps made, and its occlusion: detail.ts. */
|
|
231
|
+
if (SURFACE_DETAIL && detailed()) surfaceDetail(n, ormOcclusion, surfaceAt.xy);
|
|
230
232
|
|
|
231
233
|
/*
|
|
232
234
|
* Microscopic relief: the surface's own texture, as a turn of the normal rather than a change
|