mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 20:44:26 +00:00
380 lines
21 KiB
GLSL
380 lines
21 KiB
GLSL
#version 460
|
||
#include "backend.glsl"
|
||
#include "model.glsl"
|
||
#include "lights.glsl"
|
||
#include "sky.glsl"
|
||
|
||
layout(location = 0) in vec3 v_normal;
|
||
layout(location = 1) in vec2 v_uv;
|
||
layout(location = 2) in vec2 v_lightmapUv;
|
||
layout(location = 3) in vec3 v_world;
|
||
layout(location = 0) out vec4 o_color;
|
||
layout(location = 1) out vec4 o_gbuf; // view normal (octahedral), roughness, reflection weight: for ssr.frag
|
||
|
||
vec2 octEncode(vec3 n) {
|
||
n /= abs(n.x) + abs(n.y) + abs(n.z);
|
||
if (n.z < 0.0) n.xy = (1.0 - abs(n.yx)) * vec2(n.x >= 0.0 ? 1.0 : -1.0, n.y >= 0.0 ? 1.0 : -1.0);
|
||
return n.xy * 0.5 + 0.5;
|
||
}
|
||
|
||
// 1 = sunlit. Cascade by view distance, normal offset by the cascade's texel size,
|
||
// 3×3 bilinear PCF taps (a smooth 4×4 filter).
|
||
float sunShadow(vec3 n) {
|
||
if (FRAME.sunColor.w < 0.5) return 1.0;
|
||
float depth = dot(v_world - FRAME.camPos.xyz, FRAME.camFwd.xyz);
|
||
if (depth > FRAME.splits.w) return 1.0;
|
||
int c = depth < FRAME.splits.x ? 0 : depth < FRAME.splits.y ? 1 : depth < FRAME.splits.z ? 2 : 3;
|
||
float texel = FRAME.cascadeTexel[c];
|
||
vec4 p = FRAME.cascades[c] * vec4(v_world + n * texel * 1.5, 1.0);
|
||
vec2 uv = p.xy * 0.5 + 0.5;
|
||
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))) || p.z > 1.0) return 1.0;
|
||
float atlasTexel = FRAME.shadow.x;
|
||
vec2 base = vec2(c & 1, c >> 1) * 0.5;
|
||
vec2 lo = base + atlasTexel, hi = base + 0.5 - atlasTexel;
|
||
vec2 at = base + uv * 0.5;
|
||
uint map = uint(FRAME.sunDir.w);
|
||
float lit = 0.0;
|
||
float soft = FRAME.shadow.w; // the sun's apparent size (r_shadowsoft); 0 = the plain 3x3 filter
|
||
if (soft <= 0.0) {
|
||
for (int y = -1; y <= 1; ++y)
|
||
for (int x = -1; x <= 1; ++x) lit += shadowTap(map, at + vec2(x, y) * atlasTexel, lo, hi, p.z - 0.0002);
|
||
lit /= 9.0;
|
||
} else {
|
||
// PCSS: how far above this point the things shading it are (blocker search), then a
|
||
// filter that widens with that gap, so shadows are sharp where they touch and soft away.
|
||
const vec2 disk[12] = vec2[](vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621),
|
||
vec2(0.962, -0.195), vec2(0.473, -0.480), vec2(0.519, 0.767), vec2(0.185, -0.893),
|
||
vec2(0.507, 0.064), vec2(0.896, 0.412), vec2(-0.322, -0.933), vec2(-0.792, -0.598));
|
||
float spin = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))) * 6.2831853;
|
||
mat2 rot = mat2(cos(spin), sin(spin), -sin(spin), cos(spin));
|
||
float blockers = 0.0, found = 0.0;
|
||
for (int i = 0; i < 12; ++i) {
|
||
float d = sampleTexLod(map, clamp(at + rot * disk[i] * atlasTexel * 6.0, lo, hi), 0.0).r;
|
||
if (d < p.z - 0.0002) blockers += d, found += 1.0;
|
||
}
|
||
if (found == 0.0) return 1.0;
|
||
float gap = (p.z - blockers / found) * 16000.0; // world units (the cascades' depth range)
|
||
float width = clamp(gap * 0.012 * soft / texel, 1.0, 8.0); // in shadow texels
|
||
for (int i = 0; i < 12; ++i) lit += shadowTap(map, at + rot * disk[i] * atlasTexel * width, lo, hi, p.z - 0.0002);
|
||
lit /= 12.0;
|
||
}
|
||
// Fade out over the last 10% of the shadow distance instead of a hard edge.
|
||
float fade = clamp((FRAME.splits.w - depth) / (FRAME.splits.w * 0.1), 0.0, 1.0);
|
||
return mix(1.0, lit, fade);
|
||
}
|
||
|
||
// Smooth value noise, for ripples and the ragged edges of puddles.
|
||
float hash3(vec3 p) { return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453); }
|
||
float vnoise(vec3 p) {
|
||
vec3 i = floor(p), f = fract(p);
|
||
f = f * f * (3.0 - 2.0 * f);
|
||
return mix(mix(mix(hash3(i), hash3(i + vec3(1, 0, 0)), f.x), mix(hash3(i + vec3(0, 1, 0)), hash3(i + vec3(1, 1, 0)), f.x), f.y),
|
||
mix(mix(hash3(i + vec3(0, 0, 1)), hash3(i + vec3(1, 0, 1)), f.x), mix(hash3(i + vec3(0, 1, 1)), hash3(i + vec3(1, 1, 1)), f.x), f.y), f.z);
|
||
}
|
||
|
||
// Tangent frame from screen-space derivatives (no stored tangents needed): T along +u, B along +v.
|
||
mat3 cotangentFrame(vec3 n, vec3 p, vec2 uv) {
|
||
vec3 dp1 = dFdx(p), dp2 = dFdy(p);
|
||
vec2 duv1 = dFdx(uv), duv2 = dFdy(uv);
|
||
vec3 dp2perp = cross(dp2, n), dp1perp = cross(n, dp1);
|
||
vec3 t = dp2perp * duv1.x + dp1perp * duv2.x;
|
||
vec3 b = dp2perp * duv1.y + dp1perp * duv2.y;
|
||
float invmax = inversesqrt(max(max(dot(t, t), dot(b, b)), 1e-20));
|
||
return mat3(t * invmax, b * invmax, n);
|
||
}
|
||
|
||
// Image-based light: what the split-sum integral of GGX × Fresnel comes to (F0 scale, bias),
|
||
// fitted analytically (Karis) instead of a lookup texture.
|
||
vec2 envBRDF(float rough, float ndv) {
|
||
const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022), c1 = vec4(1.0, 0.0425, 1.04, -0.04);
|
||
vec4 r = rough * c0 + c1;
|
||
float a004 = min(r.x * r.x, exp2(-9.28 * ndv)) * r.x + r.y;
|
||
return vec2(-1.04, 1.04) * a004 + r.zw;
|
||
}
|
||
|
||
bool hasSky() { return FRAME.sky.x > 0.5; }
|
||
vec3 sky(vec3 dir, float level) { return skyRadiance(uint(FRAME.sky.x), FRAME.sky.y, FRAME.sky.w, dir, level); }
|
||
// The sky's light on a surface facing n (irradiance / π), from its spherical harmonics.
|
||
vec3 skyLight(vec3 n) {
|
||
return FRAME.skySH[0].rgb * 0.282095 + FRAME.skySH[1].rgb * 0.488603 * n.y + FRAME.skySH[2].rgb * 0.488603 * n.z
|
||
+ FRAME.skySH[3].rgb * 0.488603 * n.x + FRAME.skySH[4].rgb * 1.092548 * n.x * n.y + FRAME.skySH[5].rgb * 1.092548 * n.y * n.z
|
||
+ FRAME.skySH[6].rgb * 0.315392 * (3.0 * n.z * n.z - 1.0) + FRAME.skySH[7].rgb * 1.092548 * n.x * n.z
|
||
+ FRAME.skySH[8].rgb * 0.546274 * (n.x * n.x - n.y * n.y);
|
||
}
|
||
float luma(vec3 c) { return dot(c, vec3(0.2126, 0.7152, 0.0722)); }
|
||
|
||
// Height fog / aerial perspective: denser low down, coloured by the sky behind (blurred) and
|
||
// brighter towards the sun. Returns the colour seen through `dist` of it.
|
||
#ifndef KEEL_GL
|
||
#include "froxel.glsl"
|
||
#define GI_PROBES FRAME.giProbes
|
||
#include "gi.glsl"
|
||
#endif
|
||
vec3 applyFog(vec3 color, vec3 world) {
|
||
vec3 ray = world - FRAME.camPos.xyz;
|
||
float dist = length(ray);
|
||
vec3 rd = ray / max(dist, 1e-3);
|
||
if (!hasSky() || FRAME.fog.x <= 0.0) return color;
|
||
float k = FRAME.fog.y;
|
||
float start = FRAME.fog.x * exp(-k * (FRAME.camPos.z - FRAME.fog.w));
|
||
float kz = k * rd.z;
|
||
float amount = start * (abs(kz) > 1e-6 ? (1.0 - exp(-kz * dist)) / kz : dist);
|
||
float f = min(1.0 - exp(-amount), FRAME.fog.z);
|
||
vec3 fogColor = sky(normalize(vec3(rd.xy, max(rd.z, 0.05))), FRAME.sky.z) * 0.9;
|
||
if (FRAME.sunColor.w > 0.5) fogColor += FRAME.sunColor.rgb * pow(max(dot(rd, FRAME.sunDir.xyz), 0.0), 8.0) * 0.25;
|
||
return mix(color, fogColor, f);
|
||
}
|
||
|
||
vec3 ambientCube(vec3 n) {
|
||
vec3 w = n * n;
|
||
return w.x * (n.x > 0.0 ? DRAW.cube[0].rgb : DRAW.cube[1].rgb)
|
||
+ w.y * (n.y > 0.0 ? DRAW.cube[2].rgb : DRAW.cube[3].rgb)
|
||
+ w.z * (n.z > 0.0 ? DRAW.cube[4].rgb : DRAW.cube[5].rgb);
|
||
}
|
||
|
||
void main() {
|
||
// A mirror's pass: nothing behind the mirror (the wall it's on, the room beyond).
|
||
if (dot(FRAME.clip.xyz, FRAME.clip.xyz) > 0.5 && dot(v_world, FRAME.clip.xyz) < FRAME.clip.w) discard;
|
||
// The mirror itself: the picture its pass made, where this pixel is (slightly darkened, as
|
||
// real mirrors are), and nothing for the screen-space reflections to add.
|
||
if (FRAME.mirror.x > 0.5 && (pc.textures & 0xFFFFu) == uint(FRAME.mirror.x)) {
|
||
o_color = vec4(fetchTex(uint(FRAME.mirror.x), ivec2(gl_FragCoord.xy)).rgb * vec3(0.9, 0.92, 0.92), 1.0);
|
||
o_gbuf = vec4(0.5, 0.5, 1.0, 0.0);
|
||
return;
|
||
}
|
||
vec4 albedo = sampleTex(pc.textures & 0xFFFFu, v_uv);
|
||
if (albedo.a < 0.5) discard; // alpha-tested texture (fences, foliage)
|
||
vec4 tint = unpackUnorm4x8(pc.tint);
|
||
vec3 base = albedo.rgb * tint.rgb;
|
||
vec3 n = normalize(v_normal);
|
||
vec3 geo = n; // the surface itself, before relief
|
||
vec3 view = normalize(FRAME.camPos.xyz - v_world);
|
||
// Relief and gloss from the material map. Derivatives first, while control flow is uniform.
|
||
mat3 tbn = cotangentFrame(n, v_world, v_uv);
|
||
float gloss = 0.15, metal = 0.0;
|
||
uint materialMap = pc.material & 0xFFFFu, skyVisMap = pc.material >> 16;
|
||
if (materialMap != 0u) {
|
||
vec4 m = sampleTex(materialMap, v_uv);
|
||
vec2 xy = m.xy * 2.0 - 1.0; // X, Y; Z rebuilt (B holds metalness)
|
||
n = normalize(tbn * vec3(xy, sqrt(max(1.0 - dot(xy, xy), 0.0))));
|
||
metal = m.z;
|
||
gloss = m.w;
|
||
} else if (DRAW.probe.w > 0.5) { // the model's own surface (Renderer::Surface)
|
||
uint s = uint(DRAW.probe.w - 0.5);
|
||
metal = float(s & 255u) / 255.0;
|
||
gloss = 1.0 - float((s >> 8) & 255u) / 255.0;
|
||
}
|
||
// Weather: how open to the sky this spot is (the lightmap's sky map; moving things: their
|
||
// sun visibility), then rain wetting it (darker, glossy) and snow lying on what faces up.
|
||
float exposed = skyVisMap != 0u ? sampleTex(skyVisMap, v_lightmapUv).r : DRAW.params.y > 0.5 ? DRAW.params.x : 0.0;
|
||
exposed = smoothstep(0.25, 0.7, exposed);
|
||
if (FRAME.weather.x > 0.0) {
|
||
float wet = FRAME.weather.x * exposed * (0.45 + 0.55 * max(geo.z, 0.0));
|
||
base *= 1.0 - 0.45 * wet;
|
||
gloss = mix(gloss, 0.93, wet * 0.85);
|
||
}
|
||
float snowCover = 0.0;
|
||
if (FRAME.weather.y > 0.0) {
|
||
float snow = FRAME.weather.y * exposed * smoothstep(0.35, 0.8, geo.z) * smoothstep(0.2, 0.6, vnoise(v_world * 0.05) * 0.5 + FRAME.weather.y);
|
||
snowCover = snow;
|
||
// Snow isn't flat white: drifts a little brighter, hollows a little bluer.
|
||
float drift = vnoise(v_world * 0.012) * 0.6 + vnoise(v_world * 0.05) * 0.4;
|
||
base = mix(base, mix(vec3(0.78, 0.82, 0.9), vec3(0.9, 0.91, 0.93), drift), snow);
|
||
gloss = mix(gloss, 0.25, snow);
|
||
metal *= 1.0 - snow;
|
||
}
|
||
int liquid = int(DRAW.params.w + 0.5);
|
||
float liquidAlpha = 1.0;
|
||
if (liquid == 1) {
|
||
// A film of liquid: thickest in the middle (UV v = 0), ragged at the edge, rippled.
|
||
float thickness = 1.0 - v_uv.y;
|
||
float ragged = vnoise(v_world * 0.35) * 0.6 + vnoise(v_world * 1.1) * 0.4;
|
||
float edge = 0.12 + 0.45 * ragged;
|
||
if (thickness < edge) discard;
|
||
liquidAlpha = smoothstep(edge, edge + 0.2, thickness);
|
||
float e = 0.05;
|
||
vec3 q = v_world * 0.6;
|
||
vec3 grad = vec3(vnoise(q + vec3(e, 0, 0)) - vnoise(q - vec3(e, 0, 0)), vnoise(q + vec3(0, e, 0)) - vnoise(q - vec3(0, e, 0)),
|
||
vnoise(q + vec3(0, 0, e)) - vnoise(q - vec3(0, 0, e))) / (2.0 * e);
|
||
n = normalize(n - (grad - n * dot(grad, n)) * 0.12);
|
||
// Deeper liquid is richer in colour (Beer–Lambert-ish); thin edges just wet the surface.
|
||
base = mix(base * 0.35, base, 0.4 + 0.6 * thickness);
|
||
gloss = 0.95;
|
||
} else if (liquid == 2) {
|
||
// A body of liquid: light passes through it, more of it where it's thicker. Looking
|
||
// straight through the middle is the longest path (rich, dense); the edges are thin.
|
||
float through = abs(dot(n, view));
|
||
base = mix(base * 1.15, base * 0.5, through);
|
||
liquidAlpha = mix(0.45, 1.0, through);
|
||
gloss = 0.92;
|
||
}
|
||
if (liquid == 4) {
|
||
// A decal (a burn mark) on a disc: matte, darkest in the middle, its edge soft and ragged.
|
||
float thickness = 1.0 - v_uv.y;
|
||
float ragged = vnoise(v_world * 0.12) * 0.6 + vnoise(v_world * 0.45) * 0.4;
|
||
liquidAlpha = smoothstep(0.0, 0.9, thickness + (ragged - 0.5) * 0.6);
|
||
gloss = 0.08;
|
||
}
|
||
if (liquid == 3) {
|
||
// Open water: waves (a few travelling swells plus noise ripples) bend the normal; looking
|
||
// straight down you see into it, at a grazing angle it mirrors the sky (Fresnel, below).
|
||
float t = DRAW.probe.z;
|
||
vec2 p = v_world.xy, g = vec2(0.0);
|
||
vec2 d1 = vec2(0.8, 0.6), d2 = vec2(-0.5, 0.86), d3 = vec2(0.2, -0.98);
|
||
g += d1 * cos(dot(p, d1) * 0.035 + t * 1.1) * 0.09;
|
||
g += d2 * cos(dot(p, d2) * 0.07 + t * 1.7) * 0.06;
|
||
g += d3 * cos(dot(p, d3) * 0.13 + t * 2.3) * 0.04;
|
||
// Two layers of ripples drifting different ways (the wind, and the surface's own churn).
|
||
float e = 0.5;
|
||
vec3 q = vec3(p * 0.09 + vec2(t * 0.25, t * 0.1), t * 0.35), q2 = vec3(p * 0.21 - vec2(t * 0.12, t * 0.3), t * 0.6);
|
||
g += vec2(vnoise(q + vec3(e, 0, 0)) - vnoise(q - vec3(e, 0, 0)), vnoise(q + vec3(0, e, 0)) - vnoise(q - vec3(0, e, 0))) * 0.3;
|
||
g += vec2(vnoise(q2 + vec3(e, 0, 0)) - vnoise(q2 - vec3(e, 0, 0)), vnoise(q2 + vec3(0, e, 0)) - vnoise(q2 - vec3(0, e, 0))) * 0.18;
|
||
n = normalize(geo + vec3(-g, 0.0));
|
||
float through = abs(dot(n, view));
|
||
base = mix(base * 1.25, base * 0.6, through);
|
||
liquidAlpha = mix(0.95, 0.62, through);
|
||
gloss = 0.97;
|
||
}
|
||
if (DRAW.params.z > 0.5) {
|
||
// Liquid: cut away above the surface; looking in through the cut, the inside (back)
|
||
// faces stand in for the flat surface.
|
||
if (dot(v_world, DRAW.plane.xyz) > DRAW.plane.w) discard;
|
||
if (!gl_FrontFacing) {
|
||
n = DRAW.plane.xyz;
|
||
base *= 1.2;
|
||
}
|
||
}
|
||
// Metals reflect in their own colour and scatter nothing: F0 is the albedo, diffuse goes.
|
||
g_f0 = mix(vec3(0.04), base, metal);
|
||
base *= 1.0 - metal;
|
||
vec3 sunDir = FRAME.sunDir.xyz;
|
||
// Relief can't light what the surface itself faces away from (no light leaking round corners).
|
||
float facing = dot(geo, sunDir) > 0.0 ? max(dot(n, sunDir), 0.0) : 0.0;
|
||
vec3 sun = FRAME.sunColor.rgb * FRAME.sunColor.w * facing;
|
||
float rough = clamp(1.0 - gloss, 0.05, 1.0);
|
||
vec3 sunSpec = FRAME.sunColor.rgb * FRAME.sunColor.w * facing * specGGX(n, sunDir, view, rough);
|
||
// How much open sky this spot sees (reflections of the sky fade with it).
|
||
float skyVis = 0.0;
|
||
// Cavities in the relief (grout, pores) get less of the soft, baked light.
|
||
float cavity = clamp(1.0 - (1.0 - dot(n, geo)) * 2.5, 0.45, 1.0);
|
||
vec3 specular = vec3(0.0), env = vec3(0.0);
|
||
bool realtime = FRAME.shadow.z > 0.5;
|
||
uint lightmap = pc.textures >> 16;
|
||
vec3 light;
|
||
if (lightmap != 0u) {
|
||
vec4 rgbm = sampleTex(lightmap, v_lightmapUv); // RGBM, range 8
|
||
vec3 baked = rgbm.rgb * rgbm.a * 8.0 * cavity;
|
||
// Night: what the sky gave (most of the light where it's open) goes; lamps stay.
|
||
baked *= 1.0 - FRAME.weather.z * 0.9 * smoothstep(0.0, 0.45, skyVisMap != 0u ? sampleTex(skyVisMap, v_lightmapUv).r : 0.0);
|
||
env = baked;
|
||
skyVis = skyVisMap != 0u ? sampleTex(skyVisMap, v_lightmapUv).r : 0.0;
|
||
if (realtime) {
|
||
float vis = facing > 0.0 ? sunShadow(geo) : 0.0;
|
||
light = baked + sun * vis;
|
||
specular = sunSpec * vis;
|
||
} else { // old maps: the sun is baked in; players and props darken it where they block it
|
||
float shade = 1.0 - FRAME.shadow.y * (1.0 - sunShadow(n)) * facing * FRAME.sunColor.w;
|
||
light = baked * shade;
|
||
}
|
||
} else if (DRAW.params.y > 0.5) {
|
||
// Moving things: baked ambient cube plus the sun. With a real-time sun the shadow map
|
||
// already has the world in it; otherwise the probe's sun visibility stands in for it.
|
||
float vis = facing > 0.0 ? sunShadow(geo) : 0.0;
|
||
vis = realtime ? vis : DRAW.params.x * vis;
|
||
env = ambientCube(n) * cavity * (1.0 - FRAME.weather.z * 0.8 * max(DRAW.params.x, 0.35));
|
||
light = env + sun * vis;
|
||
// Probes don't know where the sky is: compare their light with the open sky's.
|
||
if (hasSky()) skyVis = clamp(luma(env) / max(luma(skyLight(n)), 1e-3), 0.0, 1.0) * mix(0.6, 1.0, DRAW.params.x);
|
||
specular = sunSpec * vis;
|
||
} else if (hasSky()) {
|
||
// No baked light of its own (water, map-less things): the sky's light here and the real
|
||
// sun or moon, through its shadow — so it darkens at night like everything else.
|
||
float vis = facing > 0.0 ? sunShadow(geo) : 0.0;
|
||
env = skyLight(n);
|
||
light = env + sun * vis;
|
||
specular = sunSpec * vis;
|
||
} else {
|
||
light = vec3(sunLight(n));
|
||
}
|
||
uint count;
|
||
vec3 dynSpec;
|
||
light += dynamicLights(v_world, n, rough, view, count, dynSpec);
|
||
#ifndef KEEL_GL
|
||
if (FRAME.gi.x > 0.5) {
|
||
vec3 bounced = giIrradiance(v_world, n, view) * cavity; // their light, bounced
|
||
if (FRAME.gi.z > 0.5) { o_color = vec4(bounced * 4.0, 1.0); o_gbuf = vec4(0.0); return; }
|
||
light += bounced;
|
||
}
|
||
#endif
|
||
// Reflections (split-sum image-based light): the sky, prefiltered for this roughness, where
|
||
// the sky is open; the light arriving here stands in for the room where it isn't.
|
||
float ndv = clamp(dot(n, view), 1e-4, 1.0);
|
||
vec3 r = reflect(-view, n);
|
||
r = normalize(r + geo * max(-dot(r, geo), 0.0) * 1.05); // keep it above the surface
|
||
vec2 ab = envBRDF(rough, ndv);
|
||
float occlusion = skyVis * skyVis;
|
||
// What's around: the nearest reflection probe (env_cubemap) where there is one, else the
|
||
// light arriving here; the sky takes over as far as this spot sees it.
|
||
// (probes were baked in daylight: at night what they show goes dark too)
|
||
vec3 near = DRAW.probe.x > 0.5 ? skyRadiance(uint(DRAW.probe.x), 1.0 - 0.92 * FRAME.weather.z, 0.0, r, rough * DRAW.probe.y) : env;
|
||
vec3 surroundings = hasSky() ? mix(near, sky(r, rough * FRAME.sky.z), occlusion) : near;
|
||
vec3 reflection = surroundings * (g_f0 * ab.x + ab.y) * cavity;
|
||
vec3 color = base * light + specular + dynSpec + reflection;
|
||
// Snow glitters: here and there a crystal catches the sun (or a lamp) towards the eye.
|
||
if (snowCover > 0.05) {
|
||
vec3 cell = floor(v_world * 1.7);
|
||
float h = fract(sin(dot(cell, vec3(12.9898, 78.233, 45.164))) * 43758.5453);
|
||
float glint = step(0.975, h) * pow(max(dot(n, normalize(sunDir + view)), 0.0), 40.0);
|
||
color += (FRAME.sunColor.rgb * FRAME.sunColor.w * (facing > 0.0 ? 1.0 : 0.0) + dynSpec * 4.0) * glint * snowCover * 1.2;
|
||
}
|
||
float alpha = tint.a * liquidAlpha;
|
||
if (liquid == 3) {
|
||
// Open water barely scatters light of its own: it's what's mirrored (more the flatter
|
||
// you look: Fresnel) over what's seen through it (the bottom, more looking straight
|
||
// down), tinted by a dim veil of its own colour. Blended over what's behind it, so:
|
||
// alpha = the share it covers, colour = what covers it.
|
||
vec3 mirrored = hasSky() ? mix(env, sky(r, 0.0), occlusion) : vec3(0.45, 0.6, 0.8);
|
||
float fres = 0.02 + 0.98 * pow(1.0 - clamp(abs(dot(n, view)), 0.0, 1.0), 5.0);
|
||
float clear = 0.72 * clamp(abs(dot(geo, view)), 0.0, 1.0); // (how much of the bottom shows through)
|
||
vec3 veil = base * (light * 0.3 + vec3(0.015));
|
||
float cover = fres + (1.0 - fres) * (1.0 - clear);
|
||
color = (mirrored * fres + veil * (1.0 - fres) * (1.0 - clear)) / max(cover, 1e-3);
|
||
color += FRAME.sunColor.rgb * FRAME.sunColor.w * facing * specGGX(n, sunDir, view, 0.06) + dynSpec; // (the sun's glint, lamps')
|
||
alpha = clamp(cover, 0.0, 1.0);
|
||
} else if (liquid != 0 && liquid != 4) {
|
||
// Wet: a sharp sun glint and the sky mirrored at grazing angles, on top of what's lit.
|
||
vec3 mirrored = hasSky() ? mix(env, sky(r, 0.0), occlusion) : vec3(0.45, 0.6, 0.8);
|
||
float fres = 0.02 + 0.98 * pow(1.0 - clamp(dot(n, view), 0.0, 1.0), 5.0);
|
||
color += mirrored * fres * 0.9 + FRAME.sunColor.rgb * FRAME.sunColor.w * facing * specGGX(n, sunDir, view, 0.08);
|
||
if (liquid == 1) alpha = clamp(alpha * (0.75 + fres), 0.0, 0.95);
|
||
}
|
||
if (alpha < 0.5 && liquid != 4 && liquid != 3) {
|
||
// Glass (clear materials; denser translucent things like liquid or cracks shade normally): more reflective at grazing angles (Fresnel), plus a sharp sun glint.
|
||
vec3 v = normalize(FRAME.camPos.xyz - v_world);
|
||
vec3 nf = dot(n, v) < 0.0 ? -n : n; // the side facing us, whatever the winding
|
||
float fresnel = pow(1.0 - clamp(dot(nf, v), 0.0, 1.0), 4.0);
|
||
float glint = pow(max(dot(nf, normalize(sunDir + v)), 0.0), 150.0) * FRAME.sunColor.w;
|
||
// Glass barely scatters light itself: mostly what's behind it, plus reflections.
|
||
vec3 rg = reflect(-v, nf);
|
||
vec3 mirrored = hasSky() ? mix(env, sky(rg, 0.0), max(occlusion, 0.25)) : vec3(0.45, 0.6, 0.8);
|
||
color = color * 0.12 + FRAME.sunColor.rgb * glint * 3.0 + mirrored * fresnel * 0.8;
|
||
// A neutral sheen where it turns away from you (no colour: grey to white), so glass reads
|
||
// as glass at an angle.
|
||
color += vec3(0.06 + fresnel * 0.35) * (hasSky() ? max(occlusion, 0.4) : 0.6);
|
||
alpha = clamp(alpha * 0.6 + fresnel * 0.5 + glint, 0.0, 1.0);
|
||
}
|
||
#ifndef KEEL_GL
|
||
if (FRAME.froxel.x > 0.5) { // the fog grid: lit, shadowed fog (and the air's light)
|
||
vec4 f = froxelFog(gl_FragCoord.xy / FRAME.screen.xy, dot(v_world - FRAME.camPos.xyz, FRAME.camFwd.xyz));
|
||
color = color * f.a + f.rgb;
|
||
} else
|
||
#endif
|
||
color = applyFog(color, v_world);
|
||
o_color = vec4(color, alpha);
|
||
// View space as the post passes see it: x right, y down the screen, z forward.
|
||
vec3 fwd = FRAME.camFwd.xyz, right = normalize(cross(fwd, vec3(0.0, 0.0, 1.0))), up = cross(right, fwd);
|
||
vec3 nv = vec3(dot(n, right), -dot(n, up), dot(n, fwd));
|
||
float reflectWeight = dot(g_f0 * ab.x + ab.y, vec3(0.333)) * cavity * (liquid != 0 ? 1.5 : 1.0);
|
||
o_gbuf = vec4(octEncode(nv), rough, reflectWeight);
|
||
if (FRAME.camFwd.w > 0.5) o_color.rgb = mix(o_color.rgb, heatmap(count), 0.6);
|
||
}
|