@geoblocks/cesium-post-process 0.1.2 → 0.2.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/README.md CHANGED
@@ -18,7 +18,7 @@ Each effect is inactive after construction. Every option is also a property, app
18
18
 
19
19
  ```javascript
20
20
  import {Color, Rectangle} from '@cesium/engine';
21
- import {AnalogVideo, Clouds, ColorIsolation, DigitalVideo, DroneDisplay, Infrared, Jello, LensDistortion, Lightning, MotionBlur, Precipitation, SnowLine, SpeedLines, Spotlight, Super8, Technicolor, TiltShift, Toy, ValleyFog, WhiteModel} from '@geoblocks/cesium-post-process';
21
+ import {AnalogVideo, Clouds, ColorIsolation, DigitalVideo, DroneDisplay, Dufour, Infrared, Jello, LensDistortion, Lightning, MotionBlur, Precipitation, SnowLine, SpeedLines, Spotlight, Super8, Technicolor, TiltShift, Toy, ValleyFog, WhiteModel} from '@geoblocks/cesium-post-process';
22
22
 
23
23
  const snowLine = new SnowLine(viewer, {
24
24
  altitude: 2000, // meters above the ellipsoid where the snow is half covering
@@ -112,6 +112,17 @@ const whiteModel = new WhiteModel(viewer, {
112
112
  });
113
113
  whiteModel.active = true;
114
114
 
115
+ // or, in Toy's place, the Dufour map's hachures
116
+ const dufour = new Dufour(viewer, {
117
+ spacing: 3.5, // the strokes' spacing on the screen, in pixels
118
+ strokeLength: 14, // a stroke's length between its breaks, in spacings, on average
119
+ weight: 1, // 1 for Lehmann's scale stretched for the Alps: the ink's share grows with the slope, all of it at 60 degrees
120
+ oblique: 0.8, // how much thinner the strokes facing the north-west light, and thicker away from it, 0 to 1
121
+ flatSlope: 4, // the slope under which the ground is left blank, in degrees
122
+ outline: 0.8, // how dark the silhouettes' outline; 0 for none
123
+ });
124
+ dufour.active = true;
125
+
115
126
  const tiltShift = new TiltShift(viewer, {
116
127
  focus,
117
128
  range: 0.3, // width of the sharp band, in factors of two of the focal distance (an orthographic camera: shares of the view's height)
@@ -197,18 +208,33 @@ super8.grain = 0.3;
197
208
  super8.destroy();
198
209
  ```
199
210
 
200
- ### Shader chunk
211
+ ### Shader chunks
201
212
 
202
213
  `EyeFromDepth` is the GLSL chunk the depth-based effects prepend to their fragment shaders: `vec4 eyeAt(sampler2D depthTexture, vec2 uv)` gives a pixel's eye coordinates from the depth texture, with `w` 0 for the sky, with or without a logarithmic depth buffer. It is exported for effects of your own, and for `@geoblocks/cesium-flyto`'s depth of field.
203
214
 
215
+ `Height`, prepended after it, gives heights above the ellipsoid: `float heightAt(vec3 eye)` of a point in eye coordinates, relative to the camera so that single precision keeps it to the centimeter, and `vec2 belowHeight(vec3 rayEnd, float height)`, the part of the ray from the camera to a point below a height, as shares of it. Its uniforms, the camera's height, the local up direction and the Earth's radius, come from `heightUniforms(scene)` in double precision:
216
+
217
+ ```js
218
+ import {EyeFromDepth, Height, heightUniforms} from '@geoblocks/cesium-post-process';
219
+
220
+ const stage = new PostProcessStage({
221
+ fragmentShader: EyeFromDepth + Height + myShader, // myShader calls eyeAt and heightAt
222
+ uniforms: {...heightUniforms(scene), myUniform: () => value},
223
+ });
224
+ ```
225
+
226
+ `Normal`, also prepended after `EyeFromDepth`, gives a pixel's normal in eye coordinates from the depth of its neighbors, facing the camera: `vec3 normalAt(sampler2D depthTexture, vec2 uv, vec3 center, float distance)`, with `center` the pixel's eye coordinates and the neighbors `distance` pixels away, on each axis the nearer side so that the scene's edges do not bend it; and `vec3 smoothNormalAt(sampler2D depthTexture, vec2 uv, vec3 center)`, averaged over two distances so that the terrain's flat triangles blend into a smoother surface. It needs no uniforms.
227
+
204
228
  ### Order of the effects
205
229
 
206
- Cesium runs the stages in the order they are added and cannot insert one elsewhere. Activate the effects in this order: `SnowLine`, `ValleyFog`, `Spotlight`, `Clouds`, `Precipitation`, `Lightning`, `Toy` or `WhiteModel`, `TiltShift`, `MotionBlur`, `SpeedLines`, `ColorIsolation`, `Infrared`, `Technicolor`, `Super8`, `LensDistortion`, `Jello`, `DroneDisplay`, `AnalogVideo` or `DigitalVideo`. The snow is then under the fog, the clouds behind the rain and the lightning in front of them, the rain or the falling snow in the scene, the lens effects apply to the whole scene, and the film looks apply to everything, their black bars included. The drone camera comes last, in the order of a real one: its lens bends the picture, its sensor reads it with the jello of its shake, its display is drawn over the picture and stays straight, and the video signal, analog or digital, disturbs both. To activate an effect later, deactivate and activate again the active effects that come after it.
230
+ Cesium runs the stages in the order they are added and cannot insert one elsewhere. Activate the effects in this order: `SnowLine`, `ValleyFog`, `Spotlight`, `Clouds`, `Precipitation`, `Lightning`, `Toy`, `WhiteModel` or `Dufour`, `TiltShift`, `MotionBlur`, `SpeedLines`, `ColorIsolation`, `Infrared`, `Technicolor`, `Super8`, `LensDistortion`, `Jello`, `DroneDisplay`, `AnalogVideo` or `DigitalVideo`. The snow is then under the fog, the clouds behind the rain and the lightning in front of them, the rain or the falling snow in the scene, the lens effects apply to the whole scene, and the film looks apply to everything, their black bars included. The drone camera comes last, in the order of a real one: its lens bends the picture, its sensor reads it with the jello of its shake, its display is drawn over the picture and stays straight, and the video signal, analog or digital, disturbs both. To activate an effect later, deactivate and activate again the active effects that come after it.
207
231
 
208
232
  ### Notes
209
233
 
210
234
  `WhiteModel` is the look of an architect's plaster model or white render: the colors turned to their value and pressed into a narrow range of whites, so the light and the shapes alone tell the surfaces apart; lower `values` for more contrast, a `saturation` above 0 to keep an accent (the project in a color, the context white). The darkest colors, under `poche` (a tenth by default), keep their value, fading into the whites about it: a section's poché stays solid black; give 0 if deep shadows or a dusk scene should stay gray. An accent's hue is scaled down rather than clipped where a channel would pass 1. It is a `Toy` with another grading and shares its edges, found in the depth (`DepthEdges`, a shader chunk both prepend): the creases lit and shaded, the outline on the near side of a jump. Where surfaces meet it adds an occlusion, the Alchemy ambient obscurance ("The Alchemy Screen-Space Ambient Obscurance Algorithm", McGuire et al., HPG 2011): each pixel's normal from the depth's slopes, then the share of 12 samples on a spiral in a disc of `radius` meters that rise above its surface, each the more as it rises steeper and none beyond the radius, the spiral turned from pixel to pixel. A corner's foot gets about a half, a flat floor none. Zoomed out, when the disc is under a few pixels wide, the occlusion fades out: its samples would fall within the depth's noise. Each pixel then reads the depth about 20 times. For the whitest model, give the scene's materials light colors too: the grading keeps their differences in value.
