mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 12:34:27 +00:00
Shapes with live settings, shape editing, knife and hole tools, drawing shapes onto other objects, combining objects (add / subtract / overlap), copies, thickness, painting with stickers and layers, a Layout tab, a Print tab with checks, packed .fry files with autosave, export to GLB, glTF, FBX, OBJ, 3MF, STL and PLY, folders in Layers, its own crash screen, and a website for GitHub Pages in docs/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NMSrd5oe1GpUD7E6d825KP
99 lines
4.8 KiB
GLSL
99 lines
4.8 KiB
GLSL
// Clustered dynamic lights for mesh.frag. Grid constants match render/clusters.h.
|
||
const uvec3 kClusters = uvec3(16, 9, 24);
|
||
const float kClusterNear = 8.0, kClusterFar = 16384.0;
|
||
|
||
uint clusterIndex(vec3 world) {
|
||
float depth = dot(world - FRAME.camPos.xyz, FRAME.camFwd.xyz);
|
||
uint z = depth <= kClusterNear ? 0u : min(uint(log(depth / kClusterNear) / log(kClusterFar / kClusterNear) * float(kClusters.z)), kClusters.z - 1u);
|
||
uvec2 tile = min(uvec2(gl_FragCoord.xy / FRAME.screen.xy * vec2(kClusters.xy)), kClusters.xy - 1u);
|
||
return (z * kClusters.y + tile.y) * kClusters.x + tile.x;
|
||
}
|
||
|
||
// 1 = lit. 3×3 bilinear PCF taps inside the light's tile of the shadow atlas.
|
||
float atlasShadow(int view, vec3 world, vec3 n, float dist) {
|
||
ShadowView sv = SHADOW_VIEW(view);
|
||
vec4 c = sv.viewProj * vec4(world + n * (0.75 + dist * 0.006), 1.0);
|
||
if (c.w <= 0.0) return 1.0;
|
||
c.xyz /= c.w;
|
||
if (any(greaterThan(abs(c.xy), vec2(1.0))) || c.z > 1.0) return 1.0;
|
||
float texel = 1.0 / FRAME.screen.w;
|
||
vec2 lo = sv.rect.xy + texel, hi = sv.rect.xy + sv.rect.zw - texel;
|
||
vec2 at = sv.rect.xy + (c.xy * 0.5 + 0.5) * sv.rect.zw;
|
||
uint atlas = uint(FRAME.screen.z);
|
||
float lit = 0.0;
|
||
for (int y = -1; y <= 1; ++y)
|
||
for (int x = -1; x <= 1; ++x) lit += shadowTap(atlas, at + vec2(x, y) * texel, lo, hi, c.z - 0.00002);
|
||
return lit / 9.0;
|
||
}
|
||
|
||
// GGX specular: height-correlated Smith visibility, Schlick Fresnel (F0: g_f0).
|
||
// Times π, because our light values are "what a white wall shows" (diffuse = albedo × light).
|
||
// Multiply by n·l and the light. rough: perceptual roughness.
|
||
// Reflectance at normal incidence for this surface (mesh.frag sets it: 0.04 for dielectrics,
|
||
// the albedo for metals).
|
||
vec3 g_f0 = vec3(0.04);
|
||
|
||
vec3 specGGX(vec3 n, vec3 l, vec3 v, float rough) {
|
||
vec3 h = normalize(l + v);
|
||
float a = max(rough * rough, 0.002), a2 = a * a;
|
||
float ndl = max(dot(n, l), 1e-4), ndv = max(dot(n, v), 1e-4), ndh = max(dot(n, h), 0.0), vdh = max(dot(v, h), 0.0);
|
||
float q = ndh * ndh * (a2 - 1.0) + 1.0;
|
||
float d = a2 / (3.14159265 * q * q);
|
||
float vis = 0.5 / (ndl * sqrt(ndv * ndv * (1.0 - a2) + a2) + ndv * sqrt(ndl * ndl * (1.0 - a2) + a2));
|
||
vec3 f = g_f0 + (1.0 - g_f0) * pow(1.0 - vdh, 5.0);
|
||
return min(3.14159265 * d * vis * f, vec3(64.0));
|
||
}
|
||
|
||
// Light reaching the surface from every dynamic light in its cluster: diffuse (returned) and
|
||
// the highlights (spec), sharper and stronger the glossier the surface.
|
||
vec3 dynamicLights(vec3 world, vec3 n, float rough, vec3 v, out uint count, out vec3 spec) {
|
||
count = 0u;
|
||
spec = vec3(0.0);
|
||
if (FRAME.camPos.w < 0.5) return vec3(0.0);
|
||
// A mirror's pass sees from somewhere else than the clusters were made for: every light.
|
||
bool every = FRAME.mirror.y > 0.5;
|
||
uvec2 cell = every ? uvec2(0u, min(uint(FRAME.camPos.w), 64u)) : CELL(clusterIndex(world));
|
||
count = cell.y;
|
||
vec3 total = vec3(0.0);
|
||
for (uint k = 0u; k < cell.y; ++k) {
|
||
Light l = LIGHT(every ? k : LIGHT_INDEX(cell.x + k));
|
||
vec3 toLight = l.posRadius.xyz - world;
|
||
float dist = length(toLight);
|
||
if (dist >= l.posRadius.w) continue;
|
||
vec3 L = toLight / max(dist, 0.001);
|
||
float ndl = dot(n, L);
|
||
if (ndl <= 0.0) continue;
|
||
float a = 1.0 - dist / l.posRadius.w;
|
||
float atten = a * a;
|
||
if (l.spot.w > -1.5) atten *= spotShape(l, dot(-L, l.spot.xyz));
|
||
if (atten <= 0.0) continue;
|
||
if (l.color.w >= 0.0) {
|
||
int view = int(l.color.w);
|
||
if (l.spot.w <= -1.5) { // point light: one atlas tile per cube face, +X -X +Y -Y +Z -Z
|
||
vec3 d = -toLight, ad = abs(d);
|
||
view += ad.x >= ad.y && ad.x >= ad.z ? (d.x > 0.0 ? 0 : 1) : ad.y >= ad.z ? (d.y > 0.0 ? 2 : 3) : (d.z > 0.0 ? 4 : 5);
|
||
}
|
||
atten *= atlasShadow(view, world, n, dist);
|
||
}
|
||
total += l.color.rgb * atten * ndl;
|
||
// Area light (a sphere of radius extra.y): the highlight comes from the point on it
|
||
// nearest the reflected ray, and widens to match (Karis 2013, "representative point").
|
||
vec3 Ls = L;
|
||
float norm = 1.0;
|
||
if (l.extra.y > 0.0) {
|
||
vec3 r = reflect(-v, n);
|
||
vec3 toRay = dot(toLight, r) * r - toLight;
|
||
vec3 closest = toLight + toRay * clamp(l.extra.y / max(length(toRay), 1e-3), 0.0, 1.0);
|
||
Ls = normalize(closest);
|
||
float a = max(rough * rough, 0.002), a2 = clamp(a + l.extra.y / (2.0 * dist), 0.0, 1.0);
|
||
norm = (a / a2) * (a / a2);
|
||
}
|
||
spec += l.color.rgb * atten * ndl * specGGX(n, Ls, v, rough) * norm;
|
||
}
|
||
return total;
|
||
}
|
||
|
||
vec3 heatmap(uint count) {
|
||
float t = clamp(float(count) / 16.0, 0.0, 1.0);
|
||
return count == 0u ? vec3(0.0, 0.0, 0.15) : mix(vec3(0.0, 0.4, 1.0), vec3(1.0, 0.1, 0.0), t);
|
||
}
|