@driftengine/core 4.13.1 → 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 +42 -0
- package/README.md +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/render/backend/api.d.ts +63 -8
- package/dist/render/backend/webgl2/pointSkinFeedback.d.ts +23 -0
- package/dist/render/backend/webgl2/pointSkinFeedback.js +159 -0
- package/dist/render/backend/webgl2/renderer.d.ts +33 -0
- package/dist/render/backend/webgl2/renderer.js +88 -4
- package/dist/render/backend/webgpu/buffers.d.ts +10 -2
- package/dist/render/backend/webgpu/buffers.js +66 -16
- package/dist/render/backend/webgpu/clothTextures.d.ts +6 -3
- package/dist/render/backend/webgpu/clothTextures.js +14 -11
- package/dist/render/backend/webgpu/drawCommand.js +3 -3
- package/dist/render/backend/webgpu/gpuClothSet.js +1 -0
- package/dist/render/backend/webgpu/groupShelf.d.ts +31 -0
- package/dist/render/backend/webgpu/groupShelf.js +54 -0
- package/dist/render/backend/webgpu/pointSkinPass.d.ts +27 -0
- package/dist/render/backend/webgpu/pointSkinPass.js +104 -0
- package/dist/render/backend/webgpu/renderer.d.ts +40 -7
- package/dist/render/backend/webgpu/renderer.js +151 -48
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/shaders/clothSolve.wgsl.js +7 -2
- package/dist/render/backend/webgpu/shaders/pointSkin.wgsl.d.ts +13 -0
- package/dist/render/backend/webgpu/shaders/pointSkin.wgsl.js +57 -0
- package/dist/render/backend/webgpu/vertexFormats.js +1 -1
- package/dist/render/lightBudget.d.ts +6 -0
- package/dist/render/lightBudget.js +6 -0
- package/dist/render/mesh.d.ts +19 -5
- package/dist/render/mesh.js +39 -8
- package/dist/render/pointSkin.d.ts +55 -0
- package/dist/render/pointSkin.js +121 -0
- package/dist/render/rendererHarness.js +6 -0
- package/dist/render/shader.d.ts +3 -1
- package/dist/render/shader.js +6 -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/dist/render/vertexPacking.d.ts +16 -4
- package/dist/render/vertexPacking.js +10 -3
- package/package.json +6 -6
- package/src/index.ts +4 -0
- package/src/render/backend/api.ts +70 -7
- package/src/render/backend/webgl2/pointSkinFeedback.ts +173 -0
- package/src/render/backend/webgl2/renderer.ts +121 -4
- package/src/render/backend/webgpu/buffers.ts +90 -16
- package/src/render/backend/webgpu/clothTextures.ts +14 -11
- package/src/render/backend/webgpu/drawCommand.ts +3 -3
- package/src/render/backend/webgpu/gpuClothSet.ts +1 -0
- package/src/render/backend/webgpu/groupShelf.ts +74 -0
- package/src/render/backend/webgpu/pointSkinPass.ts +115 -0
- package/src/render/backend/webgpu/renderer.ts +192 -83
- package/src/render/backend/webgpu/shaders/clothSolve.wgsl.ts +7 -2
- package/src/render/backend/webgpu/shaders/pointSkin.wgsl.ts +58 -0
- package/src/render/backend/webgpu/vertexFormats.ts +1 -1
- package/src/render/lightBudget.ts +7 -0
- package/src/render/mesh.ts +49 -7
- package/src/render/pointSkin.ts +178 -0
- package/src/render/rendererHarness.ts +6 -0
- package/src/render/shader.ts +5 -0
- 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/src/render/vertexPacking.ts +25 -5
package/CHANGELOG.json
CHANGED
|
@@ -1,4 +1,46 @@
|
|
|
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
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"version": "4.13.2",
|
|
15
|
+
"date": "2026-10-11",
|
|
16
|
+
"entries": [
|
|
17
|
+
{
|
|
18
|
+
"kind": "added",
|
|
19
|
+
"short": "A mesh stored as weighted points in its joints' own spaces is posed on the device, so the main thread hands over the joints and nothing else: `createPointSkin` and `posePointSkin`.",
|
|
20
|
+
"text": "`createPointSkin(mesh, data)` and `posePointSkin(skin, matrices)` pose a dynamic mesh on the device from a format some models are stored in: each vertex up to four points, each in the space of the joint it follows, with a weight each, and a normal in the space of its first joint (`PointSkinData`). Posed, a vertex is the weighted sum of each point moved by its joint, which ordinary skinning cannot express, since it moves one bind-pose position by a blend of joints; such a rig was posed on the main thread and its positions uploaded every frame, measured by a consumer at 38% of the main thread's drawing time. WebGPU poses in a compute pass and WebGL2 by transform feedback, writing the mesh's own positions and normals, so every pass draws it as any dynamic mesh, its shadow and its motion included, and a skinned vertex stage that has no attribute left for four more points is untouched. The joints are each joint's transform into the mesh's model space, sixteen floats a joint with no inverse bind. The mesh's bounds become a box around each joint's reach, scaled by the joint's largest axis and by the most a vertex's weights sum to, so it is culled as it moves without the posed vertices coming back to the CPU. Measured on an RX 9070 XT (`demo/dev/pointSkin.html`): the device's pose draws the picture the same pose computed on the CPU draws, to the pixel on WebGPU and within 6 pixels on WebGL2, and differs from the rest pose by 70,792; under reconstruction the two part by 743 pixels a level deep, the rounding of single against double precision, where a motion fault parts them by 43,629."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"kind": "added",
|
|
24
|
+
"short": "A mesh's colours can be rewritten in place: a mesh made `{ dynamicColors: true }` takes `updateMeshColors`, so ground shaded again every frame is no longer made again.",
|
|
25
|
+
"text": "`updateMeshColors(mesh, colors)` rewrites a mesh's colours, three linear floats a vertex, on a mesh created with `{ dynamicColors: true }`, which keeps them as floats in a buffer of their own. Nothing else moves: not the positions, not the bounds, and no motion vector. `updateMesh` rewrites positions and normals only, so a surface whose colour changes while it stays where it is, such as ground darkened with its distance from a moving camera, was made again as a new mesh nearly every frame: a consumer measured 295 of 301 frames measured, a fifth of the drawing's main-thread time. The option is independent of `dynamic`, and costs twelve bytes a vertex where sixteen-bit colours cost six or eight and one colour every vertex shares costs nothing. On WebGPU the colours are the mesh's last vertex buffer, so every other buffer stays where it was, and every pipeline that draws such a mesh, the shadow pass's included, reads them there. Measured on an RX 9070 XT (`demo/dev/meshColors.html`): colours rewritten in place draw the picture a mesh made with the same colours draws, to the pixel on both backends, drawn alone and instanced."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"kind": "added",
|
|
29
|
+
"short": "A skinned cloth can be drawn back toward its skinned shape, so styled hair keeps the shape it was made in at rest and swings out of it as the body moves: `pull`, a stiffness.",
|
|
30
|
+
"text": "`SkinnedClothParameters.pull` draws every free particle of a garment back toward where its skinning puts it, as a stiffness: metres a second squared for every metre away. Applied first among the limits, after the constraints, each substep keeping `1 / (1 + pull · h²)` of a particle's offset, which is a compliant constraint's step, so the shape it holds against gravity is one length at every step: a particle at rest under gravity sits `9.81 / pull` metres below its skinned place. The limits could not do this, since a max distance is a sphere a particle may fall anywhere inside, so styled hair pinned at its roots fell to its edge and hung over the face. **A stiffness rather than a share of the offset kept a second as `damping` is, which is what was asked for**: a share scaled by the step holds a sag proportional to it, measured on hair authored to keep 0.7 of its offset a 1/60 step at 6.36, 3.49 and 1.83 mm at 1/60, 1/120 and 1/240, where the stiffness holds 6.36 mm at all three. A share `k` kept a step `s` is `(1 / k - 1) / s²` and keeps exactly `k` at that step. The same on the device and on the CPU: with a pull, the device's solve parts from the CPU's by 0.001 mm, no more than the CPU parts from itself nudged a tenth of a micrometre a frame (`demo/dev/skinnedCloth.html?parity=1&pull=400`)."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"kind": "added",
|
|
34
|
+
"short": "An Android app can be held the way its game is played: `\"android\": { \"orientation\": \"sensorLandscape\" }` in drift.package.json, written into the activity.",
|
|
35
|
+
"text": "`drift.package.json`'s `android.orientation` is written into the activity's `android:screenOrientation`, so an app starts the right way round before any of the game's code runs, which a `lockOrientation` taken at run time cannot do. It takes every value Android documents (`landscape`, `sensorLandscape`, `portrait`, `sensorPortrait`, `userLandscape`, `fullSensor` and the rest) and refuses one it does not when the manifest is read, naming the field, since Android ignores an attribute value it does not know and the build would quietly be the phone's choice again. `unspecified`, the default, leaves the copied manifest exactly as it was. Until now the template's `unspecified` was the only answer, so a game held sideways built from a copy of the packager's templates, copied again from each release. `drift-package doctor` prints it beside the permissions and the cleartext policy."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"kind": "fixed",
|
|
39
|
+
"short": "A skinned draw that follows cloth binds the groups it made before, where WebGPU made them again nearly every frame: 24 a frame for two characters, and none now.",
|
|
40
|
+
"text": "A skinned draw's bind group was kept one a palette slot and material and made again whenever a resource in it differed from the last draw's. A garment's particles take turns between two textures, a step each, so a draw following cloth found the other texture every frame; and several meshes following one cloth bind one particle texture each with a binding of its own, so they replaced each other's group draw by draw. A consumer counted 24 groups made a frame for two characters, and none with cloth off. Each key now keeps a shelf of up to sixteen groups, each found again by the four resources it was built with, so a steady garment's draws make none; the cast shadows' groups, which missed the same way, keep a shelf a palette slot. And the views a draw of one binding binds with a set of particles are kept for every binding rather than for the last, which made two objects a draw as several meshes took turns. Measured on an RX 9070 XT (`demo/dev/skinnedCloth.html`): the garment, the garment under reconstruction and three garments stepped as one draw the picture a build that makes every group again draws, to the pixel, where binding last step's particles moves 18,321 pixels."
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
2
44
|
{
|
|
3
45
|
"version": "4.13.1",
|
|
4
46
|
"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
|
@@ -155,8 +155,9 @@ export { BLOOM_LEVELS } from './render/bloomChain.ts';
|
|
|
155
155
|
export type { BloomResponse } from './render/bloomChain.ts';
|
|
156
156
|
export { probeDevice } from './render/backend/probe.ts';
|
|
157
157
|
export type { ProbeVerdict } from './render/backend/probe.ts';
|
|
158
|
-
export type { BoltHandle, CausticsHandle, ClothBindingHandle, ClothParticlesHandle, FlockHandle, IncrementalMeshHandle, LineHandle, MeshHandle, MeshOptions, PrepareOptions, RenderBackend, RendererApi, ParticleHandle, PlumeHandle, ScatterHandle, InstancedHandle, SdfTextHandle, SurfaceTextureHandle, TextHandle, WaterHandle, WindStreakHandle, } from './render/backend/api.ts';
|
|
158
|
+
export type { BoltHandle, CausticsHandle, ClothBindingHandle, PointSkinHandle, ClothParticlesHandle, FlockHandle, IncrementalMeshHandle, LineHandle, MeshHandle, MeshOptions, PrepareOptions, RenderBackend, RendererApi, ParticleHandle, PlumeHandle, ScatterHandle, InstancedHandle, SdfTextHandle, SurfaceTextureHandle, TextHandle, WaterHandle, WindStreakHandle, } from './render/backend/api.ts';
|
|
159
159
|
export type { ClothBindingData } from './render/clothBindingData.ts';
|
|
160
|
+
export type { PointSkinData } from './render/pointSkin.ts';
|
|
160
161
|
export { anisotropicModel, eyeModel, hairModel, lightmapModel, skinModel, } from './render/surfaceModel.ts';
|
|
161
162
|
export type { AnisotropicModel, EyeModel, HairModel, LightmapModel, SkinModel, SurfaceModel, SurfaceModelKind, } from './render/surfaceModel.ts';
|
|
162
163
|
export { DEFAULT_FRAME_REFLECTIONS } from './render/frameReflections.ts';
|
|
@@ -230,6 +231,8 @@ export { SurfaceTexture } from './render/surfaceTexture.ts';
|
|
|
230
231
|
export type { SurfaceMaterial, SurfaceTextureOptions } from './render/surfaceTexture.ts';
|
|
231
232
|
export type { SurfaceProjection } from './render/surfaceProjection.ts';
|
|
232
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';
|
|
233
236
|
export type { SurfaceLayerLook } from './render/surfaceLayerLooks.ts';
|
|
234
237
|
export { compressedFormatName, isCompressedSource, levelBytes, uploadsCompressed, } from './render/compressedSource.ts';
|
|
235
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
|
|
@@ -12,6 +12,7 @@ import type { CompressedTextureSource, SurfaceSource } from '../compressedSource
|
|
|
12
12
|
import type { LightmapPage } from '../lightmap.ts';
|
|
13
13
|
import type { DeviceParticles } from '../particlePool.ts';
|
|
14
14
|
import type { ClothBindingData } from '../clothBindingData.ts';
|
|
15
|
+
import type { PointSkinData } from '../pointSkin.ts';
|
|
15
16
|
import type { TextStyle } from '../textLayout.ts';
|
|
16
17
|
import type { SdfFont } from '../sdfFont.ts';
|
|
17
18
|
import type { SdfTextStyle } from '../sdfTextLayout.ts';
|
|
@@ -49,6 +50,12 @@ export interface MeshHandle {
|
|
|
49
50
|
*/
|
|
50
51
|
export interface ClothBindingHandle {
|
|
51
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* A dynamic mesh's point skin on the device, from `createPointSkin`: its vertices as weighted points
|
|
55
|
+
* in its joints' spaces. Opaque likewise.
|
|
56
|
+
*/
|
|
57
|
+
export interface PointSkinHandle {
|
|
58
|
+
}
|
|
52
59
|
/** One character's cloth particles on the device, from `createClothParticles`. Opaque likewise. */
|
|
53
60
|
export interface ClothParticlesHandle {
|
|
54
61
|
}
|
|
@@ -190,15 +197,24 @@ export interface MeshOptions {
|
|
|
190
197
|
/**
|
|
191
198
|
* Whether this mesh's positions will be rewritten with `updateMesh`.
|
|
192
199
|
*
|
|
193
|
-
* Costs a `DYNAMIC_DRAW` hint on WebGL2 and
|
|
194
|
-
*
|
|
195
|
-
*
|
|
200
|
+
* Costs a `DYNAMIC_DRAW` hint on WebGL2, and on WebGPU two buffers of its own for the positions
|
|
201
|
+
* and the normals beside the rest of the vertex, bound with it on every draw, and floats for the
|
|
202
|
+
* normals where they might have been sixteen bits. Paid only where a consumer asks.
|
|
196
203
|
*/
|
|
197
204
|
readonly dynamic?: boolean;
|
|
205
|
+
/**
|
|
206
|
+
* Whether this mesh's colours will be rewritten with `updateMeshColors`.
|
|
207
|
+
*
|
|
208
|
+
* Costs the colours as floats in a buffer of their own, twelve bytes a vertex where they might
|
|
209
|
+
* have been sixteen bits or, for one colour every vertex shares, nothing at all. Independent of
|
|
210
|
+
* `dynamic`: a surface shaded again as a camera moves rewrites its colours every frame and its
|
|
211
|
+
* positions never, and should pay for neither the positions' buffers nor their motion.
|
|
212
|
+
*/
|
|
213
|
+
readonly dynamicColors?: boolean;
|
|
198
214
|
}
|
|
199
215
|
export type RendererApi = Omit<{
|
|
200
216
|
[K in keyof Renderer]: Renderer[K];
|
|
201
|
-
}, 'gpuTimer' | 'createMesh' | 'createMeshIncremental' | 'updateMesh' | 'drawMesh' | 'disposeMesh' | 'drawTranslucentMesh' | 'drawLightVolume' | 'drawFilm' | 'createScatter' | 'drawScatter' | 'uploadScatter' | 'disposeScatter' | 'createInstanced' | 'uploadInstanced' | 'drawInstanced' | 'drawTranslucentInstanced' | 'disposeInstanced' | 'createWater' | 'drawWater' | 'disposeWater' | 'createPlumes' | 'drawPlumes' | 'disposePlumes' | 'setPlumeScale' | 'createCaustics' | 'drawCaustics' | 'disposeCaustics' | 'createParticles' | 'drawParticles' | 'drawDeviceParticles' | 'disposeParticles' | 'createWindStreaks' | 'drawWindStreaks' | 'disposeWindStreaks' | 'createFlock' | 'drawFlock' | 'disposeFlock' | 'createBolts' | 'drawBolts' | 'disposeBolts' | 'createLines' | 'drawLines' | 'disposeLines' | 'createText' | 'setText' | 'setPlate' | 'drawText' | 'textWidth' | 'disposeText' | 'createSdfText' | 'setSdfText' | 'drawSdfText' | 'disposeSdfText' | 'createSurfaceTexture' | 'createSurfaceTextureArray' | 'createLightmap' | 'createSceneCapture' | 'captureScene' | 'updateSurfaceTexture' | 'disposeSurfaceTexture' | 'setSurfaceTexture' | 'setMaterial' | 'prepareMesh' | 'prepareInstanced' | 'setSurfaceOverlay' | 'createClothBinding' | 'createClothParticles' | 'updateClothParticles' | 'setCloth' | 'disposeClothBinding' | 'disposeClothParticles'> & {
|
|
217
|
+
}, 'gpuTimer' | 'createMesh' | 'createMeshIncremental' | 'updateMesh' | 'updateMeshColors' | 'drawMesh' | 'disposeMesh' | 'drawTranslucentMesh' | 'drawLightVolume' | 'drawFilm' | 'createScatter' | 'drawScatter' | 'uploadScatter' | 'disposeScatter' | 'createInstanced' | 'uploadInstanced' | 'drawInstanced' | 'drawTranslucentInstanced' | 'disposeInstanced' | 'createWater' | 'drawWater' | 'disposeWater' | 'createPlumes' | 'drawPlumes' | 'disposePlumes' | 'setPlumeScale' | 'createCaustics' | 'drawCaustics' | 'disposeCaustics' | 'createParticles' | 'drawParticles' | 'drawDeviceParticles' | 'disposeParticles' | 'createWindStreaks' | 'drawWindStreaks' | 'disposeWindStreaks' | 'createFlock' | 'drawFlock' | 'disposeFlock' | 'createBolts' | 'drawBolts' | 'disposeBolts' | 'createLines' | 'drawLines' | 'disposeLines' | 'createText' | 'setText' | 'setPlate' | 'drawText' | 'textWidth' | 'disposeText' | 'createSdfText' | 'setSdfText' | 'drawSdfText' | 'disposeSdfText' | 'createSurfaceTexture' | 'createSurfaceTextureArray' | 'createLightmap' | 'createSceneCapture' | 'captureScene' | 'updateSurfaceTexture' | 'disposeSurfaceTexture' | 'setSurfaceTexture' | 'setMaterial' | 'prepareMesh' | 'prepareInstanced' | 'setSurfaceOverlay' | 'createClothBinding' | 'createClothParticles' | 'updateClothParticles' | 'setCloth' | 'disposeClothBinding' | 'disposeClothParticles' | 'createPointSkin' | 'posePointSkin' | 'disposePointSkin'> & {
|
|
202
218
|
/**
|
|
203
219
|
* A mesh's cloth binding: which simulation triangle each of its vertices follows, where on it and
|
|
204
220
|
* by how much (`ClothBindingData`). The mesh must be skinned — a binding blends each vertex
|
|
@@ -222,6 +238,29 @@ export type RendererApi = Omit<{
|
|
|
222
238
|
setCloth(binding: ClothBindingHandle | null, particles?: ClothParticlesHandle | null): void;
|
|
223
239
|
disposeClothBinding(binding: ClothBindingHandle): void;
|
|
224
240
|
disposeClothParticles(particles: ClothParticlesHandle): void;
|
|
241
|
+
/**
|
|
242
|
+
* A dynamic mesh posed on the device from weighted points in its joints' own spaces
|
|
243
|
+
* (`PointSkinData`): each vertex the weighted sum of up to four points, each moved by its joint,
|
|
244
|
+
* and a normal turned by its first joint. Posed into the mesh's own positions and normals, so
|
|
245
|
+
* every pass draws it as any dynamic mesh, motion vectors included. See `pointSkin.ts`.
|
|
246
|
+
*
|
|
247
|
+
* **For a rig whose vertices are stored that way**, which ordinary skinning cannot pose: it moves
|
|
248
|
+
* one bind-pose position by a blend of joints. Such a rig was posed on the main thread and
|
|
249
|
+
* uploaded every frame, reported at 38% of the main thread's drawing time.
|
|
250
|
+
*
|
|
251
|
+
* The mesh must be `{ dynamic: true }` and have the skin's vertex count; any other is refused in
|
|
252
|
+
* words, as is a joint past 511.
|
|
253
|
+
*/
|
|
254
|
+
createPointSkin(mesh: MeshHandle, data: PointSkinData): PointSkinHandle;
|
|
255
|
+
/**
|
|
256
|
+
* Pose a skin: `matrices` holds each joint's transform into the mesh's model space, sixteen floats
|
|
257
|
+
* a joint, column-major, at least as many joints as the skin names. **Once a frame, before the
|
|
258
|
+
* mesh's draws**: a second pose in a frame reaches every draw of it on WebGPU, as a second
|
|
259
|
+
* `updateMesh` does. The mesh's bounds become a box around every joint's reach, so it is culled
|
|
260
|
+
* as it moves without the posed vertices ever coming back to the CPU.
|
|
261
|
+
*/
|
|
262
|
+
posePointSkin(skin: PointSkinHandle, matrices: Float32Array): void;
|
|
263
|
+
disposePointSkin(skin: PointSkinHandle): void;
|
|
225
264
|
/**
|
|
226
265
|
* What the following draws lay over their surfaces beyond their materials — a rim of light, a
|
|
227
266
|
* dissolve with a glowing edge, wrinkle normals by region — or null for none, which every pass
|
|
@@ -581,16 +620,32 @@ export type RendererApi = Omit<{
|
|
|
581
620
|
* quads with a matrix each, which caps the resolution at the number of draw calls a consumer
|
|
582
621
|
* can afford. A consumer found it by trying to hang a banner.
|
|
583
622
|
*
|
|
584
|
-
* **Only positions and normals.**
|
|
585
|
-
*
|
|
586
|
-
*
|
|
587
|
-
*
|
|
623
|
+
* **Only positions and normals.** Emissive and the rest describe what a surface *is* and do not
|
|
624
|
+
* change because it bent; rewriting them every frame would be uploading unchanged bytes. Colour
|
|
625
|
+
* is the exception a consumer found, a surface shaded again every frame without moving, and it
|
|
626
|
+
* has `updateMeshColors` of its own rather than a place here, so a rewrite of either sends only
|
|
627
|
+
* what changed. The vertex *count* is fixed: the index buffer, the pipeline and every other
|
|
628
|
+
* attribute are sized against it, and an update of a different size is refused naming both.
|
|
588
629
|
*
|
|
589
630
|
* The mesh must have been created with `{ dynamic: true }`. That is not ceremony — it decides
|
|
590
631
|
* the buffer hint on one backend and what has to be kept on the other, and a mesh that never
|
|
591
632
|
* deforms should pay for neither.
|
|
592
633
|
*/
|
|
593
634
|
updateMesh(mesh: MeshHandle, positions: Float32Array, normals?: Float32Array): void;
|
|
635
|
+
/**
|
|
636
|
+
* Rewrite a mesh's colours, three floats a vertex, linear as `MeshData.colors` is.
|
|
637
|
+
*
|
|
638
|
+
* **For a surface whose colour changes while it stays where it is**: ground shaded with its
|
|
639
|
+
* distance from a moving camera, a heat map, a tile lit by a game's own rules. Without it such a
|
|
640
|
+
* surface is made again as a new mesh every frame, which is a buffer allocated and let go each
|
|
641
|
+
* frame on both backends and every pipeline it draws with looked up again.
|
|
642
|
+
*
|
|
643
|
+
* The mesh must have been created with `{ dynamicColors: true }`, which gives the colours a buffer
|
|
644
|
+
* of their own; any other is refused in words. Nothing else moves: not the positions, not the
|
|
645
|
+
* bounds, and no motion vector, since a colour does not move. A different vertex count is refused
|
|
646
|
+
* naming both.
|
|
647
|
+
*/
|
|
648
|
+
updateMeshColors(mesh: MeshHandle, colors: Float32Array): void;
|
|
594
649
|
/** Draw a handle this renderer made. Passing one from the other backend is a caller error. */
|
|
595
650
|
drawMesh(mesh: MeshHandle, model: ReadonlyMat4, depthLayer?: number, tint?: Vec3 | null,
|
|
596
651
|
/**
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Mesh } from '../../mesh.ts';
|
|
2
|
+
import { type PointSkinData, type PointSkinReach } from '../../pointSkin.ts';
|
|
3
|
+
/** The program and feedback object every skin on a context poses with, made at the first skin. */
|
|
4
|
+
export interface PointSkinPose {
|
|
5
|
+
readonly program: WebGLProgram;
|
|
6
|
+
readonly feedback: WebGLTransformFeedback;
|
|
7
|
+
}
|
|
8
|
+
export declare function createPointSkinPose(gl: WebGL2RenderingContext): PointSkinPose;
|
|
9
|
+
export declare class GlPointSkin {
|
|
10
|
+
readonly mesh: Mesh;
|
|
11
|
+
readonly reach: PointSkinReach;
|
|
12
|
+
private readonly count;
|
|
13
|
+
private readonly vao;
|
|
14
|
+
private readonly buffers;
|
|
15
|
+
private readonly joints;
|
|
16
|
+
private readonly width;
|
|
17
|
+
constructor(gl: WebGL2RenderingContext, mesh: Mesh, data: PointSkinData);
|
|
18
|
+
/** One buffer, read as each `[location, size, stride, offset]` says. */
|
|
19
|
+
private attach;
|
|
20
|
+
/** Pose every vertex into the mesh's buffers now, and undo every binding it made. */
|
|
21
|
+
pose(gl: WebGL2RenderingContext, pose: PointSkinPose, matrices: Float32Array): void;
|
|
22
|
+
dispose(gl: WebGL2RenderingContext): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A point skin on WebGL2: posed by transform feedback, a vertex stage run once a vertex with the
|
|
3
|
+
* rasteriser off, its two outputs captured into the dynamic mesh's own position and normal buffers.
|
|
4
|
+
* What a point skin is, and why it poses into a mesh, is `pointSkin.ts`; the same arithmetic in the
|
|
5
|
+
* same order is `backend/webgpu/shaders/pointSkin.wgsl.ts`.
|
|
6
|
+
*
|
|
7
|
+
* **Every binding it makes it undoes**, because it runs between a frame's draws: the program, the
|
|
8
|
+
* vertex array, the joint texture on its own unit, the feedback buffers and the discard. A buffer
|
|
9
|
+
* left bound for feedback while a draw reads it as vertices is an error WebGL2 raises at the draw.
|
|
10
|
+
*/
|
|
11
|
+
import { POINT_SKIN_TEXTURE_UNIT } from '../../lightBudget.js';
|
|
12
|
+
import { reachOf } from '../../pointSkin.js';
|
|
13
|
+
import { compileProgram } from '../../shader.js';
|
|
14
|
+
const POSE_VERT = /* glsl */ `#version 300 es
|
|
15
|
+
precision highp float;
|
|
16
|
+
precision highp int;
|
|
17
|
+
layout(location = 0) in vec4 aNamed;
|
|
18
|
+
layout(location = 1) in vec4 aWeights;
|
|
19
|
+
layout(location = 2) in vec3 aPoint0;
|
|
20
|
+
layout(location = 3) in vec3 aPoint1;
|
|
21
|
+
layout(location = 4) in vec3 aPoint2;
|
|
22
|
+
layout(location = 5) in vec3 aPoint3;
|
|
23
|
+
layout(location = 6) in vec3 aNormal;
|
|
24
|
+
uniform highp sampler2D uJoints;
|
|
25
|
+
out vec3 vPosition;
|
|
26
|
+
out vec3 vNormal;
|
|
27
|
+
|
|
28
|
+
mat4 jointAt(float joint) {
|
|
29
|
+
int at = int(joint) * 4;
|
|
30
|
+
return mat4(
|
|
31
|
+
texelFetch(uJoints, ivec2(at, 0), 0),
|
|
32
|
+
texelFetch(uJoints, ivec2(at + 1, 0), 0),
|
|
33
|
+
texelFetch(uJoints, ivec2(at + 2, 0), 0),
|
|
34
|
+
texelFetch(uJoints, ivec2(at + 3, 0), 0)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
void main() {
|
|
39
|
+
vec3 p = vec3(0.0);
|
|
40
|
+
if (aWeights.x != 0.0) p += aWeights.x * (jointAt(aNamed.x) * vec4(aPoint0, 1.0)).xyz;
|
|
41
|
+
if (aWeights.y != 0.0) p += aWeights.y * (jointAt(aNamed.y) * vec4(aPoint1, 1.0)).xyz;
|
|
42
|
+
if (aWeights.z != 0.0) p += aWeights.z * (jointAt(aNamed.z) * vec4(aPoint2, 1.0)).xyz;
|
|
43
|
+
if (aWeights.w != 0.0) p += aWeights.w * (jointAt(aNamed.w) * vec4(aPoint3, 1.0)).xyz;
|
|
44
|
+
vec3 raw = mat3(jointAt(aNamed.x)) * aNormal;
|
|
45
|
+
float l = length(raw);
|
|
46
|
+
vNormal = l > 0.0 ? raw / l : raw;
|
|
47
|
+
vPosition = p;
|
|
48
|
+
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
const POSE_FRAG = /* glsl */ `#version 300 es
|
|
52
|
+
precision mediump float;
|
|
53
|
+
out vec4 colour;
|
|
54
|
+
void main() {
|
|
55
|
+
colour = vec4(0.0);
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
export function createPointSkinPose(gl) {
|
|
59
|
+
const program = compileProgram(gl, POSE_VERT, POSE_FRAG, 'pointSkin.pose', [
|
|
60
|
+
'vPosition',
|
|
61
|
+
'vNormal',
|
|
62
|
+
]);
|
|
63
|
+
gl.useProgram(program);
|
|
64
|
+
gl.uniform1i(gl.getUniformLocation(program, 'uJoints'), POINT_SKIN_TEXTURE_UNIT);
|
|
65
|
+
gl.useProgram(null);
|
|
66
|
+
const feedback = gl.createTransformFeedback();
|
|
67
|
+
if (feedback === null)
|
|
68
|
+
throw new Error('createTransformFeedback failed');
|
|
69
|
+
return { program, feedback };
|
|
70
|
+
}
|
|
71
|
+
export class GlPointSkin {
|
|
72
|
+
mesh;
|
|
73
|
+
reach;
|
|
74
|
+
count;
|
|
75
|
+
vao;
|
|
76
|
+
buffers = [];
|
|
77
|
+
joints;
|
|
78
|
+
width;
|
|
79
|
+
constructor(gl, mesh, data) {
|
|
80
|
+
this.mesh = mesh;
|
|
81
|
+
this.reach = reachOf(data);
|
|
82
|
+
this.count = data.weights.length / 4;
|
|
83
|
+
const vao = gl.createVertexArray();
|
|
84
|
+
if (vao === null)
|
|
85
|
+
throw new Error('createVertexArray failed');
|
|
86
|
+
this.vao = vao;
|
|
87
|
+
gl.bindVertexArray(vao);
|
|
88
|
+
this.attach(gl, data.joints, [[0, 4, 16, 0]]);
|
|
89
|
+
this.attach(gl, data.weights, [[1, 4, 16, 0]]);
|
|
90
|
+
this.attach(gl, data.points, [
|
|
91
|
+
[2, 3, 48, 0],
|
|
92
|
+
[3, 3, 48, 12],
|
|
93
|
+
[4, 3, 48, 24],
|
|
94
|
+
[5, 3, 48, 36],
|
|
95
|
+
]);
|
|
96
|
+
this.attach(gl, data.normals, [[6, 3, 12, 0]]);
|
|
97
|
+
gl.bindVertexArray(null);
|
|
98
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
99
|
+
/* One row, four texels a joint, as the skin palette is. */
|
|
100
|
+
this.width = Math.max(1, this.reach.joints * 4);
|
|
101
|
+
const joints = gl.createTexture();
|
|
102
|
+
if (joints === null)
|
|
103
|
+
throw new Error('createTexture failed');
|
|
104
|
+
this.joints = joints;
|
|
105
|
+
gl.activeTexture(gl.TEXTURE0 + POINT_SKIN_TEXTURE_UNIT);
|
|
106
|
+
gl.bindTexture(gl.TEXTURE_2D, joints);
|
|
107
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, this.width, 1);
|
|
108
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
109
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
110
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
111
|
+
}
|
|
112
|
+
/** One buffer, read as each `[location, size, stride, offset]` says. */
|
|
113
|
+
attach(gl, values, reads) {
|
|
114
|
+
const buffer = gl.createBuffer();
|
|
115
|
+
if (buffer === null)
|
|
116
|
+
throw new Error('createBuffer failed');
|
|
117
|
+
this.buffers.push(buffer);
|
|
118
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
119
|
+
gl.bufferData(gl.ARRAY_BUFFER, values, gl.STATIC_DRAW);
|
|
120
|
+
for (const [location, size, stride, offset] of reads) {
|
|
121
|
+
gl.enableVertexAttribArray(location);
|
|
122
|
+
gl.vertexAttribPointer(location, size, gl.FLOAT, false, stride, offset);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Pose every vertex into the mesh's buffers now, and undo every binding it made. */
|
|
126
|
+
pose(gl, pose, matrices) {
|
|
127
|
+
const streams = this.mesh.streams;
|
|
128
|
+
if (streams === null)
|
|
129
|
+
return;
|
|
130
|
+
gl.useProgram(pose.program);
|
|
131
|
+
gl.bindVertexArray(this.vao);
|
|
132
|
+
gl.activeTexture(gl.TEXTURE0 + POINT_SKIN_TEXTURE_UNIT);
|
|
133
|
+
gl.bindTexture(gl.TEXTURE_2D, this.joints);
|
|
134
|
+
if (this.reach.joints > 0) {
|
|
135
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.width, 1, gl.RGBA, gl.FLOAT, matrices, 0);
|
|
136
|
+
}
|
|
137
|
+
gl.enable(gl.RASTERIZER_DISCARD);
|
|
138
|
+
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, pose.feedback);
|
|
139
|
+
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, streams.positions);
|
|
140
|
+
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, streams.normals);
|
|
141
|
+
gl.beginTransformFeedback(gl.POINTS);
|
|
142
|
+
gl.drawArrays(gl.POINTS, 0, this.count);
|
|
143
|
+
gl.endTransformFeedback();
|
|
144
|
+
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
|
|
145
|
+
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, null);
|
|
146
|
+
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
|
|
147
|
+
gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, null);
|
|
148
|
+
gl.disable(gl.RASTERIZER_DISCARD);
|
|
149
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
150
|
+
gl.bindVertexArray(null);
|
|
151
|
+
gl.useProgram(null);
|
|
152
|
+
}
|
|
153
|
+
dispose(gl) {
|
|
154
|
+
for (const buffer of this.buffers)
|
|
155
|
+
gl.deleteBuffer(buffer);
|
|
156
|
+
gl.deleteTexture(this.joints);
|
|
157
|
+
gl.deleteVertexArray(this.vao);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -85,6 +85,8 @@ import type { FlockParams } from '../../flockRenderer.ts';
|
|
|
85
85
|
import { InstancedBatch } from './instanced.ts';
|
|
86
86
|
import type { BoneAnimationClip, BoneAnimationHandle } from '../../boneAnimation.ts';
|
|
87
87
|
import type { InstancedOptions, MeshInstances } from '../../instances.ts';
|
|
88
|
+
import { type PointSkinData } from '../../pointSkin.ts';
|
|
89
|
+
import { GlPointSkin } from './pointSkinFeedback.ts';
|
|
88
90
|
/** Reused each frame: which shader light slot each bound cubemap serves. */
|
|
89
91
|
export type { ShadowCasterSink, ShadowCasters } from '../../shadowCasters.ts';
|
|
90
92
|
/** Lighting and atmosphere for a frame. The game decides what goes in it. */
|
|
@@ -1298,6 +1300,13 @@ export declare class WebGL2Renderer implements RendererApi {
|
|
|
1298
1300
|
/** Whether it has room for the layers' looks beside them, and whether the refusal was said. */
|
|
1299
1301
|
private looksRoom;
|
|
1300
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;
|
|
1301
1310
|
private warnedLayersModelMap;
|
|
1302
1311
|
private warnedLayersTriplanar;
|
|
1303
1312
|
private warnedOverlayRoom;
|
|
@@ -1869,6 +1878,25 @@ export declare class WebGL2Renderer implements RendererApi {
|
|
|
1869
1878
|
* buffer is the same buffer. Rebinding here would cost a state change for nothing.
|
|
1870
1879
|
*/
|
|
1871
1880
|
updateMesh(mesh: Mesh, positions: Float32Array, normals?: Float32Array): void;
|
|
1881
|
+
/** The program and feedback object every point skin poses with, made at the first one. */
|
|
1882
|
+
private pointSkinPose;
|
|
1883
|
+
/**
|
|
1884
|
+
* A dynamic mesh posed on the device from weighted points in its joints' spaces, by transform
|
|
1885
|
+
* feedback. Refused in words on a mesh that is not dynamic, whose buffers are not hinted for a
|
|
1886
|
+
* rewrite every frame.
|
|
1887
|
+
*/
|
|
1888
|
+
createPointSkin(mesh: Mesh, data: PointSkinData): GlPointSkin;
|
|
1889
|
+
/**
|
|
1890
|
+
* Pose a skin now: `matrices` holds each joint's transform into the mesh's model space, sixteen
|
|
1891
|
+
* floats, column-major. A command in the stream, so the draws after it read this pose.
|
|
1892
|
+
*/
|
|
1893
|
+
posePointSkin(skin: GlPointSkin, matrices: Float32Array): void;
|
|
1894
|
+
disposePointSkin(skin: GlPointSkin): void;
|
|
1895
|
+
/**
|
|
1896
|
+
* Rewrite a mesh's colours, on a mesh made `dynamicColors`. The VAO is left alone for the reason
|
|
1897
|
+
* `updateMesh` gives: the buffer is the same buffer.
|
|
1898
|
+
*/
|
|
1899
|
+
updateMeshColors(mesh: Mesh, colors: Float32Array): void;
|
|
1872
1900
|
/**
|
|
1873
1901
|
* Compile the skinned flat program, once, on the first rigged mesh.
|
|
1874
1902
|
*
|
|
@@ -2178,6 +2206,11 @@ export declare class WebGL2Renderer implements RendererApi {
|
|
|
2178
2206
|
* as they are, said once — which on a material sharing its arrays reads the wrong textures, so
|
|
2179
2207
|
* the sentence names the way out.
|
|
2180
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;
|
|
2181
2214
|
private looksFit;
|
|
2182
2215
|
/** Says once that a layered material's triplanar projection is laid on the horizontal plane. */
|
|
2183
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';
|
|
@@ -134,6 +135,8 @@ import { CUTOUT_RESOLVE_CODE, resolveCutout } from '../../cutoutDither.js';
|
|
|
134
135
|
import { materialHasMaps, noteMapsWithoutUvs } from '../../mapsWithoutUvs.js';
|
|
135
136
|
import { VIEW_MODEL_DEPTH_SHARE, viewModelDepthRange } from '../../viewModel.js';
|
|
136
137
|
import { noLitSwitches } from '../../shaders/flat/glassTint.js';
|
|
138
|
+
import { poseBounds, validatePointSkin, validatePose, } from '../../pointSkin.js';
|
|
139
|
+
import { GlPointSkin, createPointSkinPose } from './pointSkinFeedback.js';
|
|
137
140
|
/**
|
|
138
141
|
* How many frames `firstFrameSettled` will ask before it stops asking.
|
|
139
142
|
*
|
|
@@ -974,6 +977,13 @@ export class WebGL2Renderer {
|
|
|
974
977
|
/** Whether it has room for the layers' looks beside them, and whether the refusal was said. */
|
|
975
978
|
looksRoom = null;
|
|
976
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();
|
|
977
987
|
warnedLayersModelMap = false;
|
|
978
988
|
warnedLayersTriplanar = false;
|
|
979
989
|
warnedOverlayRoom = false;
|
|
@@ -2584,7 +2594,7 @@ export class WebGL2Renderer {
|
|
|
2584
2594
|
*/
|
|
2585
2595
|
const drawn = withLightmapUvs(data);
|
|
2586
2596
|
this.warmVariants(drawn);
|
|
2587
|
-
return new Mesh(this.gl, drawn, options.dynamic === true);
|
|
2597
|
+
return new Mesh(this.gl, drawn, options.dynamic === true, false, options.dynamicColors === true);
|
|
2588
2598
|
}
|
|
2589
2599
|
/**
|
|
2590
2600
|
* Geometry in, handle out — but the geometry lands over as many frames as the caller gives it.
|
|
@@ -2598,7 +2608,7 @@ export class WebGL2Renderer {
|
|
|
2598
2608
|
createMeshIncremental(data, options = {}) {
|
|
2599
2609
|
const drawn = withLightmapUvs(data);
|
|
2600
2610
|
this.warmVariants(drawn);
|
|
2601
|
-
const { mesh, upload } = createMeshIncremental(this.gl, drawn, options.dynamic === true);
|
|
2611
|
+
const { mesh, upload } = createMeshIncremental(this.gl, drawn, options.dynamic === true, options.dynamicColors === true);
|
|
2602
2612
|
return {
|
|
2603
2613
|
mesh,
|
|
2604
2614
|
upload: {
|
|
@@ -2641,6 +2651,43 @@ export class WebGL2Renderer {
|
|
|
2641
2651
|
updateMesh(mesh, positions, normals) {
|
|
2642
2652
|
mesh.update(this.gl, positions, normals);
|
|
2643
2653
|
}
|
|
2654
|
+
/** The program and feedback object every point skin poses with, made at the first one. */
|
|
2655
|
+
pointSkinPose = null;
|
|
2656
|
+
/**
|
|
2657
|
+
* A dynamic mesh posed on the device from weighted points in its joints' spaces, by transform
|
|
2658
|
+
* feedback. Refused in words on a mesh that is not dynamic, whose buffers are not hinted for a
|
|
2659
|
+
* rewrite every frame.
|
|
2660
|
+
*/
|
|
2661
|
+
createPointSkin(mesh, data) {
|
|
2662
|
+
if (mesh.streams === null) {
|
|
2663
|
+
throw new Error('a point skin poses a mesh created with { dynamic: true }, whose positions and normals ' +
|
|
2664
|
+
'are rewritten every frame. Say so at `createMesh`.');
|
|
2665
|
+
}
|
|
2666
|
+
validatePointSkin(data, mesh.vertexCount);
|
|
2667
|
+
this.pointSkinPose ??= createPointSkinPose(this.gl);
|
|
2668
|
+
return new GlPointSkin(this.gl, mesh, data);
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* Pose a skin now: `matrices` holds each joint's transform into the mesh's model space, sixteen
|
|
2672
|
+
* floats, column-major. A command in the stream, so the draws after it read this pose.
|
|
2673
|
+
*/
|
|
2674
|
+
posePointSkin(skin, matrices) {
|
|
2675
|
+
validatePose(matrices, skin.reach);
|
|
2676
|
+
if (this.contextLost || this.pointSkinPose === null)
|
|
2677
|
+
return;
|
|
2678
|
+
poseBounds(matrices, skin.reach, skin.mesh.bounds);
|
|
2679
|
+
skin.pose(this.gl, this.pointSkinPose, matrices);
|
|
2680
|
+
}
|
|
2681
|
+
disposePointSkin(skin) {
|
|
2682
|
+
skin.dispose(this.gl);
|
|
2683
|
+
}
|
|
2684
|
+
/**
|
|
2685
|
+
* Rewrite a mesh's colours, on a mesh made `dynamicColors`. The VAO is left alone for the reason
|
|
2686
|
+
* `updateMesh` gives: the buffer is the same buffer.
|
|
2687
|
+
*/
|
|
2688
|
+
updateMeshColors(mesh, colors) {
|
|
2689
|
+
mesh.updateColors(this.gl, colors);
|
|
2690
|
+
}
|
|
2644
2691
|
/**
|
|
2645
2692
|
* Compile the skinned flat program, once, on the first rigged mesh.
|
|
2646
2693
|
*
|
|
@@ -3268,11 +3315,18 @@ export class WebGL2Renderer {
|
|
|
3268
3315
|
const modelMap = material?.modelMap ?? null;
|
|
3269
3316
|
/* A layered material's mask, where it is a map, goes where a model's would: surfaceLayers.ts. */
|
|
3270
3317
|
const layers = this.layersOf(material);
|
|
3271
|
-
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;
|
|
3272
3322
|
gl.activeTexture(gl.TEXTURE0 + MODEL_MAP_TEXTURE_UNIT);
|
|
3273
3323
|
gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.emptyTexture2DArray);
|
|
3274
3324
|
if (boundMap !== null)
|
|
3275
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);
|
|
3276
3330
|
packSurfaceLayers(layers, this.layerFloats);
|
|
3277
3331
|
if (layers !== null)
|
|
3278
3332
|
this.askLit('LAYERED');
|
|
@@ -3559,6 +3613,33 @@ export class WebGL2Renderer {
|
|
|
3559
3613
|
* as they are, said once — which on a material sharing its arrays reads the wrong textures, so
|
|
3560
3614
|
* the sentence names the way out.
|
|
3561
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
|
+
}
|
|
3562
3643
|
looksFit(layers) {
|
|
3563
3644
|
if (!asksLayerLooks(layers))
|
|
3564
3645
|
return false;
|
|
@@ -5556,6 +5637,7 @@ export class WebGL2Renderer {
|
|
|
5556
5637
|
movingSun: asIf.MOVING_SUN ?? this.litOn.MOVING_SUN,
|
|
5557
5638
|
layered: asIf.LAYERED ?? this.litOn.LAYERED,
|
|
5558
5639
|
layerLooks: asIf.LAYER_LOOKS ?? this.litOn.LAYER_LOOKS,
|
|
5640
|
+
surfaceDetail: asIf.SURFACE_DETAIL ?? this.litOn.SURFACE_DETAIL,
|
|
5559
5641
|
maxLights: budget.maxLights,
|
|
5560
5642
|
maxAreaLights: budget.maxAreaLights,
|
|
5561
5643
|
});
|
|
@@ -7415,9 +7497,10 @@ export class WebGL2Renderer {
|
|
|
7415
7497
|
this.projection.fill(0);
|
|
7416
7498
|
/* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
|
|
7417
7499
|
resolveTransmission(null, this.transmission);
|
|
7418
|
-
/* Nor layers: one layer until a material asks for more. */
|
|
7500
|
+
/* Nor layers: one layer until a material asks for more. Nor detail. */
|
|
7419
7501
|
this.layerFloats.fill(0);
|
|
7420
7502
|
this.lookFloats.fill(0);
|
|
7503
|
+
this.detailFloats.fill(0);
|
|
7421
7504
|
this.overlayMaps = null;
|
|
7422
7505
|
this.overlayTexture = null;
|
|
7423
7506
|
/* Nor an ambient of its own: the frame's, until a draw asks. See `setAmbientSH`. */
|
|
@@ -7562,6 +7645,7 @@ export class WebGL2Renderer {
|
|
|
7562
7645
|
gl.uniform4fv(u['uWorldUv'] ?? null, this.projection);
|
|
7563
7646
|
gl.uniform4fv(u['uLayers'] ?? null, this.layerFloats);
|
|
7564
7647
|
gl.uniform4fv(u['uLayerLooks'] ?? null, this.lookFloats);
|
|
7648
|
+
gl.uniform4fv(u['uDetail'] ?? null, this.detailFloats);
|
|
7565
7649
|
/* Neither refracting nor glass, which is every draw that does not say otherwise. See
|
|
7566
7650
|
`bindSeeThrough` for the four numbers in the one vector. */
|
|
7567
7651
|
gl.uniform4f(u['uSeeThrough'] ?? null, 0, 0, 0, 0);
|