mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +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
61 lines
3.1 KiB
GLSL
61 lines
3.1 KiB
GLSL
// The light in the air at a point (volumetric.frag, froxel.comp): the sun through its shadow
|
|
// cascades, and the dynamic lights of that point's cluster with a one-tap shadow each. Needs
|
|
// FRAME, LIGHT, CELL, LIGHT_INDEX and SHADOW_VIEW (model.glsl with KEEL_FRAME_ONLY).
|
|
float sunlitAt(vec3 w) {
|
|
float depth = dot(w - 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;
|
|
vec4 p = FRAME.cascades[c] * vec4(w, 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;
|
|
vec2 base = vec2(c & 1, c >> 1) * 0.5;
|
|
float t = FRAME.shadow.x;
|
|
return shadowTap(uint(FRAME.sunDir.w), base + uv * 0.5, base + t, base + 0.5 - t, p.z - 0.0005);
|
|
}
|
|
|
|
// The light cluster a point in the air is in (render/clusters.h), from its pixel's place on screen.
|
|
const uvec3 kClusters = uvec3(16, 9, 24);
|
|
const float kClusterNear = 8.0, kClusterFar = 16384.0;
|
|
uint clusterAt(vec3 w, vec2 uv) {
|
|
float depth = dot(w - 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(uv * vec2(kClusters.xy)), kClusters.xy - 1u);
|
|
return (z * kClusters.y + tile.y) * kClusters.x + tile.x;
|
|
}
|
|
float phaseHG(float mu, float g) { return (1.0 - g * g) / (12.566 * pow(1.0 + g * g - 2.0 * g * mu, 1.5)); }
|
|
// The dynamic lights lighting the air at w (towards the eye along -rd).
|
|
vec3 lightsAt(vec3 w, vec3 rd, vec2 uv) {
|
|
if (FRAME.camPos.w < 0.5) return vec3(0.0);
|
|
uvec2 cell = CELL(clusterAt(w, uv));
|
|
vec3 total = vec3(0.0);
|
|
for (uint k = 0u; k < min(cell.y, 8u); ++k) {
|
|
Light l = LIGHT(LIGHT_INDEX(cell.x + k));
|
|
vec3 toLight = l.posRadius.xyz - w;
|
|
float dist = length(toLight);
|
|
if (dist >= l.posRadius.w) continue;
|
|
vec3 L = toLight / max(dist, 0.001);
|
|
float a = 1.0 - dist / l.posRadius.w, 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) { // shadowed: one tap in its atlas tile
|
|
int view = int(l.color.w);
|
|
if (l.spot.w <= -1.5) {
|
|
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);
|
|
}
|
|
ShadowView sv = SHADOW_VIEW(view);
|
|
vec4 c = sv.viewProj * vec4(w, 1.0);
|
|
if (c.w > 0.0) {
|
|
c.xyz /= c.w;
|
|
if (all(lessThanEqual(abs(c.xy), vec2(1.0))) && c.z <= 1.0) {
|
|
float texel = 1.0 / FRAME.screen.w;
|
|
vec2 at = sv.rect.xy + (c.xy * 0.5 + 0.5) * sv.rect.zw;
|
|
atten *= shadowTap(uint(FRAME.screen.z), at, sv.rect.xy + texel, sv.rect.xy + sv.rect.zw - texel, c.z - 0.00005);
|
|
}
|
|
}
|
|
}
|
|
total += l.color.rgb * atten * phaseHG(dot(rd, -L), 0.3);
|
|
}
|
|
return total;
|
|
}
|
|
|