Files
OneCAD/crates/ui-egui/src/pbr_gpu.rs
T
selune a261aaa841 Render: integrated workspace, preview image jobs and opaque PBR (#1046)
Lands the Render workspace (R3), bounded preview-image jobs with cancellation and atomic PNG export (R4a), and opaque PBR looks as one change.

- Render workspace: an overlay-free product preview on the installed GPU device. Returning to Design restores its camera. render.scene and render.settings.apply are added.
- Image jobs: render.preflight/start/status/cancel/export run bounded CPU preview jobs. Export captures the job, overwrite choice and document, and checks the caller's file scope before writing.
- Opaque PBR: looks can carry metal fraction, roughness and dielectric IOR. The product viewport and preview PNG use them; geometry and mass are unchanged. The v1 catalog export refuses PBR rather than dropping it.
- Render setups and PBR looks are saved under design format 21. Labels are in en/de/ja/ko/zh/ru.

Supersedes #706 and #800. Refs #620, #673, #756, #1004.

Contributed by @0xlillith.

Co-authored-by: lillith <35337584+0xlillith@users.noreply.github.com>
2026-10-14 19:24:36 -04:00

79 lines
3.0 KiB
Rust

//! Original GPU translation of the L1 opaque surface and fixed studio reference.
//! Appended to the existing viewport shader so camera/output conventions are shared.
pub const VERTEX_SIZE: usize = 48;
pub const SOURCE: &str = r#"
struct PbrOut {
@builtin(position) pos: vec4<f32>,
@location(0) n: vec3<f32>,
@location(1) base: vec3<f32>,
@location(2) wp: vec3<f32>,
@location(3) parameters: vec3<f32>,
};
fn pbr_unit(v: vec3<f32>) -> vec3<f32> {
let scale = max(max(abs(v.x), abs(v.y)), abs(v.z));
if (scale == 0.0) { return vec3<f32>(0.0, 0.0, 1.0); }
let s = v / scale;
return s / length(s);
}
fn pbr_encode(c: vec3<f32>) -> vec3<f32> {
return select(c * 12.92, 1.055 * pow(c, vec3<f32>(1.0 / 2.4)) - vec3<f32>(0.055), c > vec3<f32>(0.0031308));
}
@vertex
fn vs_pbr(@location(0) p: vec3<f32>, @location(1) n: vec3<f32>, @location(2) base: vec3<f32>, @location(3) parameters: vec3<f32>) -> PbrOut {
var o: PbrOut;
o.pos = u.vp * vec4<f32>(p, 1.0);
o.n = n;
o.base = base;
o.wp = p;
o.parameters = parameters;
return o;
}
fn pbr_fresnel(f0: vec3<f32>, cosine: f32) -> vec3<f32> {
let t = 1.0 - cosine;
return f0 + (vec3<f32>(1.0) - f0) * (t * t * t * t * t);
}
fn pbr_brdf(base: vec3<f32>, parameters: vec3<f32>, n: vec3<f32>, v: vec3<f32>, l: vec3<f32>) -> vec3<f32> {
let nv = clamp(dot(n, v), -1.0, 1.0);
let nl = clamp(dot(n, l), -1.0, 1.0);
if (nv <= 0.0 || nl <= 0.0) { return vec3<f32>(0.0); }
let h = pbr_unit(v + l);
let nh = clamp(dot(n, h), 0.0, 1.0);
let vh = clamp(dot(v, h), 0.0, 1.0);
let alpha = parameters.y * parameters.y;
let a2 = alpha * alpha;
let denominator = (1.0 - nh * nh) + nh * nh * a2;
let distribution = a2 / (3.141592653589793 * denominator * denominator);
let gv = 2.0 / (nv + sqrt(a2 + (1.0 - a2) * nv * nv));
let gl = 2.0 / (nl + sqrt(a2 + (1.0 - a2) * nl * nl));
let ratio = (parameters.z - 1.0) / (parameters.z + 1.0);
let f0 = mix(vec3<f32>(ratio * ratio), base, parameters.x);
let specular = pbr_fresnel(f0, vh) * (distribution * 0.25 * gv * gl);
let diffuse = base * ((1.0 - parameters.x) / 3.141592653589793);
return specular + (vec3<f32>(1.0) - pbr_fresnel(f0, nv)) * (vec3<f32>(1.0) - pbr_fresnel(f0, nl)) * diffuse;
}
@fragment
fn fs_pbr(i: PbrOut) -> @location(0) vec4<f32> {
let n = pbr_unit(i.n);
var view = u.back.xyz;
if (u.eye.w > 0.5) { view = u.eye.xyz - i.wp; }
let v = pbr_unit(view);
let lights = array<vec4<f32>, 3>(vec4<f32>(0.6, 0.0, 0.8, 3.0), vec4<f32>(-0.8, 0.0, 0.6, 1.0), vec4<f32>(0.0, -0.6, 0.8, 1.5));
var radiance = vec3<f32>(0.0);
for (var k = 0u; k < 3u; k += 1u) {
let light = lights[k];
radiance += pbr_brdf(i.base, i.parameters, n, v, light.xyz) * (light.w * max(dot(n, light.xyz), 0.0));
}
let mapped = radiance / (vec3<f32>(1.0) + radiance);
if (u.back.w > 0.5) { return vec4<f32>(mapped, 1.0); }
return vec4<f32>(pbr_encode(mapped), 1.0);
}
"#;