@opsydyn/effect-three 0.0.0-stage → 0.1.0
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.md +58 -0
- package/LICENSE +21 -0
- package/README.md +191 -2
- package/dist/index-4045wme0.js +129 -0
- package/dist/index-4045wme0.js.map +10 -0
- package/dist/index-ef8pbv9g.js +5118 -0
- package/dist/index-ef8pbv9g.js.map +40 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1361 -0
- package/dist/index.js.map +14 -0
- package/dist/kernel/animation.d.ts +14 -0
- package/dist/kernel/animation.d.ts.map +1 -0
- package/dist/kernel/behaviours.d.ts +16 -0
- package/dist/kernel/behaviours.d.ts.map +1 -0
- package/dist/kernel/bodies.d.ts +60 -0
- package/dist/kernel/bodies.d.ts.map +1 -0
- package/dist/kernel/bodySources.d.ts +47 -0
- package/dist/kernel/bodySources.d.ts.map +1 -0
- package/dist/kernel/build.d.ts +564 -0
- package/dist/kernel/build.d.ts.map +1 -0
- package/dist/kernel/cameraMotion.d.ts +39 -0
- package/dist/kernel/cameraMotion.d.ts.map +1 -0
- package/dist/kernel/clock.d.ts +17 -0
- package/dist/kernel/clock.d.ts.map +1 -0
- package/dist/kernel/contactShadowPasses.d.ts +47 -0
- package/dist/kernel/contactShadowPasses.d.ts.map +1 -0
- package/dist/kernel/description.d.ts +1120 -0
- package/dist/kernel/description.d.ts.map +1 -0
- package/dist/kernel/diff.d.ts +90 -0
- package/dist/kernel/diff.d.ts.map +1 -0
- package/dist/kernel/environmentPresets.d.ts +30 -0
- package/dist/kernel/environmentPresets.d.ts.map +1 -0
- package/dist/kernel/equality.d.ts +64 -0
- package/dist/kernel/equality.d.ts.map +1 -0
- package/dist/kernel/frames.d.ts +55 -0
- package/dist/kernel/frames.d.ts.map +1 -0
- package/dist/kernel/index.d.ts +37 -0
- package/dist/kernel/index.d.ts.map +1 -0
- package/dist/kernel/index.js +335 -0
- package/dist/kernel/index.js.map +9 -0
- package/dist/kernel/instances.d.ts +26 -0
- package/dist/kernel/instances.d.ts.map +1 -0
- package/dist/kernel/labels.d.ts +45 -0
- package/dist/kernel/labels.d.ts.map +1 -0
- package/dist/kernel/loop.d.ts +49 -0
- package/dist/kernel/loop.d.ts.map +1 -0
- package/dist/kernel/materials.d.ts +24 -0
- package/dist/kernel/materials.d.ts.map +1 -0
- package/dist/kernel/motion.d.ts +36 -0
- package/dist/kernel/motion.d.ts.map +1 -0
- package/dist/kernel/orbit.d.ts +127 -0
- package/dist/kernel/orbit.d.ts.map +1 -0
- package/dist/kernel/overlays.d.ts +16 -0
- package/dist/kernel/overlays.d.ts.map +1 -0
- package/dist/kernel/performance.d.ts +41 -0
- package/dist/kernel/performance.d.ts.map +1 -0
- package/dist/kernel/physics.d.ts +166 -0
- package/dist/kernel/physics.d.ts.map +1 -0
- package/dist/kernel/physicsDebug.d.ts +18 -0
- package/dist/kernel/physicsDebug.d.ts.map +1 -0
- package/dist/kernel/pointer.d.ts +357 -0
- package/dist/kernel/pointer.d.ts.map +1 -0
- package/dist/kernel/renderKit.d.ts +48 -0
- package/dist/kernel/renderKit.d.ts.map +1 -0
- package/dist/kernel/renderer.d.ts +83 -0
- package/dist/kernel/renderer.d.ts.map +1 -0
- package/dist/kernel/resources.d.ts +112 -0
- package/dist/kernel/resources.d.ts.map +1 -0
- package/dist/kernel/sceneGraph.d.ts +155 -0
- package/dist/kernel/sceneGraph.d.ts.map +1 -0
- package/dist/kernel/sceneIndex.d.ts +90 -0
- package/dist/kernel/sceneIndex.d.ts.map +1 -0
- package/dist/kernel/stage.d.ts +87 -0
- package/dist/kernel/stage.d.ts.map +1 -0
- package/dist/kernel/textLayout.d.ts +29 -0
- package/dist/kernel/textLayout.d.ts.map +1 -0
- package/dist/kernel/tween.d.ts +35 -0
- package/dist/kernel/tween.d.ts.map +1 -0
- package/dist/kernel/xrInput.d.ts +107 -0
- package/dist/kernel/xrInput.d.ts.map +1 -0
- package/dist/kernel/xrLocomotion.d.ts +38 -0
- package/dist/kernel/xrLocomotion.d.ts.map +1 -0
- package/dist/runtime/FrameSource.d.ts +29 -0
- package/dist/runtime/FrameSource.d.ts.map +1 -0
- package/dist/runtime/ResourceRegistry.d.ts +18 -0
- package/dist/runtime/ResourceRegistry.d.ts.map +1 -0
- package/dist/runtime/SceneRuntime.d.ts +68 -0
- package/dist/runtime/SceneRuntime.d.ts.map +1 -0
- package/dist/runtime/SceneSchema.d.ts +2536 -0
- package/dist/runtime/SceneSchema.d.ts.map +1 -0
- package/dist/runtime/errors.d.ts +19 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +11 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/webgl/contactShadows.d.ts +5 -0
- package/dist/webgl/contactShadows.d.ts.map +1 -0
- package/dist/webgl/index.d.ts +11 -0
- package/dist/webgl/index.d.ts.map +1 -0
- package/dist/webgl/index.js +239 -0
- package/dist/webgl/index.js.map +13 -0
- package/dist/webgl/shaderGrid.d.ts +27 -0
- package/dist/webgl/shaderGrid.d.ts.map +1 -0
- package/dist/webgl/sky.d.ts +4 -0
- package/dist/webgl/sky.d.ts.map +1 -0
- package/dist/webgpu/contactShadows.d.ts +5 -0
- package/dist/webgpu/contactShadows.d.ts.map +1 -0
- package/dist/webgpu/index.d.ts +10 -0
- package/dist/webgpu/index.d.ts.map +1 -0
- package/dist/webgpu/index.js +216 -0
- package/dist/webgpu/index.js.map +13 -0
- package/dist/webgpu/shaderGrid.d.ts +5 -0
- package/dist/webgpu/shaderGrid.d.ts.map +1 -0
- package/dist/webgpu/sky.d.ts +4 -0
- package/dist/webgpu/sky.d.ts.map +1 -0
- package/package.json +82 -4
- package/src/index.ts +11 -0
- package/src/kernel/animation.ts +24 -0
- package/src/kernel/behaviours.ts +36 -0
- package/src/kernel/bodies.ts +338 -0
- package/src/kernel/bodySources.ts +203 -0
- package/src/kernel/build.ts +1325 -0
- package/src/kernel/cameraMotion.ts +202 -0
- package/src/kernel/clock.ts +35 -0
- package/src/kernel/contactShadowPasses.ts +207 -0
- package/src/kernel/description.ts +1197 -0
- package/src/kernel/diff.ts +383 -0
- package/src/kernel/environmentPresets.ts +38 -0
- package/src/kernel/equality.ts +604 -0
- package/src/kernel/frames.ts +124 -0
- package/src/kernel/index.ts +274 -0
- package/src/kernel/instances.ts +97 -0
- package/src/kernel/labels.ts +48 -0
- package/src/kernel/loop.ts +248 -0
- package/src/kernel/materials.ts +258 -0
- package/src/kernel/motion.ts +75 -0
- package/src/kernel/orbit.ts +554 -0
- package/src/kernel/overlays.ts +130 -0
- package/src/kernel/performance.ts +101 -0
- package/src/kernel/physics.ts +361 -0
- package/src/kernel/physicsDebug.ts +68 -0
- package/src/kernel/pointer.ts +785 -0
- package/src/kernel/renderKit.ts +53 -0
- package/src/kernel/renderer.ts +136 -0
- package/src/kernel/resources.ts +504 -0
- package/src/kernel/sceneGraph.ts +1440 -0
- package/src/kernel/sceneIndex.ts +219 -0
- package/src/kernel/stage.ts +157 -0
- package/src/kernel/textLayout.ts +102 -0
- package/src/kernel/tween.ts +80 -0
- package/src/kernel/xrInput.ts +230 -0
- package/src/kernel/xrLocomotion.ts +72 -0
- package/src/runtime/FrameSource.ts +41 -0
- package/src/runtime/ResourceRegistry.ts +24 -0
- package/src/runtime/SceneRuntime.ts +229 -0
- package/src/runtime/SceneSchema.ts +983 -0
- package/src/runtime/errors.ts +18 -0
- package/src/runtime/index.ts +10 -0
- package/src/webgl/contactShadows.ts +67 -0
- package/src/webgl/index.ts +19 -0
- package/src/webgl/shaderGrid.ts +196 -0
- package/src/webgl/sky.ts +42 -0
- package/src/webgpu/contactShadows.ts +83 -0
- package/src/webgpu/index.ts +19 -0
- package/src/webgpu/shaderGrid.ts +158 -0
- package/src/webgpu/sky.ts +35 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed failures of the scene runtime.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { Data } from 'effect';
|
|
7
|
+
|
|
8
|
+
import type { DescriptionError } from '../kernel/index.ts';
|
|
9
|
+
|
|
10
|
+
/** A scene description was rejected. Nothing was applied. */
|
|
11
|
+
export class InvalidScene extends Data.TaggedError('InvalidScene')<{
|
|
12
|
+
readonly error: DescriptionError;
|
|
13
|
+
}> {}
|
|
14
|
+
|
|
15
|
+
/** The renderer backend could not be created (for example, no WebGL2 context). */
|
|
16
|
+
export class RendererUnavailable extends Data.TaggedError('RendererUnavailable')<{
|
|
17
|
+
readonly cause: unknown;
|
|
18
|
+
}> {}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Effect runtime: lifetimes, services and typed errors around the kernel.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
export { InvalidScene, RendererUnavailable } from './errors.ts';
|
|
7
|
+
export * as FrameSource from './FrameSource.ts';
|
|
8
|
+
export * as ResourceRegistry from './ResourceRegistry.ts';
|
|
9
|
+
export * as SceneRuntime from './SceneRuntime.ts';
|
|
10
|
+
export * as SceneSchema from './SceneSchema.ts';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WebGL kit's contact shadow materials: three's depth material with its output
|
|
3
|
+
* replaced by the shadow (as Drei's `ContactShadows`, `bf6f4ad`), and three's horizontal
|
|
4
|
+
* and vertical blur shaders. The passes themselves are the kernel's.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { type Color, MeshDepthMaterial, ShaderMaterial } from 'three';
|
|
9
|
+
import { HorizontalBlurShader } from 'three/addons/shaders/HorizontalBlurShader.js';
|
|
10
|
+
import { VerticalBlurShader } from 'three/addons/shaders/VerticalBlurShader.js';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
type BlurPass,
|
|
14
|
+
createContactShadowPasses,
|
|
15
|
+
type ShadowMaterials,
|
|
16
|
+
} from '../kernel/contactShadowPasses.ts';
|
|
17
|
+
import type { SceneRenderer } from '../kernel/renderer.ts';
|
|
18
|
+
import type { ContactShadowPasses, ShadowedScene } from '../kernel/renderKit.ts';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The shadow pass's edit to three's depth shader: its basic-packing output, and the
|
|
22
|
+
* shadow written instead (darkest where an object touches the plane).
|
|
23
|
+
*/
|
|
24
|
+
const shadowShader = {
|
|
25
|
+
depthOutput: 'gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );',
|
|
26
|
+
shadowOutput: 'gl_FragColor = vec4( shadowColor * fragCoordZ * 2.0, 1.0 - fragCoordZ );',
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
const depth = (color: Color): MeshDepthMaterial => {
|
|
30
|
+
const material = new MeshDepthMaterial();
|
|
31
|
+
material.depthTest = false;
|
|
32
|
+
material.depthWrite = false;
|
|
33
|
+
material.onBeforeCompile = (shader) => {
|
|
34
|
+
shader.uniforms['shadowColor'] = { value: color };
|
|
35
|
+
shader.fragmentShader = `uniform vec3 shadowColor;\n${shader.fragmentShader.replace(
|
|
36
|
+
shadowShader.depthOutput,
|
|
37
|
+
shadowShader.shadowOutput,
|
|
38
|
+
)}`;
|
|
39
|
+
};
|
|
40
|
+
return material;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** three's blur shaders: `h` (horizontal) or `v` (vertical) is the step, `tDiffuse` the source. */
|
|
44
|
+
const blurShaders = {
|
|
45
|
+
horizontal: { shader: HorizontalBlurShader, step: 'h' },
|
|
46
|
+
vertical: { shader: VerticalBlurShader, step: 'v' },
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
const blur = (direction: 'horizontal' | 'vertical'): BlurPass => {
|
|
50
|
+
const { shader, step } = blurShaders[direction];
|
|
51
|
+
const material = new ShaderMaterial({ ...shader, depthTest: false });
|
|
52
|
+
return {
|
|
53
|
+
material,
|
|
54
|
+
set: (source, amount) => {
|
|
55
|
+
Reflect.set(material.uniforms['tDiffuse'] ?? {}, 'value', source);
|
|
56
|
+
Reflect.set(material.uniforms[step] ?? {}, 'value', amount);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const webglShadowMaterials: ShadowMaterials = { depth, blur, flippedTargets: false };
|
|
62
|
+
|
|
63
|
+
/** Creates the contact shadow passes for one scene graph and renderer, in GLSL. */
|
|
64
|
+
export const createContactShadows = (
|
|
65
|
+
graph: ShadowedScene,
|
|
66
|
+
renderer: SceneRenderer,
|
|
67
|
+
): ContactShadowPasses => createContactShadowPasses(graph, renderer, webglShadowMaterials);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WebGL render kit: the scene parts written in GLSL (the sky, the shader grid,
|
|
3
|
+
* contact shadows), for renderers drawing with three.js's `WebGLRenderer`. The Foldkit
|
|
4
|
+
* adapter's `webglRendererFactory` carries it; pass it to `createSceneGraph({ kit })`
|
|
5
|
+
* when using the kernel directly.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { RenderKit } from '../kernel/renderKit.ts';
|
|
10
|
+
import { createContactShadows } from './contactShadows.ts';
|
|
11
|
+
import { hostShaderGrid } from './shaderGrid.ts';
|
|
12
|
+
import { hostSky } from './sky.ts';
|
|
13
|
+
|
|
14
|
+
export const webglKit: RenderKit = {
|
|
15
|
+
drawsLabels: true,
|
|
16
|
+
sky: hostSky,
|
|
17
|
+
shaderGrid: hostShaderGrid,
|
|
18
|
+
contactShadows: createContactShadows,
|
|
19
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drei's `Grid` (`src/core/Grid.tsx` at `bf6f4ad`, MIT; itself after Fyrestar's
|
|
3
|
+
* `THREE.InfiniteGridHelper` and threlte's `Grid`): a plane whose shader draws thin cell
|
|
4
|
+
* lines and thicker section lines from screen-space derivatives, fading with distance
|
|
5
|
+
* from the camera's foot on the plane. An infinite grid stretches the plane by the fade
|
|
6
|
+
* distance; one that follows the camera slides under it.
|
|
7
|
+
*
|
|
8
|
+
* Each frame the material needs the camera drawing it projected onto the grid's plane.
|
|
9
|
+
* Drei sets that in `useFrame`; here `onBeforeRender` does it, so every view (and each
|
|
10
|
+
* eye in XR) gets its own camera.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import {
|
|
15
|
+
BackSide,
|
|
16
|
+
Color,
|
|
17
|
+
DoubleSide,
|
|
18
|
+
FrontSide,
|
|
19
|
+
Mesh,
|
|
20
|
+
Plane,
|
|
21
|
+
PlaneGeometry,
|
|
22
|
+
ShaderMaterial,
|
|
23
|
+
type Side,
|
|
24
|
+
Vector3,
|
|
25
|
+
} from 'three';
|
|
26
|
+
|
|
27
|
+
import type { MaterialSide, ShaderGridNode } from '../kernel/description.ts';
|
|
28
|
+
import type { HostedShaderGrid } from '../kernel/renderKit.ts';
|
|
29
|
+
|
|
30
|
+
/** The mesh a shader grid is hosted as. */
|
|
31
|
+
export type HostShaderGrid = Mesh<PlaneGeometry, ShaderMaterial>;
|
|
32
|
+
|
|
33
|
+
const sides = { front: FrontSide, back: BackSide, double: DoubleSide } satisfies Record<
|
|
34
|
+
MaterialSide,
|
|
35
|
+
Side
|
|
36
|
+
>;
|
|
37
|
+
|
|
38
|
+
const vertexShader = /* glsl */ `
|
|
39
|
+
uniform vec3 cameraOnPlane;
|
|
40
|
+
uniform vec3 planeOrigin;
|
|
41
|
+
uniform float fadeDistance;
|
|
42
|
+
uniform bool infiniteGrid;
|
|
43
|
+
uniform bool followCamera;
|
|
44
|
+
|
|
45
|
+
varying vec3 gridPosition;
|
|
46
|
+
varying vec4 worldPosition;
|
|
47
|
+
|
|
48
|
+
void main() {
|
|
49
|
+
// The plane geometry lies in XY; the grid lies in XZ.
|
|
50
|
+
gridPosition = position.xzy;
|
|
51
|
+
if (infiniteGrid) gridPosition *= 1.0 + fadeDistance;
|
|
52
|
+
worldPosition = modelMatrix * vec4(gridPosition, 1.0);
|
|
53
|
+
if (followCamera) {
|
|
54
|
+
worldPosition.xyz += cameraOnPlane - planeOrigin;
|
|
55
|
+
gridPosition = (inverse(modelMatrix) * worldPosition).xyz;
|
|
56
|
+
}
|
|
57
|
+
gl_Position = projectionMatrix * viewMatrix * worldPosition;
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
const fragmentShader = /* glsl */ `
|
|
62
|
+
uniform vec3 cameraOnPlane;
|
|
63
|
+
uniform float cellSize;
|
|
64
|
+
uniform float cellThickness;
|
|
65
|
+
uniform vec3 cellColor;
|
|
66
|
+
uniform float sectionSize;
|
|
67
|
+
uniform float sectionThickness;
|
|
68
|
+
uniform vec3 sectionColor;
|
|
69
|
+
uniform float fadeDistance;
|
|
70
|
+
uniform float fadeStrength;
|
|
71
|
+
uniform float fadeFrom;
|
|
72
|
+
|
|
73
|
+
varying vec3 gridPosition;
|
|
74
|
+
varying vec4 worldPosition;
|
|
75
|
+
|
|
76
|
+
// 1 on a line of a grid with this spacing, 0 between, antialiased over a pixel.
|
|
77
|
+
float lines(float spacing, float thickness) {
|
|
78
|
+
vec2 at = gridPosition.xz / spacing;
|
|
79
|
+
vec2 away = abs(fract(at - 0.5) - 0.5) / fwidth(at);
|
|
80
|
+
return 1.0 - min(min(away.x, away.y) + 1.0 - thickness, 1.0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void main() {
|
|
84
|
+
float cell = lines(cellSize, cellThickness);
|
|
85
|
+
float section = lines(sectionSize, sectionThickness);
|
|
86
|
+
float distanceAway = distance(cameraOnPlane * fadeFrom, worldPosition.xyz);
|
|
87
|
+
float fade = pow(1.0 - min(distanceAway / fadeDistance, 1.0), fadeStrength);
|
|
88
|
+
vec3 color = mix(cellColor, sectionColor, min(1.0, sectionThickness * section));
|
|
89
|
+
float alpha = (cell + section) * fade;
|
|
90
|
+
alpha = mix(0.75 * alpha, alpha, section);
|
|
91
|
+
if (alpha <= 0.0) discard;
|
|
92
|
+
gl_FragColor = vec4(color, alpha);
|
|
93
|
+
#include <tonemapping_fragment>
|
|
94
|
+
#include <colorspace_fragment>
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
const up = new Vector3(0, 1, 0);
|
|
99
|
+
const origin = new Vector3();
|
|
100
|
+
const plane = new Plane();
|
|
101
|
+
const cameraPosition = new Vector3();
|
|
102
|
+
|
|
103
|
+
/** Writes a node's settings into its material's uniforms. */
|
|
104
|
+
const writeUniforms = (material: ShaderMaterial, node: ShaderGridNode): void => {
|
|
105
|
+
const { uniforms } = material;
|
|
106
|
+
const set = (name: string, value: unknown): void => {
|
|
107
|
+
const uniform = uniforms[name];
|
|
108
|
+
if (uniform !== undefined) uniform.value = value;
|
|
109
|
+
};
|
|
110
|
+
set('cellSize', node.cellSize);
|
|
111
|
+
set('cellThickness', node.cellThickness);
|
|
112
|
+
set('sectionSize', node.sectionSize);
|
|
113
|
+
set('sectionThickness', node.sectionThickness);
|
|
114
|
+
set('fadeDistance', node.fadeDistance);
|
|
115
|
+
set('fadeStrength', node.fadeStrength);
|
|
116
|
+
set('fadeFrom', node.fadeFrom);
|
|
117
|
+
set('infiniteGrid', node.infinite);
|
|
118
|
+
set('followCamera', node.followCamera);
|
|
119
|
+
uniforms['cellColor']?.value.setHex(node.cellColor);
|
|
120
|
+
uniforms['sectionColor']?.value.setHex(node.sectionColor);
|
|
121
|
+
material.side = sides[node.side];
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** Places the grid and its plane from its description. */
|
|
125
|
+
const writePlacement = (mesh: HostShaderGrid, node: ShaderGridNode): void => {
|
|
126
|
+
mesh.position.fromArray(node.position);
|
|
127
|
+
mesh.rotation.fromArray([...node.rotation]);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/** Creates the grid mesh for a node. */
|
|
131
|
+
export const createShaderGrid = (node: ShaderGridNode): HostShaderGrid => {
|
|
132
|
+
const material = new ShaderMaterial({
|
|
133
|
+
uniforms: {
|
|
134
|
+
cellSize: { value: 0 },
|
|
135
|
+
cellThickness: { value: 0 },
|
|
136
|
+
cellColor: { value: new Color() },
|
|
137
|
+
sectionSize: { value: 0 },
|
|
138
|
+
sectionThickness: { value: 0 },
|
|
139
|
+
sectionColor: { value: new Color() },
|
|
140
|
+
fadeDistance: { value: 0 },
|
|
141
|
+
fadeStrength: { value: 0 },
|
|
142
|
+
fadeFrom: { value: 0 },
|
|
143
|
+
infiniteGrid: { value: false },
|
|
144
|
+
followCamera: { value: false },
|
|
145
|
+
cameraOnPlane: { value: new Vector3() },
|
|
146
|
+
planeOrigin: { value: new Vector3() },
|
|
147
|
+
},
|
|
148
|
+
vertexShader,
|
|
149
|
+
fragmentShader,
|
|
150
|
+
transparent: true,
|
|
151
|
+
});
|
|
152
|
+
writeUniforms(material, node);
|
|
153
|
+
const mesh: HostShaderGrid = new Mesh(new PlaneGeometry(...node.size), material);
|
|
154
|
+
// The shader moves vertices (infinite, following): the plane's own bounds mean nothing.
|
|
155
|
+
mesh.frustumCulled = false;
|
|
156
|
+
writePlacement(mesh, node);
|
|
157
|
+
mesh.onBeforeRender = (_renderer, _scene, camera) => {
|
|
158
|
+
plane.setFromNormalAndCoplanarPoint(up, origin).applyMatrix4(mesh.matrixWorld);
|
|
159
|
+
plane.projectPoint(
|
|
160
|
+
cameraPosition.setFromMatrixPosition(camera.matrixWorld),
|
|
161
|
+
material.uniforms['cameraOnPlane']?.value,
|
|
162
|
+
);
|
|
163
|
+
material.uniforms['planeOrigin']?.value.set(0, 0, 0).applyMatrix4(mesh.matrixWorld);
|
|
164
|
+
};
|
|
165
|
+
return mesh;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/** Applies a new description to a grid mesh. */
|
|
169
|
+
export const updateShaderGrid = (
|
|
170
|
+
mesh: HostShaderGrid,
|
|
171
|
+
previous: ShaderGridNode,
|
|
172
|
+
next: ShaderGridNode,
|
|
173
|
+
): void => {
|
|
174
|
+
if (previous.size[0] !== next.size[0] || previous.size[1] !== next.size[1]) {
|
|
175
|
+
mesh.geometry.dispose();
|
|
176
|
+
mesh.geometry = new PlaneGeometry(...next.size);
|
|
177
|
+
}
|
|
178
|
+
writeUniforms(mesh.material, next);
|
|
179
|
+
writePlacement(mesh, next);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/** Frees the grid's geometry and material, which are its own. */
|
|
183
|
+
export const disposeShaderGrid = (mesh: HostShaderGrid): void => {
|
|
184
|
+
mesh.geometry.dispose();
|
|
185
|
+
mesh.material.dispose();
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/** The WebGL kit's shader grid: the mesh, updated in place and freed with it. */
|
|
189
|
+
export const hostShaderGrid = (node: ShaderGridNode): HostedShaderGrid => {
|
|
190
|
+
const mesh = createShaderGrid(node);
|
|
191
|
+
return {
|
|
192
|
+
object: mesh,
|
|
193
|
+
update: (previous, next) => updateShaderGrid(mesh, previous, next),
|
|
194
|
+
dispose: () => disposeShaderGrid(mesh),
|
|
195
|
+
};
|
|
196
|
+
};
|
package/src/webgl/sky.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WebGL kit's sky: three.js's `Sky` (Preetham, `addons/objects/Sky.js` at 0.186.1),
|
|
3
|
+
* as Drei's `Sky` draws it (`bf6f4ad`, `src/core/Sky.tsx`): a box scaled to `distance`
|
|
4
|
+
* whose shader paints the daylight sky for a sun position. Never picked.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { Vector3 } from 'three';
|
|
9
|
+
import { Sky as SkyMesh } from 'three/addons/objects/Sky.js';
|
|
10
|
+
|
|
11
|
+
import type { Sky } from '../kernel/description.ts';
|
|
12
|
+
import type { HostedSky } from '../kernel/renderKit.ts';
|
|
13
|
+
|
|
14
|
+
/** Writes a sky's description into the mesh's uniforms and scale. */
|
|
15
|
+
const writeSky = (mesh: SkyMesh, description: Sky): void => {
|
|
16
|
+
const { uniforms } = mesh.material;
|
|
17
|
+
const set = (name: string, value: unknown): void => {
|
|
18
|
+
const uniform = uniforms[name];
|
|
19
|
+
if (uniform !== undefined) uniform.value = value;
|
|
20
|
+
};
|
|
21
|
+
set('turbidity', description.turbidity);
|
|
22
|
+
set('rayleigh', description.rayleigh);
|
|
23
|
+
set('mieCoefficient', description.mieCoefficient);
|
|
24
|
+
set('mieDirectionalG', description.mieDirectionalG);
|
|
25
|
+
set('cloudCoverage', description.clouds);
|
|
26
|
+
set('sunPosition', new Vector3().fromArray(description.sunPosition));
|
|
27
|
+
mesh.scale.setScalar(description.distance);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Creates the sky mesh; the scene graph adds it and writes its description. */
|
|
31
|
+
export const hostSky = (): HostedSky => {
|
|
32
|
+
const mesh = new SkyMesh();
|
|
33
|
+
mesh.raycast = () => undefined;
|
|
34
|
+
return {
|
|
35
|
+
object: mesh,
|
|
36
|
+
update: (description) => writeSky(mesh, description),
|
|
37
|
+
dispose: () => {
|
|
38
|
+
mesh.geometry.dispose();
|
|
39
|
+
mesh.material.dispose();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WebGPU kit's contact shadow materials, in TSL: the WebGL kit's shadow-from-depth
|
|
3
|
+
* output (colour `color · depth · 2`, alpha `1 − depth`) and three's horizontal and
|
|
4
|
+
* vertical blur shaders (nine taps, their weights). The passes themselves are the
|
|
5
|
+
* kernel's.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { type Color, NoBlending, Texture } from 'three';
|
|
10
|
+
import { depth, float, texture, uniform, uv, vec2 } from 'three/tsl';
|
|
11
|
+
import { MeshBasicNodeMaterial } from 'three/webgpu';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
type BlurPass,
|
|
15
|
+
createContactShadowPasses,
|
|
16
|
+
type ShadowMaterials,
|
|
17
|
+
} from '../kernel/contactShadowPasses.ts';
|
|
18
|
+
import type { SceneRenderer } from '../kernel/renderer.ts';
|
|
19
|
+
import type { ContactShadowPasses, ShadowedScene } from '../kernel/renderKit.ts';
|
|
20
|
+
|
|
21
|
+
/** A material writing exactly what its nodes say: no blending, depth or tone mapping. */
|
|
22
|
+
const plainMaterial = (): MeshBasicNodeMaterial => {
|
|
23
|
+
const material = new MeshBasicNodeMaterial();
|
|
24
|
+
material.transparent = true;
|
|
25
|
+
material.blending = NoBlending;
|
|
26
|
+
material.depthTest = false;
|
|
27
|
+
material.depthWrite = false;
|
|
28
|
+
material.toneMapped = false;
|
|
29
|
+
return material;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const depthMaterial = (color: Color): MeshBasicNodeMaterial => {
|
|
33
|
+
const material = plainMaterial();
|
|
34
|
+
// Darkest where an object touches the plane: `depth` is linear for the orthographic camera.
|
|
35
|
+
material.colorNode = uniform(color).mul(depth).mul(2);
|
|
36
|
+
material.opacityNode = float(1).sub(depth);
|
|
37
|
+
return material;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** three's `HorizontalBlurShader` and `VerticalBlurShader` taps: offset and weight. */
|
|
41
|
+
const taps = [
|
|
42
|
+
[-4, 0.051],
|
|
43
|
+
[-3, 0.0918],
|
|
44
|
+
[-2, 0.12245],
|
|
45
|
+
[-1, 0.1531],
|
|
46
|
+
[0, 0.1633],
|
|
47
|
+
[1, 0.1531],
|
|
48
|
+
[2, 0.12245],
|
|
49
|
+
[3, 0.0918],
|
|
50
|
+
[4, 0.051],
|
|
51
|
+
] as const;
|
|
52
|
+
|
|
53
|
+
const blur = (direction: 'horizontal' | 'vertical'): BlurPass => {
|
|
54
|
+
const source = texture(new Texture());
|
|
55
|
+
const step = uniform(0);
|
|
56
|
+
const along = direction === 'horizontal' ? vec2(step, 0) : vec2(0, step);
|
|
57
|
+
const [first, ...rest] = taps.map(([offset, weight]) =>
|
|
58
|
+
source.sample(uv().add(along.mul(offset))).mul(weight),
|
|
59
|
+
);
|
|
60
|
+
const sum = rest.reduce((total, tap) => total.add(tap), first ?? source);
|
|
61
|
+
const material = plainMaterial();
|
|
62
|
+
material.colorNode = sum.rgb;
|
|
63
|
+
material.opacityNode = sum.a;
|
|
64
|
+
return {
|
|
65
|
+
material,
|
|
66
|
+
set: (map, amount) => {
|
|
67
|
+
source.value = map;
|
|
68
|
+
step.value = amount;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const webgpuShadowMaterials: ShadowMaterials = {
|
|
74
|
+
depth: depthMaterial,
|
|
75
|
+
blur,
|
|
76
|
+
flippedTargets: true,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Creates the contact shadow passes for one scene graph and renderer, in TSL. */
|
|
80
|
+
export const createContactShadows = (
|
|
81
|
+
graph: ShadowedScene,
|
|
82
|
+
renderer: SceneRenderer,
|
|
83
|
+
): ContactShadowPasses => createContactShadowPasses(graph, renderer, webgpuShadowMaterials);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WebGPU render kit: the scene's shaded parts (the sky, the shader grid, contact
|
|
3
|
+
* shadows) written in TSL, for three.js's `WebGPURenderer` on either of its backends.
|
|
4
|
+
* The Foldkit adapter's `webgpuRendererFactory` carries it.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type { RenderKit } from '../kernel/renderKit.ts';
|
|
9
|
+
import { createContactShadows } from './contactShadows.ts';
|
|
10
|
+
import { hostShaderGrid } from './shaderGrid.ts';
|
|
11
|
+
import { hostSky } from './sky.ts';
|
|
12
|
+
|
|
13
|
+
export const webgpuKit: RenderKit = {
|
|
14
|
+
// troika's labels are WebGL materials; real WebGPU rejects their draw calls.
|
|
15
|
+
drawsLabels: false,
|
|
16
|
+
sky: hostSky,
|
|
17
|
+
shaderGrid: hostShaderGrid,
|
|
18
|
+
contactShadows: createContactShadows,
|
|
19
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WebGPU kit's shader grid: the WebGL kit's grid (Drei's `Grid`, `bf6f4ad`, MIT; after
|
|
3
|
+
* Fyrestar's `THREE.InfiniteGridHelper` and threlte's `Grid`) written in TSL, so that
|
|
4
|
+
* three's `WebGPURenderer` can draw it on either backend. Same lines, same fade, same
|
|
5
|
+
* camera uniforms set in `onBeforeRender`.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import {
|
|
10
|
+
BackSide,
|
|
11
|
+
Color,
|
|
12
|
+
DoubleSide,
|
|
13
|
+
FrontSide,
|
|
14
|
+
Mesh,
|
|
15
|
+
Plane,
|
|
16
|
+
PlaneGeometry,
|
|
17
|
+
type Side,
|
|
18
|
+
Vector3,
|
|
19
|
+
} from 'three';
|
|
20
|
+
import {
|
|
21
|
+
abs,
|
|
22
|
+
distance,
|
|
23
|
+
float,
|
|
24
|
+
fract,
|
|
25
|
+
fwidth,
|
|
26
|
+
min,
|
|
27
|
+
mix,
|
|
28
|
+
modelWorldMatrix,
|
|
29
|
+
modelWorldMatrixInverse,
|
|
30
|
+
positionGeometry,
|
|
31
|
+
pow,
|
|
32
|
+
select,
|
|
33
|
+
uniform,
|
|
34
|
+
varying,
|
|
35
|
+
vec4,
|
|
36
|
+
} from 'three/tsl';
|
|
37
|
+
import { MeshBasicNodeMaterial } from 'three/webgpu';
|
|
38
|
+
|
|
39
|
+
import type { MaterialSide, ShaderGridNode } from '../kernel/description.ts';
|
|
40
|
+
import type { HostedShaderGrid } from '../kernel/renderKit.ts';
|
|
41
|
+
|
|
42
|
+
const sides = { front: FrontSide, back: BackSide, double: DoubleSide } satisfies Record<
|
|
43
|
+
MaterialSide,
|
|
44
|
+
Side
|
|
45
|
+
>;
|
|
46
|
+
|
|
47
|
+
/** The grid's uniforms: one node each, written from the description. */
|
|
48
|
+
const createUniforms = () => ({
|
|
49
|
+
cellSize: uniform(0.5),
|
|
50
|
+
cellThickness: uniform(0.5),
|
|
51
|
+
cellColor: uniform(new Color()),
|
|
52
|
+
sectionSize: uniform(1),
|
|
53
|
+
sectionThickness: uniform(1),
|
|
54
|
+
sectionColor: uniform(new Color()),
|
|
55
|
+
fadeDistance: uniform(100),
|
|
56
|
+
fadeStrength: uniform(1),
|
|
57
|
+
fadeFrom: uniform(1),
|
|
58
|
+
infinite: uniform(0),
|
|
59
|
+
followCamera: uniform(0),
|
|
60
|
+
cameraOnPlane: uniform(new Vector3()),
|
|
61
|
+
planeOrigin: uniform(new Vector3()),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
type Uniforms = ReturnType<typeof createUniforms>;
|
|
65
|
+
|
|
66
|
+
/** The grid's material: the WebGL kit's vertex and fragment shaders, as TSL nodes. */
|
|
67
|
+
const gridMaterial = (u: Uniforms): MeshBasicNodeMaterial => {
|
|
68
|
+
const material = new MeshBasicNodeMaterial({ transparent: true });
|
|
69
|
+
// The plane geometry lies in XY; the grid lies in XZ. Infinite grids reach the fade.
|
|
70
|
+
const lifted = positionGeometry.xzy.mul(
|
|
71
|
+
select(u.infinite.greaterThan(0.5), u.fadeDistance.add(1), 1),
|
|
72
|
+
);
|
|
73
|
+
// Following the camera slides the plane by its foot's offset, in the grid's own space.
|
|
74
|
+
const shift = modelWorldMatrixInverse.mul(vec4(u.cameraOnPlane.sub(u.planeOrigin), 0)).xyz;
|
|
75
|
+
const local = select(u.followCamera.greaterThan(0.5), lifted.add(shift), lifted);
|
|
76
|
+
material.positionNode = local;
|
|
77
|
+
const gridPosition = varying(local);
|
|
78
|
+
const worldPosition = varying(modelWorldMatrix.mul(vec4(local, 1)).xyz);
|
|
79
|
+
// 1 on a line of a grid with this spacing, 0 between, antialiased over a pixel.
|
|
80
|
+
const lines = (spacing: typeof u.cellSize, thickness: typeof u.cellThickness) => {
|
|
81
|
+
const at = gridPosition.xz.div(spacing);
|
|
82
|
+
const away = abs(fract(at.sub(0.5)).sub(0.5)).div(fwidth(at));
|
|
83
|
+
return float(1).sub(min(min(away.x, away.y).add(1).sub(thickness), 1));
|
|
84
|
+
};
|
|
85
|
+
const cell = lines(u.cellSize, u.cellThickness);
|
|
86
|
+
const section = lines(u.sectionSize, u.sectionThickness);
|
|
87
|
+
const away = distance(u.cameraOnPlane.mul(u.fadeFrom), worldPosition);
|
|
88
|
+
const fade = pow(float(1).sub(min(away.div(u.fadeDistance), 1)), u.fadeStrength);
|
|
89
|
+
const alpha = cell.add(section).mul(fade);
|
|
90
|
+
material.colorNode = mix(u.cellColor, u.sectionColor, min(1, u.sectionThickness.mul(section)));
|
|
91
|
+
material.opacityNode = mix(alpha.mul(0.75), alpha, section);
|
|
92
|
+
// Discard where there is no line, as the WebGL kit's shader does.
|
|
93
|
+
material.alphaTest = 1e-4;
|
|
94
|
+
return material;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** Writes a node's settings into the uniforms and material. */
|
|
98
|
+
const writeUniforms = (
|
|
99
|
+
u: Uniforms,
|
|
100
|
+
material: MeshBasicNodeMaterial,
|
|
101
|
+
node: ShaderGridNode,
|
|
102
|
+
): void => {
|
|
103
|
+
u.cellSize.value = node.cellSize;
|
|
104
|
+
u.cellThickness.value = node.cellThickness;
|
|
105
|
+
u.cellColor.value.setHex(node.cellColor);
|
|
106
|
+
u.sectionSize.value = node.sectionSize;
|
|
107
|
+
u.sectionThickness.value = node.sectionThickness;
|
|
108
|
+
u.sectionColor.value.setHex(node.sectionColor);
|
|
109
|
+
u.fadeDistance.value = node.fadeDistance;
|
|
110
|
+
u.fadeStrength.value = node.fadeStrength;
|
|
111
|
+
u.fadeFrom.value = node.fadeFrom;
|
|
112
|
+
u.infinite.value = node.infinite ? 1 : 0;
|
|
113
|
+
u.followCamera.value = node.followCamera ? 1 : 0;
|
|
114
|
+
material.side = sides[node.side];
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const up = new Vector3(0, 1, 0);
|
|
118
|
+
const origin = new Vector3();
|
|
119
|
+
const plane = new Plane();
|
|
120
|
+
const cameraPosition = new Vector3();
|
|
121
|
+
|
|
122
|
+
/** The WebGPU kit's shader grid: a plane with the grid material, placed and updated. */
|
|
123
|
+
export const hostShaderGrid = (node: ShaderGridNode): HostedShaderGrid => {
|
|
124
|
+
const u = createUniforms();
|
|
125
|
+
const material = gridMaterial(u);
|
|
126
|
+
writeUniforms(u, material, node);
|
|
127
|
+
const mesh = new Mesh(new PlaneGeometry(...node.size), material);
|
|
128
|
+
// The shader moves vertices (infinite, following): the plane's own bounds mean nothing.
|
|
129
|
+
mesh.frustumCulled = false;
|
|
130
|
+
const place = (next: ShaderGridNode): void => {
|
|
131
|
+
mesh.position.fromArray(next.position);
|
|
132
|
+
mesh.rotation.fromArray([...next.rotation]);
|
|
133
|
+
};
|
|
134
|
+
place(node);
|
|
135
|
+
mesh.onBeforeRender = (_renderer, _scene, camera) => {
|
|
136
|
+
plane.setFromNormalAndCoplanarPoint(up, origin).applyMatrix4(mesh.matrixWorld);
|
|
137
|
+
plane.projectPoint(
|
|
138
|
+
cameraPosition.setFromMatrixPosition(camera.matrixWorld),
|
|
139
|
+
u.cameraOnPlane.value,
|
|
140
|
+
);
|
|
141
|
+
u.planeOrigin.value.set(0, 0, 0).applyMatrix4(mesh.matrixWorld);
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
object: mesh,
|
|
145
|
+
update: (previous, next) => {
|
|
146
|
+
if (previous.size[0] !== next.size[0] || previous.size[1] !== next.size[1]) {
|
|
147
|
+
mesh.geometry.dispose();
|
|
148
|
+
mesh.geometry = new PlaneGeometry(...next.size);
|
|
149
|
+
}
|
|
150
|
+
writeUniforms(u, material, next);
|
|
151
|
+
place(next);
|
|
152
|
+
},
|
|
153
|
+
dispose: () => {
|
|
154
|
+
mesh.geometry.dispose();
|
|
155
|
+
material.dispose();
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WebGPU kit's sky: three.js's `SkyMesh` (`addons/objects/SkyMesh.js` at 0.186.1), the
|
|
3
|
+
* same Preetham sky as the WebGL kit's `Sky`, written in TSL. Never picked.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import { SkyMesh } from 'three/addons/objects/SkyMesh.js';
|
|
8
|
+
|
|
9
|
+
import type { Sky } from '../kernel/description.ts';
|
|
10
|
+
import type { HostedSky } from '../kernel/renderKit.ts';
|
|
11
|
+
|
|
12
|
+
/** Writes a sky's description into the mesh's uniforms and scale. */
|
|
13
|
+
const writeSky = (mesh: SkyMesh, description: Sky): void => {
|
|
14
|
+
mesh.turbidity.value = description.turbidity;
|
|
15
|
+
mesh.rayleigh.value = description.rayleigh;
|
|
16
|
+
mesh.mieCoefficient.value = description.mieCoefficient;
|
|
17
|
+
mesh.mieDirectionalG.value = description.mieDirectionalG;
|
|
18
|
+
mesh.cloudCoverage.value = description.clouds;
|
|
19
|
+
mesh.sunPosition.value.fromArray(description.sunPosition);
|
|
20
|
+
mesh.scale.setScalar(description.distance);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Creates the sky mesh; the scene graph adds it and writes its description. */
|
|
24
|
+
export const hostSky = (): HostedSky => {
|
|
25
|
+
const mesh = new SkyMesh();
|
|
26
|
+
mesh.raycast = () => undefined;
|
|
27
|
+
return {
|
|
28
|
+
object: mesh,
|
|
29
|
+
update: (description) => writeSky(mesh, description),
|
|
30
|
+
dispose: () => {
|
|
31
|
+
mesh.geometry.dispose();
|
|
32
|
+
mesh.material.dispose();
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
};
|