scene: terrain fine grain domain-warped so it stops reading as hatching #66
3 changed files with 21 additions and 5 deletions
|
|
@ -114,6 +114,8 @@ describe('shader agrees with the pure function on its numbers', () => {
|
|||
expect(GRID_FRAG).toContain(`#define ALB_WOBBLE_S ${ALBEDO.wobbleSlope}`);
|
||||
expect(GRID_FRAG).toContain(`#define ALB_GRAIN_COARSE ${ALBEDO.grainCoarse}`);
|
||||
expect(GRID_FRAG).toContain(`#define ALB_STRATA ${ALBEDO.strata}`);
|
||||
expect(GRID_FRAG).toContain(`#define ALB_FINE_WARP ${ALBEDO.fineWarp.toFixed(1)}`);
|
||||
expect(GRID_FRAG).toContain('waves3(warpq(qy),');
|
||||
// the only fetch for colour-by-material would be a texture; the height map's texelFetch is the only other read, and the sky lookup is the baked scattering table's
|
||||
expect(GRID_FRAG.match(/texelFetch|texture\((?!uSky|uAP)/g)).toHaveLength(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ export const ALBEDO = {
|
|||
/** Albedo grain: relative swing of the two sine-product octaves (about 20 m and 5 m periods), and of the rock strata. */
|
||||
grainCoarse: 0.35,
|
||||
grainFine: 0.3,
|
||||
/** Metres the fine octave's coordinates are pushed by a slower sine, so its parallel stripes bend and do not read as hatching. */
|
||||
fineWarp: 7,
|
||||
/** The fine octave fades out between these distances (metres): beyond them it is under a pixel and only shimmers. */
|
||||
fineNear: 250,
|
||||
fineFar: 700,
|
||||
|
|
@ -132,6 +134,13 @@ const FINE = [
|
|||
] as const;
|
||||
const waves = (k: ReadonlyArray<readonly [number, number]>, a: number, b: number) =>
|
||||
k.reduce((sum, [ka, kb]) => sum + Math.sin(a * ka + b * kb), 0) / 3;
|
||||
/** `waves` of coordinates warped by a sine each (the shader's `warpq`): three plane waves alone make a regular lattice. */
|
||||
const wavesWarped = (k: ReadonlyArray<readonly [number, number]>, a: number, b: number) =>
|
||||
waves(
|
||||
k,
|
||||
a + ALBEDO.fineWarp * Math.sin(b * 0.55 + 1.3),
|
||||
b + ALBEDO.fineWarp * Math.sin(a * 0.47 + 0.7)
|
||||
);
|
||||
|
||||
/**
|
||||
* The multiplier on albedo that gives the surface grain and the rock its layers, at a surface point (metres) with unit
|
||||
|
|
@ -148,10 +157,12 @@ export function grainAt(
|
|||
distance: number
|
||||
): number {
|
||||
const w = triWeights(n);
|
||||
const over = (k: ReadonlyArray<readonly [number, number]>) =>
|
||||
w[0] * waves(k, z, y) + w[1] * waves(k, x, z) + w[2] * waves(k, x, y);
|
||||
const coarse = over(COARSE);
|
||||
const fine = over(FINE) * (1 - smooth(ALBEDO.fineNear, ALBEDO.fineFar, distance));
|
||||
const over = (
|
||||
k: ReadonlyArray<readonly [number, number]>,
|
||||
f: typeof waves
|
||||
) => w[0] * f(k, z, y) + w[1] * f(k, x, z) + w[2] * f(k, x, y);
|
||||
const coarse = over(COARSE, waves);
|
||||
const fine = over(FINE, wavesWarped) * (1 - smooth(ALBEDO.fineNear, ALBEDO.fineFar, distance));
|
||||
const layers = Math.sin(y * 0.9 + wobbleAt(x, y, z, n) * 3);
|
||||
return Math.max(
|
||||
0,
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ const ALBEDO_DEFINES = `
|
|||
#define ALB_WOBBLE_S ${lit(ALBEDO.wobbleSlope)}
|
||||
#define ALB_GRAIN_COARSE ${lit(ALBEDO.grainCoarse)}
|
||||
#define ALB_GRAIN_FINE ${lit(ALBEDO.grainFine)}
|
||||
#define ALB_FINE_WARP ${lit(ALBEDO.fineWarp)}
|
||||
#define ALB_STRATA ${lit(ALBEDO.strata)}
|
||||
#define ALB_FINE_NEAR ${lit(ALBEDO.fineNear)}
|
||||
#define ALB_FINE_FAR ${lit(ALBEDO.fineFar)}
|
||||
|
|
@ -175,6 +176,8 @@ ${FETCH}
|
|||
// Triplanar patterns: each projection plane's value, blended by the weights of the face's normal (terrainAlbedo.triWeights).
|
||||
float wob2(vec2 q) { return sin(q.x * 0.043 + q.y * 0.017) * sin(q.y * 0.051 - q.x * 0.023); }
|
||||
float waves3(vec2 q, vec2 a, vec2 b, vec2 c) { return (sin(dot(q, a)) + sin(dot(q, b)) + sin(dot(q, c))) / 3.0; }
|
||||
// The fine octave's coordinates pushed by a slower sine each (terrainAlbedo wavesWarped): three plane waves alone make a regular lattice.
|
||||
vec2 warpq(vec2 q) { return q + ALB_FINE_WARP * vec2(sin(q.y * 0.55 + 1.3), sin(q.x * 0.47 + 0.7)); }
|
||||
void main() {
|
||||
// Normal from the bilinear height at a continuous position, a stencil 1.5 texels each side: smooth across texel borders.
|
||||
vec2 p = v_uv * float(uTex - 1);
|
||||
|
|
@ -195,7 +198,7 @@ void main() {
|
|||
vec3 albedo = mix(mix(ground, uSnow, snow), uRock, rockW);
|
||||
// Surface grain and rock strata, the same arithmetic as terrainAlbedo.grainAt().
|
||||
float coarse = tw.x * waves3(qx, vec2(0.31, 0.27), vec2(-0.22, 0.35), vec2(0.38, -0.12)) + tw.y * waves3(qy, vec2(0.31, 0.27), vec2(-0.22, 0.35), vec2(0.38, -0.12)) + tw.z * waves3(qz, vec2(0.31, 0.27), vec2(-0.22, 0.35), vec2(0.38, -0.12));
|
||||
float fine = tw.x * waves3(qx, vec2(1.1, 0.7), vec2(-0.8, 1.2), vec2(1.3, -0.6)) + tw.y * waves3(qy, vec2(1.1, 0.7), vec2(-0.8, 1.2), vec2(1.3, -0.6)) + tw.z * waves3(qz, vec2(1.1, 0.7), vec2(-0.8, 1.2), vec2(1.3, -0.6));
|
||||
float fine = tw.x * waves3(warpq(qx), vec2(1.1, 0.7), vec2(-0.8, 1.2), vec2(1.3, -0.6)) + tw.y * waves3(warpq(qy), vec2(1.1, 0.7), vec2(-0.8, 1.2), vec2(1.3, -0.6)) + tw.z * waves3(warpq(qz), vec2(1.1, 0.7), vec2(-0.8, 1.2), vec2(1.3, -0.6));
|
||||
float near = 1.0 - smoothstep(ALB_FINE_NEAR, ALB_FINE_FAR, length(v_world - uCamera));
|
||||
float layers = sin(v_world.y * 0.9 + w * 3.0);
|
||||
albedo *= max(0.0, 1.0 + ALB_GRAIN_COARSE * coarse + ALB_GRAIN_FINE * near * fine + ALB_STRATA * rockW * layers);
|
||||
|
|
|
|||
Loading…
Reference in a new issue