Files
3sixty2cubemap/src/gpu/shaders/c2e.wgsl
T

113 lines
4.0 KiB
WebGPU Shading Language

// Stage 2 helper: cubemap -> equirect (NEAREST, for binary masks).
//
// The exact inverse of `equirect_to_cubemap.wgsl`'s `xyzcube` geometry, using a
// gnomonic (tangent) projection: the cube-surface coordinate for a face is
// 0.5 * (in-plane component / face-normal component), which inverts the linear
// cube-surface grid used by e2c. For each equirect output pixel we compute the
// 3D direction from (lon, lat), pick the dominant-axis cube face (F/R/B/L/U/D),
// invert that face's `xyzcube` mapping to a face pixel (j, i), and nearest-
// sample the matching face mask texture. This is the consistent inverse of our
// e2c kernel (round-trip e2c -> c2e reproduces the source within edge loss),
// and is close to py360convert.c2e(..., mode="nearest").
//
// Bindings 1..6 are the six face mask textures in FACES order [F,R,B,L,U,D],
// each `face_n x face_n`, RGBA8 (mask value replicated across RGB).
struct Params {
out_w: u32,
out_h: u32,
face_n: u32,
pad: u32,
}
@group(0) @binding(0) var<uniform> params: Params;
@group(0) @binding(1) var faceF: texture_2d<f32>;
@group(0) @binding(2) var faceR: texture_2d<f32>;
@group(0) @binding(3) var faceB: texture_2d<f32>;
@group(0) @binding(4) var faceL: texture_2d<f32>;
@group(0) @binding(5) var faceU: texture_2d<f32>;
@group(0) @binding(6) var faceD: texture_2d<f32>;
@group(0) @binding(7) var out_tex: texture_storage_2d<rgba8unorm, write>;
const PI: f32 = 3.141592653589793;
fn sample_face(id: u32, j: i32, i: i32) -> vec4<f32> {
let n = i32(params.face_n);
let cj = clamp(j, 0, n - 1);
let ci = clamp(i, 0, n - 1);
switch id {
case 0u: { return textureLoad(faceF, vec2<i32>(cj, ci), 0); }
case 1u: { return textureLoad(faceR, vec2<i32>(cj, ci), 0); }
case 2u: { return textureLoad(faceB, vec2<i32>(cj, ci), 0); }
case 3u: { return textureLoad(faceL, vec2<i32>(cj, ci), 0); }
case 4u: { return textureLoad(faceU, vec2<i32>(cj, ci), 0); }
case 5u: { return textureLoad(faceD, vec2<i32>(cj, ci), 0); }
default: { return vec4<f32>(0.0); }
}
}
@compute @workgroup_size(8, 8, 1)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let w = params.out_w;
let h = params.out_h;
if (gid.x >= w || gid.y >= h) { return; }
let px = f32(gid.x);
let py = f32(gid.y);
let u_ = ((px + 0.5) / f32(w) - 0.5) * 2.0 * PI; // lon
let v_ = -((py + 0.5) / f32(h) - 0.5) * PI; // lat
let cv = cos(v_);
let dx = cv * sin(u_);
let dy = sin(v_);
let dz = cv * cos(u_);
let ax = abs(dx);
let ay = abs(dy);
let az = abs(dz);
let nm1 = f32(params.face_n - 1u);
var face_id: u32;
var jf: f32;
var if_: f32;
if (ay >= ax && ay >= az) {
if (dy > 0.0) {
// U: j=0.5*(x/y+1)*nm1, i=0.5*(z/y+1)*nm1
face_id = 4u;
jf = 0.5 * (dx / dy + 1.0) * nm1;
if_ = 0.5 * (dz / dy + 1.0) * nm1;
} else {
// D: j=0.5*(1-x/y)*nm1, i=0.5*(1+z/y)*nm1
face_id = 5u;
jf = 0.5 * (1.0 - dx / dy) * nm1;
if_ = 0.5 * (1.0 + dz / dy) * nm1;
}
} else if (ax >= az) {
if (dx > 0.0) {
// R: j=0.5*(1-z/x)*nm1, i=0.5*(1-y/x)*nm1
face_id = 1u;
jf = 0.5 * (1.0 - dz / dx) * nm1;
if_ = 0.5 * (1.0 - dy / dx) * nm1;
} else {
// L: j=0.5*(1-z/x)*nm1, i=0.5*(1+y/x)*nm1
face_id = 3u;
jf = 0.5 * (1.0 - dz / dx) * nm1;
if_ = 0.5 * (1.0 + dy / dx) * nm1;
}
} else {
if (dz > 0.0) {
// F: j=0.5*(x/z+1)*nm1, i=0.5*(1-y/z)*nm1
face_id = 0u;
jf = 0.5 * (dx / dz + 1.0) * nm1;
if_ = 0.5 * (1.0 - dy / dz) * nm1;
} else {
// B: j=0.5*(x/z+1)*nm1, i=0.5*(1+y/z)*nm1
face_id = 2u;
jf = 0.5 * (dx / dz + 1.0) * nm1;
if_ = 0.5 * (1.0 + dy / dz) * nm1;
}
}
let color = sample_face(face_id, i32(round(jf)), i32(round(if_)));
textureStore(out_tex, vec2<i32>(gid.xy), vec4<f32>(color.rgb, 1.0));
}