211
235
 
236
+ `Dufour` draws the terrain as the hachures of the Dufour map (1845-1864), black ink on white paper: strokes down the steepest slope, each a straight line broken every `strokeLength` spacings or so, its breaks staggered from the next stroke's, so that no line runs across them. A stroke covers more of the spacing as the slope is steeper, on Lehmann's scale (1799) stretched for the Alps, the ink's share growing from none on the flat to all of it at 60 degrees rather than his 45, but never more than 45 % of the spacing, so that the paper shows between the strokes, and more on the slopes away from a light from the north-west, as Dufour's: the steep ground and the slopes in the shade are darker, the flat ground under `flatSlope` is blank, the strokes fading in over 2 degrees above it, so that the terrain's slightly tilted triangles do not show as patches. The slope comes from the depth's normals, averaged over 4, 12 and 24 pixels, and the way down from one averaged wider, over 32 pixels, snapped to one of 12 directions, the two nearest blended over a narrow band: the strokes are straight and parallel on a slope rather than turning with each pixel's normal, which drew swirls close up. The strokes are fixed to the ground, in a frame of east and north on the ground below the camera, which follows it once it has moved 20 km, where the strokes jump. Their spacing is a power of two of meters, chosen for each direction from the ground a pixel covers across its strokes, far more than its width where a slope is seen at a grazing angle, the next power faded in as they grow closer than `spacing` pixels: they stay about as far apart on the screen at any distance and angle; where they still grow too close to tell apart, they fade to the share of the paper they cover. The lakes, flat, are left blank.
237
+
212
238
  `ColorIsolation`, `Infrared`, `Technicolor` and `Super8` work on display colors, after Cesium's tone mapping. `ColorIsolation`, `Infrared` and `Technicolor` follow prod80's [ReShade shaders](https://github.com/prod80/prod80-ReShade-Repository) (MIT). `Technicolor` imitates the three-strip process: the scene is split into red, green and blue records, each printed with its complementary dye, which gives its dense, saturated primaries. `Super8` blurs the highlights into a red-orange halation, as light scattered back through the film base, draws its grain as clumps a few pixels wide that differ slightly between the dye layers, and moves at 18 frames per second (grain, weave and flicker change with each film frame, light leaks drift slowly), so while it is active the scene renders 18 times per second, also with `requestRenderMode`.
213
239
 
214
240
  `MotionBlur` blurs each pixel along its motion on the screen since the previous frame, over the exposure time, with the depth-weighted gathering of "A Reconstruction Filter for Plausible Motion Blur" (McGuire et al., I3D 2012). Only the camera moves: each pixel's motion follows from its depth and the previous frame's view, written once per pixel into a velocity buffer at half resolution rather than recomputed for each sample, and the samples are weighted by depth so that near terrain and far ridges or sky do not smear into each other. Without the paper's neighborhood velocity pass, a pixel only gathers along its own motion: the blur of moving terrain does not spill over a background that does not move, such as the sky, and its outline stays sharp there. The exposure is scaled by the time between frames, so the blur is the same whatever the frame rate; after a pause in `requestRenderMode`, that time is taken as at most a tenth of a second. At strength 0 its stage is disabled.
@@ -231,6 +257,8 @@ Its `snow`, from 0 to 1, turns the rain to snow in one population, so that nothi
231
257
 
232
258
  `SnowLine` and `ValleyFog` compute each pixel's height relative to the camera, in double precision on the CPU for the camera, so they stay accurate far from the origin. The snow's slope comes from the normal rebuilt from the neighboring pixels. The fog is lit by the sun like Cesium's own fog: its color during the day, a moonlit blue at night, as bright as `scene.fog.minimumBrightness`. Both work in 3D only.
233
259
 
234
- `SnowLine`, `ValleyFog`, `Precipitation`, `Clouds`, `Lightning`, `Toy`, `WhiteModel`, `TiltShift`, `Spotlight` and `MotionBlur` read the depth buffer. The post-process stages only get the terrain's depth when it is depth tested, so `globe.depthTestAgainstTerrain` is on while any of them is active and restored when the last one is deactivated: billboards and labels that show through the terrain can be hidden by it meanwhile.
260
+ `SnowLine`, `ValleyFog`, `Precipitation`, `Clouds`, `Lightning`, `Toy`, `WhiteModel`, `Dufour`, `TiltShift`, `Spotlight` and `MotionBlur` read the depth buffer. The post-process stages only get the terrain's depth when it is depth tested, so `globe.depthTestAgainstTerrain` is on while any of them is active and restored when the last one is deactivated: billboards and labels that show through the terrain can be hidden by it meanwhile.
261
+
262
+ The depth they read is the nearest of Cesium's frustums': with `scene.logarithmicDepthBuffer` off, Cesium splits the view into frustums of `farToNearRatio` (1000 by default) from the near plane, and only the nearest one's depth reaches the stages. What lies past the split has no depth there: `Toy` and `WhiteModel` paint their sky over it and draw no edge on it, `TiltShift` blurs it as the far background, `SnowLine` and `ValleyFog` take it for sky. Keep the logarithmic depth buffer on, or set `camera.frustum.near` so that one frustum holds the scene (a near plane of 0.3 m reaches 300 m).
235
263
 
236
264
  Each effect is a full-screen pass, about 1 to 5 ms per frame at full HD on an integrated GPU, on top of a scene that may already be near its budget: Chrome then stalls for hundreds of milliseconds at a time rather than slowing down evenly. If activating an effect brings that on, the cheapest lever is `scene.msaaSamples = 1` while it is active, restored when it is deactivated, as `@geoblocks/cesium-flyto` does for its flights: 4x multisampling costs more than the effect itself, and a moving picture hides the aliasing.
@@ -2,6 +2,7 @@ export {default as AnalogVideo} from './analog-video.js';
2
2
  export {default as Clouds} from './clouds.js';
3
3
  export {default as ColorIsolation} from './color-isolation.js';
4
4
  export {default as DigitalVideo} from './digital-video.js';
5
+ export {default as Dufour} from './dufour.js';
5
6
  export {default as DroneDisplay} from './drone-display.js';
6
7
  export {default as Infrared} from './infrared.js';
7
8
  export {default as Jello} from './jello.js';
@@ -19,5 +20,8 @@ export {default as Toy} from './toy.js';
19
20
  export {default as ValleyFog} from './valley-fog.js';
20
21
  export {default as WhiteModel} from './white-model.js';
21
22
 
22
- // the shader chunk the depth-based effects share, for effects of your own
23
+ // the shader chunks the depth-based effects share, and the uniforms of Height, for effects of your own
23
24
  export {default as EyeFromDepth} from './shaders/EyeFromDepth.js';
25
+ export {default as Height} from './shaders/Height.js';
26
+ export {heightUniforms} from './height.js';
27
+ export {default as Normal} from './shaders/Normal.js';
package/dufour.js ADDED
@@ -0,0 +1,225 @@
1
+ import {Cartesian3, Color, FixedFrameTransforms, Math as CesiumMath, Matrix3, Matrix4} from '@cesium/core';
2
+ import {PostProcessStage} from '@cesium/engine';
3
+ import {acquireTerrainDepth, releaseTerrainDepth} from './depth-test.js';
4
+ import Effect from './effect.js';
5
+ import {heightUniforms} from './height.js';
6
+ import DepthEdges from './shaders/DepthEdges.js';
7
+ import DufourShader from './shaders/Dufour.js';
8
+ import EyeFromDepth from './shaders/EyeFromDepth.js';
9
+ import Hash from './shaders/Hash.js';
10
+ import Height from './shaders/Height.js';
11
+ import Normal from './shaders/Normal.js';
12
+
13
+ // how far the camera moves over the ground, in meters, before the hachures' frame follows it: they jump then
14
+ const REBASE = 20000;
15
+
16
+ const originECScratch = new Cartesian3();
17
+ const eastScratch = new Cartesian3();
18
+ const northScratch = new Cartesian3();
19
+ const surfaceScratch = new Cartesian3();
20
+ const frameScratch = new Matrix4();
21
+ const paperScratch = new Cartesian3();
22
+ const inkScratch = new Cartesian3();
23
+
24
+ /**
25
+ * The look of the Dufour map, the Swiss topographic map of the 1850s: the slopes drawn in hachures, ink strokes down
26
+ * the steepest slope, straight and broken along their length, thicker where it is steeper and on the slopes away from a light from
27
+ * the north-west, the flat ground left blank, black ink on white paper.
28
+ * The strokes are fixed to the ground: they stay put as the camera moves, a power of two of meters apart, the next
29
+ * power faded in as they grow closer than `spacing` pixels.
30
+ */
31
+ export default class Dufour extends Effect {
32
+ /**
33
+ * @param {import('@cesium/engine').CesiumWidget} viewer
34
+ * @param {{spacing?: number, strokeLength?: number, weight?: number, oblique?: number, flatSlope?: number, outline?: number, jump?: number, paper?: Color, ink?: Color}} [options]
35
+ */
36
+ constructor(viewer, options = {}) {
37
+ super(viewer);
38
+ this.spacing_ = options.spacing ?? 3.5;
39
+ this.strokeLength_ = options.strokeLength ?? 14;
40
+ this.weight_ = options.weight ?? 1;
41
+ this.oblique_ = options.oblique ?? 0.8;
42
+ this.flatSlope_ = options.flatSlope ?? 4;
43
+ this.outline_ = options.outline ?? 0.8;
44
+ this.jump_ = options.jump ?? 20;
45
+ this.paper_ = options.paper ?? Color.fromCssColorString('#f7f3ea');
46
+ this.ink_ = options.ink ?? Color.fromCssColorString('#1d1b19');
47
+ /** @type {Cartesian3 | undefined} */
48
+ this.origin_ = undefined;
49
+ // east, north and up at the origin
50
+ this.frame_ = new Matrix3();
51
+ }
52
+
53
+ /**
54
+ * @override
55
+ * @param {import('@cesium/engine').Scene} scene
56
+ */
57
+ createStage_(scene) {
58
+ const toEye = (/** @type {number} */ column, /** @type {Cartesian3} */ result) => {
59
+ this.follow_(scene);
60
+ Matrix3.getColumn(this.frame_, column, result);
61
+ return Matrix4.multiplyByPointAsVector(scene.camera.viewMatrix, result, result);
62
+ };
63
+ return new PostProcessStage({
64
+ fragmentShader: EyeFromDepth + DepthEdges + Height + Normal + Hash + DufourShader,
65
+ uniforms: {
66
+ ...heightUniforms(scene),
67
+ spacing: () => this.spacing_,
68
+ strokeLength: () => this.strokeLength_,
69
+ weight: () => this.weight_,
70
+ oblique: () => this.oblique_,
71
+ flatSlope: () => CesiumMath.toRadians(this.flatSlope_),
72
+ outline: () => this.outline_,
73
+ jump: () => this.jump_,
74
+ paper: () => Cartesian3.fromElements(this.paper_.red, this.paper_.green, this.paper_.blue, paperScratch),
75
+ ink: () => Cartesian3.fromElements(this.ink_.red, this.ink_.green, this.ink_.blue, inkScratch),
76
+ originEC: () => {
77
+ this.follow_(scene);
78
+ return Matrix4.multiplyByPoint(scene.camera.viewMatrix, /** @type {Cartesian3} */ (this.origin_), originECScratch);
79
+ },
80
+ eastEC: () => toEye(0, eastScratch),
81
+ northEC: () => toEye(1, northScratch),
82
+ },
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Moves the hachures' frame under the camera once it is REBASE meters away over the ground.
88
+ * @private
89
+ * @param {import('@cesium/engine').Scene} scene
90
+ */
91
+ follow_(scene) {
92
+ const surface = scene.ellipsoid.scaleToGeodeticSurface(scene.camera.positionWC, surfaceScratch);
93
+ if (!surface || (this.origin_ && Cartesian3.distance(surface, this.origin_) < REBASE)) {
94
+ return;
95
+ }
96
+ this.origin_ = Cartesian3.clone(surface, this.origin_);
97
+ Matrix4.getMatrix3(FixedFrameTransforms.eastNorthUpToFixedFrame(this.origin_, scene.ellipsoid, frameScratch), this.frame_);
98
+ }
99
+
100
+ /**
101
+ * @override
102
+ * @param {import('@cesium/engine').Scene} scene
103
+ */
104
+ activated_(scene) {
105
+ acquireTerrainDepth(scene);
106
+ }
107
+
108
+ /**
109
+ * @override
110
+ * @param {import('@cesium/engine').Scene} scene
111
+ */
112
+ deactivating_(scene) {
113
+ releaseTerrainDepth(scene);
114
+ }
115
+
116
+ /**
117
+ * The strokes' spacing on the screen, in pixels: the closest they get before the next power of two of meters
118
+ * takes over.
119
+ */
120
+ get spacing() {
121
+ return this.spacing_;
122
+ }
123
+
124
+ set spacing(value) {
125
+ this.spacing_ = value;
126
+ this.viewer.scene.requestRender();
127
+ }
128
+
129
+ /**
130
+ * A stroke's length between its breaks, in spacings, on average.
131
+ */
132
+ get strokeLength() {
133
+ return this.strokeLength_;
134
+ }
135
+
136
+ set strokeLength(value) {
137
+ this.strokeLength_ = value;
138
+ this.viewer.scene.requestRender();
139
+ }
140
+
141
+ /**
142
+ * The ink's weight: 1 for Lehmann's scale stretched for the Alps, the share of the spacing a stroke covers growing
143
+ * with the slope, all of it at 60 degrees, before the light.
144
+ */
145
+ get weight() {
146
+ return this.weight_;
147
+ }
148
+
149
+ set weight(value) {
150
+ this.weight_ = value;
151
+ this.viewer.scene.requestRender();
152
+ }
153
+
154
+ /**
155
+ * How much thinner the strokes on the slopes facing the north-west light, and thicker away from it, 0 to 1.
156
+ */
157
+ get oblique() {
158
+ return this.oblique_;
159
+ }
160
+
161
+ set oblique(value) {
162
+ this.oblique_ = value;
163
+ this.viewer.scene.requestRender();
164
+ }
165
+
166
+ /**
167
+ * The slope under which the ground is left blank, in degrees.
168
+ */
169
+ get flatSlope() {
170
+ return this.flatSlope_;
171
+ }
172
+
173
+ set flatSlope(value) {
174
+ this.flatSlope_ = value;
175
+ this.viewer.scene.requestRender();
176
+ }
177
+
178
+ /**
179
+ * How dark the outline along the silhouettes, 0 for none.
180
+ */
181
+ get outline() {
182
+ return this.outline_;
183
+ }
184
+
185
+ set outline(value) {
186
+ this.outline_ = value;
187
+ this.viewer.scene.requestRender();
188
+ }
189
+
190
+ /**
191
+ * How far the depth jumps, in pixels' sizes, to be a silhouette, outlined: 20 by default.
192
+ */
193
+ get jump() {
194
+ return this.jump_;
195
+ }
196
+
197
+ set jump(value) {
198
+ this.jump_ = value;
199
+ this.viewer.scene.requestRender();
200
+ }
201
+
202
+ /**
203
+ * The paper's color, and the sky's.
204
+ */
205
+ get paper() {
206
+ return this.paper_;
207
+ }
208
+
209
+ set paper(value) {
210
+ this.paper_ = value;
211
+ this.viewer.scene.requestRender();
212
+ }
213
+
214
+ /**
215
+ * The ink's color.
216
+ */
217
+ get ink() {
218
+ return this.ink_;
219
+ }
220
+
221
+ set ink(value) {
222
+ this.ink_ = value;
223
+ this.viewer.scene.requestRender();
224
+ }
225
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geoblocks/cesium-post-process",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "license": "BSD-3-Clause",
5
5
  "type": "module",
6
6
  "repository": {
@@ -22,6 +22,7 @@
22
22
  "color-isolation.js",
23
23
  "depth-test.js",
24
24
  "digital-video.js",
25
+ "dufour.js",
25
26
  "drone-display.js",
26
27
  "effect.js",
27
28
  "focus.js",
@@ -53,5 +54,5 @@
53
54
  "@cesium/core": ">=0.1.0",
54
55
  "@cesium/engine": ">=10"
55
56
  },
56
- "gitHead": "0abb7f3381fdf13b15d50047a46d3789f5f95a9e"
57
+ "gitHead": "71fc611a0eb4caf6ff6d99cbbab64f266fa73dd6"
57
58
  }
@@ -1,2 +1,2 @@
1
1
  // This file is automatically rebuilt from DepthEdges.glsl.
2
- export default "// The edges found in the depth, shared by Toy and WhiteModel, after EyeFromDepth: how far a pixel\n// lies, how large a pixel is there, the depth's neighbors around it, how sharply it bends between\n// them (a crease, not across a jump: an edge in front of something farther, nor the gentle bends\n// between the terrain's triangles), the outline on the near side of a jump; the sky's gradient.\n// its samplers read in high precision, whatever comes before (a phone's GPU's default is low)\nprecision highp sampler2D;\nuniform sampler2D depthTexture;\n\n// how much the depth bends, in pixels' sizes, to start and to end lighting an edge\nconst vec2 CREASE = vec2(0.6, 1.6);\n// how far the depth jumps, in pixels' sizes, to be an edge in front of something farther, not a crease\nuniform float jump;\n// the bevel's widest, in pixels: its neighbors read that far at the most\nconst float BEVEL = 8.0;\n// the sky's depth, far off\nconst float SKY = 1e9;\n\n// how far the pixel at uv lies, in meters: in an orthographic view past its near plane, small\n// numbers whose differences keep their precision (its camera thousands of meters back: some\n// phones' GPUs, Mali, lose it subtracting depths that large), in a perspective one from the camera\nfloat depthAt(vec2 uv) {\n if (czm_orthographicIn3D == 1.0) {\n float raw = texture(depthTexture, uv).r;\n return raw >= 1.0 ? SKY : raw * (czm_currentFrustum.y - czm_currentFrustum.x);\n }\n vec4 eye = eyeAt(depthTexture, uv);\n return eye.w == 0.0 ? SKY : -eye.z;\n}\n\n// a pixel's size in meters at a depth: an orthographic view's the same everywhere, a perspective's\n// growing with the depth\nfloat pixelAt(float depth) {\n return 2.0 / (czm_projection[0][0] * czm_viewport.z) * (czm_orthographicIn3D == 1.0 ? 1.0 : depth);\n}\n\n// the depths of the neighbors steps pixels away from uv, less its own c: right, left, up, down\nvec4 neighborsAt(vec2 uv, float c, float steps) {\n vec2 o = steps / czm_viewport.zw;\n return vec4(depthAt(uv + vec2(o.x, 0.0)), depthAt(uv - vec2(o.x, 0.0)), depthAt(uv + vec2(0.0, o.y)), depthAt(uv - vec2(0.0, o.y))) - c;\n}\n\n// how sharply the depth bends between neighbors n a span of meters apart, from 0 to 1, none across\n// a jump; convex where they lie farther than the middle, their sum positive\nfloat creaseOf(vec4 n, float span, float pixel) {\n float far = max(max(abs(n.x), abs(n.y)), max(abs(n.z), abs(n.w)));\n return smoothstep(CREASE.x, CREASE.y, (abs(n.x + n.y) + abs(n.z + n.w)) / span) * (1.0 - step(jump * pixel, far));\n}\n\n// 1 on the outline a pixel wide, on the near side of a jump: a neighbor much farther; n the\n// neighbors steps pixels away, those a pixel away when steps is 1\nfloat outlineAt(vec2 uv, float c, vec4 n, float steps, float pixel) {\n vec2 texel = 1.0 / czm_viewport.zw;\n float farther = steps > 1.0\n ? max(max(depthAt(uv + vec2(texel.x, 0.0)), depthAt(uv - vec2(texel.x, 0.0))), max(depthAt(uv + vec2(0.0, texel.y)), depthAt(uv - vec2(0.0, texel.y)))) - c\n : max(max(n.x, n.y), max(n.z, n.w));\n return step(jump * pixel, farther);\n}\n\n// the sky's gradient at uv, from the top of the screen to its bottom\nvec3 skyAt(vec2 uv, vec4 top, vec4 bottom) {\n return mix(bottom.rgb, top.rgb, smoothstep(0.0, 1.0, uv.y));\n}\n";
2
+ export default "// The edges found in the depth, shared by Toy and WhiteModel, after EyeFromDepth: how far a pixel\n// lies, how large a pixel is there, the depth's neighbors around it, how sharply it bends between\n// them (a crease, not across a jump: an edge in front of something farther, nor the gentle bends\n// between the terrain's triangles), the outline on the near side of a jump; the sky's gradient.\n// its samplers read in high precision, whatever comes before (a phone's GPU's default is low)\nprecision highp sampler2D;\nuniform sampler2D depthTexture;\n\n// how much the depth bends, in pixels' sizes, to start and to end lighting an edge\nconst vec2 CREASE = vec2(0.6, 1.6);\n// how far the depth jumps, in pixels' sizes, to be an edge in front of something farther, not a crease\nuniform float jump;\n// the bevel's widest, in pixels: its neighbors read that far at the most\nconst float BEVEL = 8.0;\n// the sky's depth, far off\nconst float SKY = 1e9;\n\n// how far the pixel at uv lies, in meters: in an orthographic view past its near plane, small\n// numbers whose differences keep their precision (its camera thousands of meters back: some\n// phones' GPUs, Mali, lose it subtracting depths that large), in a perspective one from the camera\nfloat depthAt(vec2 uv) {\n if (czm_orthographicIn3D == 1.0) {\n float raw = texture(depthTexture, uv).r;\n return raw >= 1.0 ? SKY : raw * (czm_currentFrustum.y - czm_currentFrustum.x);\n }\n vec4 eye = eyeAt(depthTexture, uv);\n return eye.w == 0.0 ? SKY : -eye.z;\n}\n\n// a pixel's size in meters at a depth: an orthographic view's the same everywhere, a perspective's\n// growing with the depth\nfloat pixelAt(float depth) {\n return 2.0 / (czm_projection[0][0] * czm_viewport.z) * (czm_orthographicIn3D == 1.0 ? 1.0 : depth);\n}\n\n// the depths of the neighbors steps pixels away from uv, less its own c: right, left, up, down\nvec4 neighborsAt(vec2 uv, float c, float steps) {\n vec2 o = steps / czm_viewport.zw;\n return vec4(depthAt(uv + vec2(o.x, 0.0)), depthAt(uv - vec2(o.x, 0.0)), depthAt(uv + vec2(0.0, o.y)), depthAt(uv - vec2(0.0, o.y))) - c;\n}\n\n// how sharply the depth bends between neighbors n a span of meters apart, from 0 to 1, none across\n// a jump; convex where they lie farther than the middle, their sum positive\nfloat creaseOf(vec4 n, float span, float pixel) {\n float far = max(max(abs(n.x), abs(n.y)), max(abs(n.z), abs(n.w)));\n return smoothstep(CREASE.x, CREASE.y, (abs(n.x + n.y) + abs(n.z + n.w)) / span) * (1.0 - step(jump * pixel, far));\n}\n\n// how far the surface beyond the pixel at uv lies behind it, in one direction o (a pixel's step): the two neighbours\n// that way extrapolated back to the pixel, linearly in an orthographic view, where the depth is affine on the screen,\n// in its reciprocal in a perspective one. A plane at any slope extrapolates exactly to the pixel, so a face seen at a\n// grazing angle is never a jump, only a nearer thing standing in front of a farther one. The nearer of the extrapolation\n// and the neighbour itself: beside a thin thing two pixels away, the extrapolation through it runs far behind the pixel\nfloat farSide(vec2 uv, vec2 o, float c) {\n float d1 = depthAt(uv + o);\n float d2 = depthAt(uv + 2.0 * o);\n if (d1 == SKY) {\n return SKY;\n }\n if (czm_orthographicIn3D == 1.0) {\n return min(d1, d2 == SKY ? d1 : 2.0 * d1 - d2) - c;\n }\n float r = d2 == SKY ? 1.0 / d1 : 2.0 / d1 - 1.0 / d2;\n return (r <= 0.0 ? d1 : min(d1, 1.0 / r)) - c;\n}\n\n// the outline, a pixel wide, on the near side of a jump: the farthest of the four directions' surfaces behind the pixel,\n// against the jump, anti-aliased over a quarter of it each way (n and steps, the creases' neighbours, unused here: the\n// outline reads its own)\nfloat outlineAt(vec2 uv, float c, vec4 n, float steps, float pixel) {\n vec2 texel = 1.0 / czm_viewport.zw;\n float farther = max(max(farSide(uv, vec2(texel.x, 0.0), c), farSide(uv, vec2(-texel.x, 0.0), c)),\n max(farSide(uv, vec2(0.0, texel.y), c), farSide(uv, vec2(0.0, -texel.y), c)));\n float limit = jump * pixel;\n return smoothstep(0.75 * limit, 1.25 * limit, farther);\n}\n\n// the sky's gradient at uv, from the top of the screen to its bottom\nvec3 skyAt(vec2 uv, vec4 top, vec4 bottom) {\n return mix(bottom.rgb, top.rgb, smoothstep(0.0, 1.0, uv.y));\n}\n";
@@ -0,0 +1,2 @@
1
+ // This file is automatically rebuilt from Dufour.glsl.
2
+ export default "// The Dufour map's look, after EyeFromDepth, DepthEdges, Height, Normal and Hash: the slopes drawn in hachures, ink\n// strokes down the steepest slope, broken along their length, thicker where it is steeper and on the slopes away from a\n// light from the north-west, the flat ground left blank; the silhouettes outlined; black ink on white paper.\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\n// the strokes' spacing on the screen, in pixels, their length in spacings, the ink's weight\nuniform float spacing;\nuniform float strokeLength;\nuniform float weight;\n// how much thinner the strokes facing the light, and thicker away from it, 0 to 1\nuniform float oblique;\n// the slope under which the ground is left blank, radians\nuniform float flatSlope;\nuniform float outline;\nuniform vec3 paper;\nuniform vec3 ink;\n// the hachures' frame, in eye coordinates: its origin on the ground below the camera, east and north there (dufour.js)\nuniform vec3 originEC;\nuniform vec3 eastEC;\nuniform vec3 northEC;\nin vec2 v_textureCoordinates;\n\n// the strokes' finest spacing, in meters\nconst float BASE = 1.0;\n// the strokes' directions: the way down the slope snapped to one of these many, the two nearest blended, so that the\n// strokes are straight lines, parallel on a slope, rather than turning with each pixel's normal\nconst float DIRECTIONS = 12.0;\n// a stroke's breaks: its length in spacings, strokeLength long on average, and the share of it left blank\nconst float BREAK = 0.08;\n// the light, from the north-west, in east and north\nconst vec2 LIGHT = vec2(-0.7071, 0.7071);\n// the ink's share grows with the slope, none on the flat, all of it at 60 degrees (radians), Lehmann's scale\n// stretched for the Alps, whose slopes are mostly past his 45; the\n// widest share of the spacing a stroke covers, the paper showing between them, an engraving's\nconst float LEHMANN = 1.047;\nconst float MAX_COVER = 0.45;\n// the slopes over which the strokes fade in above the flat ground, radians (2 degrees): the terrain's triangles,\n// slightly tilted, would show as patches\nconst float FLAT_RAMP = 0.035;\n\n// the strokes' ink at a point of the ground, en its east and north, s their spacing in meters, angle the direction\n// down the slope they run along, cover the share of the spacing they cover. Each stroke is a straight line along it,\n// broken every strokeLength spacings or so, its breaks staggered from the next stroke's: no line across the strokes\nfloat hachuresAt(vec2 en, float angle, vec2 across, float cover, float s) {\n vec2 down = vec2(-across.y, across.x);\n // a phase of each direction, so that two directions' strokes do not meet in a pattern\n float u = dot(en, across) / s + hash(vec2(angle, s));\n float stroke = floor(u);\n float w = dot(en, down) / (strokeLength * s) + hash(vec2(stroke, s));\n float dash = step(BREAK, fract(w * mix(0.8, 1.25, hash(vec2(s, stroke)))));\n // antialiased, and its mean, the share it covers, where the strokes grow too close to tell apart (a slope seen\n // edge on): no ink where it covers none\n float halfWidth = 0.5 * cover;\n float aa = fwidth(u);\n float sharp = 1.0 - smoothstep(halfWidth - aa, halfWidth + aa, abs(fract(u) - 0.5));\n return mix(sharp * dash, 2.0 * halfWidth * (1.0 - BREAK), smoothstep(0.2, 0.5, aa)) * step(1e-3, halfWidth);\n}\n\n// the strokes in one direction, their spacing a power of two of meters, crossfaded with the next, so that they stay at\n// least spacing pixels apart: taken from the ground a pixel covers across them, far more than its width where a slope\n// is seen at a grazing angle\nfloat directionAt(vec2 en, float angle, float cover) {\n vec2 across = vec2(-sin(angle), cos(angle));\n float lod = log2(max(spacing * fwidth(dot(en, across)), BASE) / BASE);\n float s = BASE * exp2(floor(lod));\n return mix(hachuresAt(en, angle, across, cover, s), hachuresAt(en, angle, across, cover, 2.0 * s), fract(lod));\n}\n\n// the strokes in both of the directions nearest the way down, blended\nfloat strokesAt(vec2 en, vec2 down, float cover) {\n // the strokes are lines: a direction and its opposite are the same\n float t = mod(atan(down.y, down.x), czm_pi) / czm_pi * DIRECTIONS;\n float bin = floor(t);\n float width = czm_pi / DIRECTIONS;\n return mix(directionAt(en, bin * width, cover), directionAt(en, (bin + 1.0) * width, cover), smoothstep(0.4, 0.6, fract(t)));\n}\n\nvoid main() {\n vec2 uv = v_textureCoordinates;\n vec4 sceneColor = texture(colorTexture, uv);\n vec4 eye = eyeAt(depthTexture, uv);\n if (eye.w == 0.0) {\n out_FragColor = vec4(paper, 1.0);\n return;\n }\n float px = czm_pixelRatio;\n // averaged wider than smoothNormalAt: the slopes of the terrain's triangles would set the strokes' width\n vec3 normal = normalize(normalAt(depthTexture, uv, eye.xyz, 4.0 * px) + normalAt(depthTexture, uv, eye.xyz, 12.0 * px) + normalAt(depthTexture, uv, eye.xyz, 24.0 * px));\n float rise = dot(normal, up);\n float slope = acos(clamp(rise, -1.0, 1.0));\n float c = depthAt(uv);\n\n // the way down the slope, the horizontal part of a normal smoothed wider: the strokes' direction would wobble with\n // the terrain's triangles\n vec3 wide = normalize(normal + normalAt(depthTexture, uv, eye.xyz, 32.0 * px));\n vec3 horizontal = wide - dot(wide, up) * up;\n vec2 down = normalize(vec2(dot(horizontal, eastEC), dot(horizontal, northEC)) + 1e-6);\n vec3 rel = eye.xyz - originEC;\n vec2 en = vec2(dot(rel, eastEC), dot(rel, northEC));\n float steep = smoothstep(flatSlope, flatSlope + FLAT_RAMP, slope) * clamp(slope / LEHMANN, 0.0, 1.0);\n float cover = clamp(weight * steep * (1.0 - oblique * dot(down, LIGHT)), 0.0, MAX_COVER);\n // read everywhere, outside any branch, for its derivatives\n float amount = strokesAt(en, down, cover);\n\n if (outline > 0.0) {\n amount = max(amount, outline * outlineAt(uv, c, vec4(0.0), 1.0, pixelAt(c)));\n }\n out_FragColor = vec4(mix(paper, ink, amount), sceneColor.a);\n}\n";
package/shaders/Toy.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // This file is automatically rebuilt from Toy.glsl.
2
- export default "// A toy's look, as an isometric game's, after DepthEdges: the edges lit where the surface bends\n// outward and shaded where it bends inward, rounded as molded plastic close up; an outline along\n// the jumps, on their near side, a darker shade of the color; the colors a little richer and\n// warmer; the sky, if asked, a soft gradient.\n// its samplers read in high precision, whatever comes before (a phone's GPU's default is low)\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\n// how much brighter a convex edge gets, how much darker a concave one\nuniform float light;\nuniform float shade;\n// the edges' bevel, in meters, a pixel at the least; how much darker the outline, 0 for none\nuniform float bevel;\nuniform float outline;\n// the colors' saturation, and a factor each of red, green and blue\nuniform float saturation;\nuniform vec3 warm;\n// the sky's gradient, from the top of the screen to its bottom; w 0 to keep the sky\nuniform vec4 skyTop;\nuniform vec4 skyBottom;\nin vec2 v_textureCoordinates;\n\nvoid main() {\n vec2 uv = v_textureCoordinates;\n vec4 sceneColor = texture(colorTexture, uv);\n float c = depthAt(uv);\n if (c == SKY) {\n out_FragColor = skyTop.w > 0.0 ? vec4(skyAt(uv, skyTop, skyBottom), 1.0) : sceneColor;\n return;\n }\n float pixel = pixelAt(c);\n // the neighbors a bevel's width away: the bend measured over it, strongest on the edge and fading\n // to nothing a bevel off, a rounded edge's highlight\n float steps = clamp(bevel / pixel, 1.0, BEVEL);\n vec4 n = neighborsAt(uv, c, steps);\n float crease = creaseOf(n, steps * pixel, pixel);\n vec3 color = sceneColor.rgb * (n.x + n.y + n.z + n.w > 0.0 ? 1.0 + light * crease : 1.0 - shade * crease);\n if (outline > 0.0) {\n color *= 1.0 - outline * outlineAt(uv, c, n, steps, pixel);\n }\n color = czm_saturation(color, saturation) * warm;\n out_FragColor = vec4(clamp(color, 0.0, 1.0), sceneColor.a);\n}\n";
2
+ export default "// A toy's look, as an isometric game's, after DepthEdges: the edges lit where the surface bends\n// outward and shaded where it bends inward, rounded as molded plastic close up; an outline along\n// the jumps, on their near side, a darker shade of the color; the colors a little richer and\n// warmer; the sky, if asked, a soft gradient.\n// its samplers read in high precision, whatever comes before (a phone's GPU's default is low)\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\n// how much brighter a convex edge gets, how much darker a concave one\nuniform float light;\nuniform float shade;\n// the edges' bevel, in meters, a pixel at the least; how much darker the outline, 0 for none\nuniform float bevel;\nuniform float outline;\n// the colors' saturation, and a factor each of red, green and blue\nuniform float saturation;\nuniform vec3 warm;\n// the sky's gradient, from the top of the screen to its bottom; w 0 to keep the sky\nuniform vec4 skyTop;\nuniform vec4 skyBottom;\nin vec2 v_textureCoordinates;\n\nvoid main() {\n vec2 uv = v_textureCoordinates;\n vec4 sceneColor = texture(colorTexture, uv);\n float c = depthAt(uv);\n if (c == SKY) {\n out_FragColor = skyTop.w > 0.0 ? vec4(skyAt(uv, skyTop, skyBottom), 1.0) : sceneColor;\n return;\n }\n float pixel = pixelAt(c);\n // the neighbors a bevel's width away: the bend measured over it, strongest on the edge and fading\n // to nothing a bevel off, a rounded edge's highlight\n float steps = clamp(bevel / pixel, 1.0, BEVEL);\n vec3 color = sceneColor.rgb;\n // the creases' neighbours read only when they light or shade: a scene drawing its own edges asks for neither\n if (light != 0.0 || shade != 0.0) {\n vec4 n = neighborsAt(uv, c, steps);\n float crease = creaseOf(n, steps * pixel, pixel);\n color *= n.x + n.y + n.z + n.w > 0.0 ? 1.0 + light * crease : 1.0 - shade * crease;\n }\n if (outline > 0.0) {\n color *= 1.0 - outline * outlineAt(uv, c, vec4(0.0), steps, pixel);\n }\n color = czm_saturation(color, saturation) * warm;\n out_FragColor = vec4(clamp(color, 0.0, 1.0), sceneColor.a);\n}\n";
@@ -1,2 +1,2 @@
1
1
  // This file is automatically rebuilt from WhiteModel.glsl.
2
- export default "// A white model's look, as an architect's plaster model or clay render, after DepthEdges: every\n// color turned to its value and the values pressed into a narrow range of whites (an accent's hue\n// kept, if asked; the darkest left black, a section's poché), so the light and the shapes alone\n// tell the surfaces apart; darker where surfaces meet, an ambient obscurance from the depth; the\n// creases lit and shaded, a fine outline along the jumps; the sky, if asked, a pale gradient.\n// its samplers read in high precision, whatever comes before (a phone's GPU's default is low)\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\n// how much brighter a convex edge gets, how much darker a concave one\nuniform float light;\nuniform float shade;\n// the edges' bevel, in meters, a pixel at the least; how much darker the outline, 0 for none\nuniform float bevel;\nuniform float outline;\n// the colors' saturation, 0 for white, and a factor each of red, green and blue: the plaster's tint\nuniform float saturation;\nuniform vec3 warm;\n// the darkest and the lightest values, the scene's black and white; the value under which a color\n// keeps it, 0 for none\nuniform vec2 values;\nuniform float poche;\n// how much darker a corner gets, and how wide its shadow is, in meters\nuniform float occlusion;\nuniform float radius;\n// the sky's gradient, from the top of the screen to its bottom; w 0 to keep the sky\nuniform vec4 skyTop;\nuniform vec4 skyBottom;\nin vec2 v_textureCoordinates;\n\n// the darkest colors' values, under the poché's, kept as they are rather than pressed into the\n// whites, fading into them over these shares of it: an architect's poché, solid black, or a text's\nconst vec2 POCHE = vec2(0.6, 1.4);\n// the occlusion: its widest, in pixels; its samples, on a spiral turned by the golden angle, the\n// spiral itself turned from pixel to pixel; how steeply above the surface a sample rises to count\n// (the normal from the depth is not exact), and how far from the pixel, in meters, a pixel and a\n// half at the least (the depth's own steps on a flat wall not counted); how many pixels wide the\n// disc is to show, fading in (zoomed out, its samples fall within the depth's noise)\nconst float RING = 48.0;\nconst int SAMPLES = 12;\nconst float GOLDEN = 2.39996;\nconst float BIAS = 0.15;\nconst float NEAREST = 0.05;\nconst vec2 SHOWN = vec2(3.0, 8.0);\n\n// how much the surfaces around uv close in over it, from 0 to 1, as the Alchemy ambient obscurance\n// (McGuire et al. 2011): the share of the samples around it, in a disc of the radius, that rise\n// above its surface (its normal from the depth's slopes), each the more as it rises steeper, none\n// farther than the radius; a corner's foot about a half, a flat floor none. p its eye coordinates, n\n// their slopes' normal\nfloat occludedAt(vec2 uv, vec3 p, vec3 n, float pixel) {\n float reach = min(radius, RING * pixel);\n float shown = smoothstep(SHOWN.x, SHOWN.y, reach / pixel);\n if (shown == 0.0) {\n return 0.0;\n }\n // turned toward the eye\n vec3 eye = czm_orthographicIn3D == 1.0 ? vec3(0.0, 0.0, 1.0) : normalize(-p);\n n = dot(n, eye) < 0.0 ? -n : n;\n float nearest = max(NEAREST, 1.5 * pixel);\n float spin = 6.2832 * fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));\n float sum = 0.0;\n for (int i = 0; i < SAMPLES; i++) {\n float a = float(i) * GOLDEN + spin;\n vec2 o = vec2(cos(a), sin(a)) * (float(i) + 0.5) / float(SAMPLES) * reach / pixel / czm_viewport.zw;\n vec4 q = eyeAt(depthTexture, uv + o);\n vec3 v = q.xyz - p;\n float d = length(v);\n if (q.w > 0.0 && d > nearest) {\n sum += max(0.0, dot(v / d, n) - BIAS) * (1.0 - smoothstep(0.5 * reach, reach, d));\n }\n }\n return shown * sum / float(SAMPLES);\n}\n\nvoid main() {\n vec2 uv = v_textureCoordinates;\n // the surface's normal from the depth's slopes, read before any pixel returns, in step with its\n // neighbors\n vec3 p = eyeAt(depthTexture, uv).xyz;\n vec3 normal = normalize(cross(dFdx(p), dFdy(p)));\n vec4 sceneColor = texture(colorTexture, uv);\n float c = depthAt(uv);\n if (c == SKY) {\n out_FragColor = skyTop.w > 0.0 ? vec4(skyAt(uv, skyTop, skyBottom), 1.0) : sceneColor;\n return;\n }\n float pixel = pixelAt(c);\n // the color's value pressed between the darkest and the lightest, but the darkest's; its hue as\n // saturated as asked, scaled down rather than clipped where a channel would pass 1\n float value = czm_luminance(sceneColor.rgb);\n float kept = poche > 0.0 ? 1.0 - smoothstep(POCHE.x * poche, POCHE.y * poche, value) : 0.0;\n float pressed = mix(mix(values.x, values.y, value), value, kept);\n vec3 hue = sceneColor.rgb * pressed / max(value, 1e-4);\n vec3 color = mix(vec3(pressed), hue / max(1.0, max(hue.r, max(hue.g, hue.b))), saturation);\n float steps = clamp(bevel / pixel, 1.0, BEVEL);\n vec4 n = neighborsAt(uv, c, steps);\n float crease = creaseOf(n, steps * pixel, pixel);\n color *= n.x + n.y + n.z + n.w > 0.0 ? 1.0 + light * crease : 1.0 - shade * crease;\n if (occlusion > 0.0) {\n color *= 1.0 - occlusion * occludedAt(uv, p, normal, pixel);\n }\n if (outline > 0.0) {\n color *= 1.0 - outline * outlineAt(uv, c, n, steps, pixel);\n }\n out_FragColor = vec4(clamp(color * warm, 0.0, 1.0), sceneColor.a);\n}\n";
2
+ export default "// A white model's look, as an architect's plaster model or clay render, after DepthEdges: every\n// color turned to its value and the values pressed into a narrow range of whites (an accent's hue\n// kept, if asked; the darkest left black, a section's poché), so the light and the shapes alone\n// tell the surfaces apart; darker where surfaces meet, an ambient obscurance from the depth; the\n// creases lit and shaded, a fine outline along the jumps; the sky, if asked, a pale gradient.\n// its samplers read in high precision, whatever comes before (a phone's GPU's default is low)\nprecision highp sampler2D;\nuniform sampler2D colorTexture;\n// how much brighter a convex edge gets, how much darker a concave one\nuniform float light;\nuniform float shade;\n// the edges' bevel, in meters, a pixel at the least; how much darker the outline, 0 for none\nuniform float bevel;\nuniform float outline;\n// the colors' saturation, 0 for white, and a factor each of red, green and blue: the plaster's tint\nuniform float saturation;\nuniform vec3 warm;\n// the darkest and the lightest values, the scene's black and white; the value under which a color\n// keeps it, 0 for none\nuniform vec2 values;\nuniform float poche;\n// how much darker a corner gets, and how wide its shadow is, in meters\nuniform float occlusion;\nuniform float radius;\n// the sky's gradient, from the top of the screen to its bottom; w 0 to keep the sky\nuniform vec4 skyTop;\nuniform vec4 skyBottom;\nin vec2 v_textureCoordinates;\n\n// the darkest colors' values, under the poché's, kept as they are rather than pressed into the\n// whites, fading into them over these shares of it: an architect's poché, solid black, or a text's\nconst vec2 POCHE = vec2(0.6, 1.4);\n// the occlusion: its widest, in pixels; its samples, on a spiral turned by the golden angle, the\n// spiral itself turned from pixel to pixel; how steeply above the surface a sample rises to count\n// (the normal from the depth is not exact), and how far from the pixel, in meters, a pixel and a\n// half at the least (the depth's own steps on a flat wall not counted); how many pixels wide the\n// disc is to show, fading in (zoomed out, its samples fall within the depth's noise)\nconst float RING = 48.0;\nconst int SAMPLES = 12;\nconst float GOLDEN = 2.39996;\nconst float BIAS = 0.15;\nconst float NEAREST = 0.05;\nconst vec2 SHOWN = vec2(3.0, 8.0);\n\n// how much the surfaces around uv close in over it, from 0 to 1, as the Alchemy ambient obscurance\n// (McGuire et al. 2011): the share of the samples around it, in a disc of the radius, that rise\n// above its surface (its normal from the depth's slopes), each the more as it rises steeper, none\n// farther than the radius; a corner's foot about a half, a flat floor none. p its eye coordinates, n\n// their slopes' normal\nfloat occludedAt(vec2 uv, vec3 p, vec3 n, float pixel) {\n float reach = min(radius, RING * pixel);\n float shown = smoothstep(SHOWN.x, SHOWN.y, reach / pixel);\n if (shown == 0.0) {\n return 0.0;\n }\n // turned toward the eye\n vec3 eye = czm_orthographicIn3D == 1.0 ? vec3(0.0, 0.0, 1.0) : normalize(-p);\n n = dot(n, eye) < 0.0 ? -n : n;\n float nearest = max(NEAREST, 1.5 * pixel);\n float spin = 6.2832 * fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));\n float sum = 0.0;\n for (int i = 0; i < SAMPLES; i++) {\n float a = float(i) * GOLDEN + spin;\n vec2 o = vec2(cos(a), sin(a)) * (float(i) + 0.5) / float(SAMPLES) * reach / pixel / czm_viewport.zw;\n vec4 q = eyeAt(depthTexture, uv + o);\n vec3 v = q.xyz - p;\n float d = length(v);\n if (q.w > 0.0 && d > nearest) {\n sum += max(0.0, dot(v / d, n) - BIAS) * (1.0 - smoothstep(0.5 * reach, reach, d));\n }\n }\n return shown * sum / float(SAMPLES);\n}\n\nvoid main() {\n vec2 uv = v_textureCoordinates;\n // the surface's normal from the depth's slopes, read before any pixel returns, in step with its\n // neighbors\n vec3 p = eyeAt(depthTexture, uv).xyz;\n vec3 normal = normalize(cross(dFdx(p), dFdy(p)));\n vec4 sceneColor = texture(colorTexture, uv);\n float c = depthAt(uv);\n if (c == SKY) {\n out_FragColor = skyTop.w > 0.0 ? vec4(skyAt(uv, skyTop, skyBottom), 1.0) : sceneColor;\n return;\n }\n float pixel = pixelAt(c);\n // the color's value pressed between the darkest and the lightest, but the darkest's; its hue as\n // saturated as asked, scaled down rather than clipped where a channel would pass 1\n float value = czm_luminance(sceneColor.rgb);\n float kept = poche > 0.0 ? 1.0 - smoothstep(POCHE.x * poche, POCHE.y * poche, value) : 0.0;\n float pressed = mix(mix(values.x, values.y, value), value, kept);\n vec3 hue = sceneColor.rgb * pressed / max(value, 1e-4);\n vec3 color = mix(vec3(pressed), hue / max(1.0, max(hue.r, max(hue.g, hue.b))), saturation);\n float steps = clamp(bevel / pixel, 1.0, BEVEL);\n if (light != 0.0 || shade != 0.0) {\n vec4 n = neighborsAt(uv, c, steps);\n float crease = creaseOf(n, steps * pixel, pixel);\n color *= n.x + n.y + n.z + n.w > 0.0 ? 1.0 + light * crease : 1.0 - shade * crease;\n }\n if (occlusion > 0.0) {\n color *= 1.0 - occlusion * occludedAt(uv, p, normal, pixel);\n }\n if (outline > 0.0) {\n color *= 1.0 - outline * outlineAt(uv, c, vec4(0.0), steps, pixel);\n }\n out_FragColor = vec4(clamp(color * warm, 0.0, 1.0), sceneColor.a);\n}\n";
package/toy.js CHANGED
@@ -16,6 +16,9 @@ const skyBottomScratch = new Cartesian4();
16
16
  * outward and shaded where it bends inward, rounded close up, the silhouettes
17
17
  * outlined in a darker shade of their color, the colors a little richer and
18
18
  * warmer, the sky a soft gradient if asked.
19
+ * The edges are found in the depth of the nearest of Cesium's frustums only: with `scene.logarithmicDepthBuffer` off,
20
+ * Cesium splits the view at `farToNearRatio` times the near plane, and what lies past the split has no depth here, so
21
+ * the sky gradient is painted over it; keep the logarithmic depth, or a near plane that keeps the scene in one frustum.
19
22
  */
20
23
  export default class Toy extends Effect {
21
24
  /**