@driftengine/core 4.13.2 → 4.13.4
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 +27 -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/clothSetPack.js +1 -0
- package/dist/render/backend/webgpu/clothSolvePack.d.ts +5 -2
- package/dist/render/backend/webgpu/clothSolvePack.js +2 -1
- package/dist/render/backend/webgpu/gpuClothSet.js +1 -1
- package/dist/render/backend/webgpu/renderer.d.ts +9 -0
- package/dist/render/backend/webgpu/renderer.js +33 -1
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +7 -5
- package/dist/render/hairLobes.d.ts +23 -0
- package/dist/render/hairLobes.js +50 -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/hair.js +51 -25
- 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 +5 -2
- package/dist/render/shaders/flat/models.js +11 -4
- 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/clothSetPack.ts +1 -0
- package/src/render/backend/webgpu/clothSolvePack.ts +6 -2
- package/src/render/backend/webgpu/gpuClothSet.ts +1 -1
- package/src/render/backend/webgpu/renderer.ts +42 -1
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +7 -5
- package/src/render/hairLobes.ts +76 -1
- package/src/render/shaders/flat/detail.ts +74 -0
- package/src/render/shaders/flat/glassTint.ts +3 -1
- package/src/render/shaders/flat/hair.ts +51 -25
- package/src/render/shaders/flat/index.ts +8 -0
- package/src/render/shaders/flat/main.ts +5 -2
- package/src/render/shaders/flat/models.ts +11 -4
- 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,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`. */
|
|
@@ -4,28 +4,34 @@
|
|
|
4
4
|
* receives, and every term to a finite answer where the geometry degenerates.
|
|
5
5
|
*
|
|
6
6
|
* **The strand is the tangent, root to tip.** The model map's green turns each strand's tilt by up
|
|
7
|
-
* to half of it either way (0.5 is none) and its blue is occlusion, which
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* to half of it either way (0.5 is none) and its blue is occlusion, which light from every side
|
|
8
|
+
* takes and the lamps do not — their shadows are theirs to cast. The red is not read: a colour from
|
|
9
|
+
* root to tip is the albedo map's to carry.
|
|
10
10
|
*
|
|
11
|
-
* **
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* **Light from every side** — the sky, the probes, DriftLight — goes through the lobes rather than
|
|
12
|
+
* past them: they are evaluated once, for a light along the eye flattened across the strand, every
|
|
13
|
+
* lobe widened by \`HAIR_AREA\` as a source that large would widen it, and their sum taken as the
|
|
14
|
+
* share of that light the strand returns (\`hairIndirect\`). That is how a real-time hair model
|
|
15
|
+
* stands one direction in for the sphere: a strand returns most toward the eye what arrives from
|
|
16
|
+
* the eye's side of it, and the R and TRT lobes are what make a fibre gold rather than a wash of
|
|
17
|
+
* its scatter. The environment's reflection leaves hair out, because R already took its share.
|
|
18
|
+
*
|
|
19
|
+
* **What it gives up**: light from behind. The direction stands for the eye's side only, so there
|
|
20
|
+
* is no transmitted lobe in it, and a head of hair lit from behind by an open sky shows no rim
|
|
21
|
+
* from the sky; the lamps carry one. And the share is one direction's, not the sphere's integral,
|
|
22
|
+
* so it moves with the eye as the lobes do rather than holding still as an average would. **What
|
|
23
|
+
* would make it wrong** is hair seen mostly against its light, a head against a bright window,
|
|
24
|
+
* whose sky arrives through the strands rather than off them and wants the transmitted lobe back.
|
|
16
25
|
*/
|
|
17
|
-
import { HAIR_F0, HAIR_MIN_BETA
|
|
26
|
+
import { HAIR_AREA, HAIR_F0, HAIR_MIN_BETA } from '../../hairLobes.js';
|
|
18
27
|
export const HAIR_GLSL = /* glsl */ `
|
|
19
28
|
const float HAIR_F0 = ${HAIR_F0.toFixed(9)};
|
|
20
29
|
const float HAIR_MIN_BETA = ${HAIR_MIN_BETA.toFixed(3)};
|
|
21
|
-
const float
|
|
30
|
+
const float HAIR_AREA = ${HAIR_AREA.toFixed(3)};
|
|
22
31
|
|
|
23
|
-
/* This texel's tilt, gathered
|
|
32
|
+
/* This texel's tilt, and the share of light from every side it returns: gathered with the surface. */
|
|
24
33
|
float hShift;
|
|
25
|
-
|
|
26
|
-
void hairSurface() {
|
|
27
|
-
hShift = uModelParams[0].x * (0.5 + mMap.g);
|
|
28
|
-
}
|
|
34
|
+
vec3 hAround;
|
|
29
35
|
|
|
30
36
|
/** A longitudinal lobe: a normalised Gaussian of width \`beta\` at \`x\` from its centre. */
|
|
31
37
|
float hairLongitudinal(float beta, float x) {
|
|
@@ -37,14 +43,18 @@ float hairFresnel(float cosine) {
|
|
|
37
43
|
return HAIR_F0 + (1.0 - HAIR_F0) * c * c * c * c * c;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
/** The normal turned toward the eye across the strand: the scatter's and
|
|
46
|
+
/** The normal turned toward the eye across the strand: the scatter's, and light from every side's. */
|
|
41
47
|
vec3 hairAcross() {
|
|
42
48
|
vec3 across = mToEye - mTangent * dot(mTangent, mToEye);
|
|
43
49
|
float length2 = dot(across, across);
|
|
44
50
|
return length2 > 1e-8 ? across * inversesqrt(length2) : mNormal;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
|
|
53
|
+
/**
|
|
54
|
+
* The lobes for light arriving along \`l\`, into \`mSpecular\` and \`mDiffuse\`: every lobe widened by
|
|
55
|
+
* \`grow\` radians, and the transmitted one scaled by \`backlit\`.
|
|
56
|
+
*/
|
|
57
|
+
void hairLobes(vec3 l, float grow, float backlit) {
|
|
48
58
|
vec3 t = mTangent;
|
|
49
59
|
float sinI = clamp(dot(t, l), -1.0, 1.0);
|
|
50
60
|
float sinO = clamp(dot(t, mToEye), -1.0, 1.0);
|
|
@@ -55,7 +65,6 @@ void hairLight(vec3 l, float sourceRadius, float dist) {
|
|
|
55
65
|
float cosPhi = dot(lp, vp) * inversesqrt(dot(lp, lp) * dot(vp, vp) + 1e-4);
|
|
56
66
|
float cosHalfPhi = sqrt(clamp(0.5 + 0.5 * cosPhi, 0.0, 1.0));
|
|
57
67
|
float beta = max(mRoughness * mRoughness, HAIR_MIN_BETA);
|
|
58
|
-
float grow = sourceRadius / max(2.0 * dist, 1e-3);
|
|
59
68
|
float x = sinI + sinO;
|
|
60
69
|
|
|
61
70
|
float r = hairLongitudinal(beta + grow, x + 2.0 * hShift) * 0.25 * cosHalfPhi
|
|
@@ -65,7 +74,7 @@ void hairLight(vec3 l, float sourceRadius, float dist) {
|
|
|
65
74
|
float h = cosHalfPhi * (1.0 + a * (0.6 - 0.8 * cosPhi));
|
|
66
75
|
float fTT = hairFresnel(cosD * sqrt(clamp(1.0 - h * h, 0.0, 1.0)));
|
|
67
76
|
float tt = hairLongitudinal(0.5 * beta + grow, x - hShift) * exp(-3.65 * cosPhi - 3.98)
|
|
68
|
-
* (1.0 - fTT) * (1.0 - fTT) *
|
|
77
|
+
* (1.0 - fTT) * (1.0 - fTT) * backlit;
|
|
69
78
|
float ttPower = 0.5 * sqrt(clamp(1.0 - h * h * a * a, 0.0, 1.0)) / cosD;
|
|
70
79
|
|
|
71
80
|
float fTRT = hairFresnel(0.5 * cosD);
|
|
@@ -80,6 +89,11 @@ void hairLight(vec3 l, float sourceRadius, float dist) {
|
|
|
80
89
|
mDiffuse = sqrt(mAlbedo) * uModelParams[0].y * mix(wrapped, 1.0 - abs(sinI), 0.33);
|
|
81
90
|
}
|
|
82
91
|
|
|
92
|
+
/** A lamp, or the sun at no size: widened by the angle it subtends, as \`sphereLobe\` takes it. */
|
|
93
|
+
void hairLight(vec3 l, float sourceRadius, float dist) {
|
|
94
|
+
hairLobes(l, sourceRadius / max(2.0 * dist, 1e-3), uModelParams[0].z);
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
/**
|
|
84
98
|
* A shadow on the scatter: light that reaches a strand in another's shadow has passed through
|
|
85
99
|
* hair to get there, so a penumbra deepens toward the fibre's own hue.
|
|
@@ -89,13 +103,25 @@ vec3 hairShade(float shadow) {
|
|
|
89
103
|
return shadow * pow(max(hue, vec3(1e-4)), vec3(1.0 - shadow));
|
|
90
104
|
}
|
|
91
105
|
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
/**
|
|
107
|
+
* The share of light from every side a strand returns: the lobes for a light along the eye
|
|
108
|
+
* flattened across the strand, unshadowed, with nothing from behind and every lobe widened by
|
|
109
|
+
* \`HAIR_AREA\`, twice their sum and at most all of it. Twice, in this engine's units, because the
|
|
110
|
+
* lobes are per unit irradiance and the scatter's half is then exactly \`HAIR_SCATTER_SPHERE\`, its
|
|
111
|
+
* own average over the sphere: \`hairLobes.ts\` says why, and holds the arithmetic to it.
|
|
112
|
+
*/
|
|
113
|
+
vec3 hairIndirect() {
|
|
114
|
+
hairLobes(hairAcross(), HAIR_AREA, 0.0);
|
|
115
|
+
return min(vec3(1.0), 2.0 * (mSpecular + mDiffuse));
|
|
95
116
|
}
|
|
96
117
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
118
|
+
void hairSurface() {
|
|
119
|
+
hShift = uModelParams[0].x * (0.5 + mMap.g);
|
|
120
|
+
hAround = hairIndirect();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Light from every side, through the lobes, under the map's occlusion. */
|
|
124
|
+
vec3 hairAround(vec3 light) {
|
|
125
|
+
return hAround * (mMap.b * light);
|
|
100
126
|
}
|
|
101
127
|
`;
|
|
@@ -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
|