mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +00:00
Fry3DX 0.4: an easy 3D modeller
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
This commit is contained in:
commit
f3e8eca87b
135 changed files with 23423 additions and 0 deletions
99
engine/shaders/lights.glsl
Normal file
99
engine/shaders/lights.glsl
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// 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);
|
||||
}
|
||||
Loading…
Reference in a new issue