Files
Toastconcern--FrameStudio/tools/splat/splat.vert
T
ToastconcernandClaude Opus 5.5 6d0b4fcd2f v0.2.0: splats, copy/paste, faster homes, seat facing, and a full bug sweep
Frame Studio
- Gaussian splats (PLY, SPZ v2-v4), PLY vertex colours, panorama skies
- Copy, cut, paste and duplicate for objects, splats and world items
- Walkable-area rebake moved off the UI thread; viewport one frame sooner;
  refresh-locked pacing (FS_PACE=timer for the old one)
- Fixed: bundle thumbnail use-after-free crash, auto-save deleting
  environments, saves into scratch copies, edits lost on non-ASCII paths,
  unopenable manifests with inf/nan, bundle save data loss, opening a home
  marking it changed, gizmo NaN, long paths, many malformed-file crashes
- Updater verifies sha256, journals the swap and never leaves the exe missing

Frame Home
- Seats turn you to face their way
- Draw batching (Hand Tutorials up to ~20% less GPU time), cut-out EQUAL pass,
  late head-pose read before submit
- Proper queue-family hand-over to the compositor layer; layer descriptor
  generations and seqlocked slots; structural SPIR-V check
- Fixed: hangs/crashes on damaged .fenv files and shaders, render-pass
  validation error, sync hazard in the foveation smoothing pass, unverified
  update downloads, half-done installs

Tests: tests_loco/env_share/spv_check wired into CMake (FRAME_TESTS) and CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VsaXGU27gsrXjDoFtyFJP9
2026-10-25 13:15:07 -05:00

63 lines
3.5 KiB
GLSL

#version 450
// Gaussian splats (src/splat_import.h), one instanced quad each, both eyes in one multiview draw.
// Each splat: a 3D Gaussian (centre, covariance) projected to a 2D ellipse on screen, the quad its 3-sigma box.
// Drawn back to front (the order buffer: sorted on the CPU, farthest first) with premultiplied alpha.
#extension GL_EXT_multiview : require
struct Splat {
vec3 pos; // model space
uint cov01; // covariance (model space): xx xy, xz yy, yz zz as half floats
uint cov23;
uint cov45;
uint rgba; // base colour (sRGB) and opacity, 8 bits each
uint pad;
};
layout(std430, set = 0, binding = 0) readonly buffer Splats { Splat splats[]; };
layout(std430, set = 0, binding = 1) readonly buffer Order { uint order[]; };
layout(std140, set = 0, binding = 2) uniform Frame {
mat4 clipFromWorld[2]; // both eyes (gl_ViewIndex)
mat4 worldFromModel; // the splat object's transform
vec4 viewport; // width, height (pixels per eye), 1/width, 1/height
} u;
layout(location = 0) out vec2 vSigma; // the corner in standard deviations along the ellipse's axes
layout(location = 1) out vec4 vColor;
const vec2 kCorner[4] = vec2[](vec2(-1, -1), vec2(1, -1), vec2(-1, 1), vec2(1, 1)); // a triangle strip
const float kExtent = 3.0; // the quad reaches 3 sigma
vec3 srgbToLinear(vec3 c) { return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c)); }
void main() {
Splat s = splats[order[gl_InstanceIndex]];
vec2 c01 = unpackHalf2x16(s.cov01), c23 = unpackHalf2x16(s.cov23), c45 = unpackHalf2x16(s.cov45);
mat3 cov = mat3(c01.x, c01.y, c23.x,
c01.y, c23.y, c45.x,
c23.x, c45.x, c45.y);
mat3 m3 = mat3(u.worldFromModel);
mat3 covW = m3 * cov * transpose(m3);
vec4 world = u.worldFromModel * vec4(s.pos, 1.0);
mat4 cfw = u.clipFromWorld[gl_ViewIndex];
vec4 clip = cfw * world;
// behind the eye, or well off screen: no quad (a point outside the clip volume)
if (clip.w <= 0.01 || any(greaterThan(abs(clip.xy), vec2(1.3 * clip.w)))) { gl_Position = vec4(0, 0, -2, 1); return; }
// The projection's Jacobian at the centre: d(ndc.xy)/d(world), from the clip matrix's rows; in pixels.
vec3 rx = vec3(cfw[0][0], cfw[1][0], cfw[2][0]), ry = vec3(cfw[0][1], cfw[1][1], cfw[2][1]), rw = vec3(cfw[0][3], cfw[1][3], cfw[2][3]);
float iw = 1.0 / clip.w;
vec3 jx = (rx - clip.x * iw * rw) * iw * (0.5 * u.viewport.x);
vec3 jy = (ry - clip.y * iw * rw) * iw * (0.5 * u.viewport.y);
// the screen-space covariance (pixels^2), with a little blur so a splat never shrinks below a pixel
float a = dot(jx, covW * jx) + 0.3, b = dot(jx, covW * jy), d = dot(jy, covW * jy) + 0.3;
float mid = 0.5 * (a + d), radius = sqrt(max(0.1, mid * mid - (a * d - b * b)));
float l1 = mid + radius, l2 = max(0.1, mid - radius);
vec2 axis = abs(b) > 1e-6 ? normalize(vec2(b, l1 - a)) : (a >= d ? vec2(1, 0) : vec2(0, 1));
vec2 major = axis * sqrt(l1), minor = vec2(-axis.y, axis.x) * sqrt(l2);
if (max(length(major), length(minor)) * kExtent < 0.5) { gl_Position = vec4(0, 0, -2, 1); return; } // under half a pixel
vec2 corner = kCorner[gl_VertexIndex];
vec2 offsetPx = (corner.x * major + corner.y * minor) * kExtent;
gl_Position = clip + vec4(offsetPx * 2.0 * u.viewport.zw * clip.w, 0, 0);
vSigma = corner * kExtent;
vec4 c = unpackUnorm4x8(s.rgba);
vColor = vec4(srgbToLinear(c.rgb), c.a);
}