Files
solvecraft/docs/render-raytrace-dielectrics.md

14 KiB
Raw Permalink Blame History

Bounded CPU dielectric preview (#1655)

This page describes CPU dielectric transport without explicit Path. The published source's renderer, document, engine and workspace tests executed in CI7; the full gate remains red and native Glass visual acceptance is pending. Explicit Path has the separate general Glass contract. See qualification. The render and engine crates keep raytrace-cpu optional; the application controls whether its normal build enables it. The CPU executor remains native only. This unit reuses the image-job host, queue, cancellation, admission lease, incremental BVH traversal and PNG encoder.

render.preflight and render.start accept explicit runtime material overrides on the CPU route (integrator: "raytrace", without progressive: true). GPU progressive requests retain their own dielectric schema and transport. Neither path infers glass from a saved appearance's alpha value.

Without Path, CPU preflight reports transparency: "physical_transmission" when its request contains admitted dielectric overrides, with dielectric_boundaries limited to ["thin_sheet","closed_box"]. Without overrides it reports "opaque_only" and an empty boundary list. This metadata describes the requested transport; both CPU raytrace cases still require surface alpha one. The optional transparent PNG background exports primary geometry coverage; glass does not gain a compositing matte for a different transmitted background.

The native host's descriptor_for(Raytrace) advertises refraction in its Preview / Image / PNG profile, and CPU requests containing dielectric overrides require that capability during both preflight and host submission. This is the bounded thin_sheet / closed_box contract below, shared by fixed and progressive CPU sampling: it does not advertise general closed-mesh transmission, caustics or diffuse global illumination. Raster and the cooperative CPU host do not advertise this capability. The render.backends registry publishes the additional cpu-raytrace descriptor only for an installed native CPU worker with the build feature enabled. New unsaved setups use CPU progressive when this host is available and legacy defaults are off; saved, legacy and offline setups retain their existing route. The explicit CPU raytrace route resolves its own integrator-specific descriptor. See backend discovery.

A CPU request example:

{"integrator":"raytrace","settings":{"width":320,"height":240},
 "raytrace":{"shadows":false,"environment":{"color":[1,1,1],"intensity":1},"dielectrics":[
   {"body_index":0,"body_name":"exact captured body and occurrence name",
    "boundary":"closed_box","ior":1.5,
    "absorption_per_mm":[0.1,0,0],"thickness_mm":0}
 ]}}

Both the captured ordinal and exact captured name must match. There are at most eight overrides, each with a name of 1–1024 bytes. IOR is finite in 1–3; absorption channels are finite in 0–100 inverse millimetres. Missing overrides use the established opaque surface. Alpha is still required to be one and never selects glass. These values do not change geometry, mass, appearances, undo or document persistence. There is no new saved render setup or format change.

closed_box is a deliberately strict certificate: exactly twelve nondegenerate triangles at the corners of a positive-size world-axis-aligned box, with outward winding and two complementary triangles tiling each of its six faces. Extra subdivision, rotated boxes, curved solids, incomplete faces, duplicate coverage and unknown boundaries refuse. A mesh flag or a user-selected boundary enum does not establish closure. thickness_mm must be zero: actual geometric entry/exit positions determine distance. The original outward geometric normal and front face flag select the interface; interpolated shading normals do not bend rays. Parallel slab entry/exit produces the Snell lateral displacement within this certified subset. This does not certify arbitrary CAD tessellations.

thin_sheet accepts one nondegenerate planar triangle or two consistently wound, coplanar triangles sharing one unique edge and lying on opposite sides of it. It has no retained interior medium. Its explicit virtual normal thickness is finite in 0–10000 mm, and is used only for absorption along thickness/cos(theta_t). Zero virtual thickness uses zero optical distance, including exact grazing. With equal indices this yields zero reflection and unit transmission. Positive virtual thickness at index-matched grazing is explicitly unsupported because the ray does not cross the slab; it is not classified as total internal reflection. The effective reflection/transmission weights sum incoherent internal returns between two parallel interfaces. Transmission keeps the original ray direction and starts at the same surface: there is no lateral displacement, modeled second geometric face or thin-film interference. Closed slabs are not two sheets.

The smooth-interface model uses Snell refraction, exact unpolarized Fresnel and total internal reflection. Bulk attenuation is exp(-sigma * distance_mm) per channel, measuring distance between the original geometric boundary positions before numerical origin offsets. Radiance transport includes the squared IOR ratio at transmission; a parallel air/glass/air traversal cancels these factors. All contributions accumulate in linear light, followed by exposure, Reinhard and sRGB encoding once. The implementation is original safe Rust written from public optics equations; no rendering-engine source was consulted or adapted. The Snell equation was consulted as a mathematical reference only; no text or assets were copied.

Primary misses preserve the request's display-sRGB background bytes, independent of exposure, or zero RGBA when transparent background is requested. Reflected/transmitted misses default to black. An explicit runtime environment:{color:[r,g,b],intensity:n} supplies a constant secondary radiance: finite linear-sRGB channels in 0–1 and relative intensity in 0–64. It is separate from the display background and receives the same one-time exposure/display mapping as the accumulated optical contributions. Unit white environment radiance maps to approximately 188/255 at zero exposure. It is not HDRI lighting and does not illuminate opaque diffuse surfaces. Transparent PNG exports primary geometry coverage with straight RGB; transmitted/reflected paths and raw EXR are unchanged.

Transport deterministically splits Fresnel branches using a fixed nine-slot DFS stack, at most eight dielectric boundary events and 511 paths per pixel. An unresolved dielectric continuation at that depth contributes black: this is an explicit finite-depth preview that can lose energy, especially near TIR. It is not an unbiased or converged final renderer. There is one active homogeneous medium. Nested, touching and overlapping volume bounds refuse, as do sheets touching/intersecting a volume, camera/near-plane origins inside a volume and an exit without its matching entry. Box edges/corners with an ambiguous interface normal refuse. Opaque surfaces terminate transport with RT2 direct lighting. Hard shadow visibility treats dielectric triangles as blockers; transmitted illumination, colored shadows and caustics are not implemented. The shadows:false fixture explicitly disables that conservative visibility.

No per-path heap allocation, new worker pool, cache or database is introduced. One fixed optical task slot is reserved fallibly before processing pixels, with its actual Vec capacity checked; the path stack remains fixed inside that slot. Admission adds the prepared primitive table plus 32 KiB for bounded runtime options, traversal/path/certificate/light state; each of the two retained option copies has at most eight bounded names. Actual task/state/option capacity bytes must fit that 32 KiB allowance. The original request's directional-light vector, dielectric-selector vector and name capacities have a separate lease in the same host pool, held by the immutable status record independently of PNG retention or eviction. Original capacities are validated before cloning. Total job and shared-host limits remain 384/512 MiB for the compatibility Compact envelope. Current closed boxes may contain 12 through 65,536 boundary triangles; every triangle must remain on the exact outward box boundary and satisfy the unchanged edge/area certificate. Fallible triangle/edge/sort scratch is prepaid for the largest selected box and reused between boxes, in addition to the fixed state above. Other CPU profiles retain their separately documented limits. Preparation, all traversals and optical transitions consume the existing cumulative billion-unit budget and cancellation token; limit exhaustion fails the job without publishing a partial image. Each task unit performs one cursor operation and at most one bounded optical transition; a task call processes at most four units or four encoding rows. These are logical work/memory bounds, not OS timing or RSS guarantees.

Source regressions cover F0=0.04, Snell/reversibility, TIR, absorption and slab offset scaling, closure/winding/identity refusals, nested-volume admission, bounded runtime DTOs, cancellation and small decoded PNG controls for clear volumes and 2/8 mm absorption. These renderer regressions have executed. Required acceptance also includes an original native scene with identifiable background geometry, paired oblique slab PNGs, physical/persistence invariants, work exhaustion, cancellation and captured-resource release. Current source tests alone do not establish this visible acceptance or qualify the draft PR.

Thin-sheet polarization correction

The incoherent internal-return series is evaluated separately for s and p polarization. For one polarization with interface power reflectance F and one-pass Beer factor a, its sheet response is R = F + (1-F)^2 F a^2 / (1-F^2 a^2) and T = (1-F)^2 a / (1-F^2 a^2). The unpolarized result is the mean of the two completed responses. Averaging the interface reflectances first changes this nonlinear series and is incorrect at oblique incidence.

An independent lossless air-to-IOR-1.5 Brewster case has Rs = 25/169 and Rp = 0, hence sheet R = 25/194 and T = 169/194. The previous draft gave R = 50/363, exceeding the correct value by 625/70422. The source regression checks these rational values and the actual image consumer with an unlit opaque transmission target and a unit white secondary environment. That image must encode reflected energy as an sRGB byte of 95; the old series gives 98. Normal incidence, matched indices, and absorbing Brewster cases are retained as separate source regressions.

This correction preserves polarization only inside the analytic parallel-sheet closure. The existing transport still carries a scalar unpolarized response; it does not add full Stokes transport or change the closed-volume approximation. The runtime schema, boundary certification, millimetre units, path limits, and virtual-thickness geometry contract are unchanged. The rational-value and actual decoded PNG regressions have executed in the CPU renderer suite; native visual and combined workflow acceptance remain separate.

Saved optical bindings and common Glass controls

The existing stable RenderSetup.optics contract can also be consumed by an explicit CPU job. Definition/feature/component/occurrence identity resolves into an exact visible captured body name and ordinal at Check/Render. The CPU request uses its existing DielectricOptions; no engineering material, product opacity, physical transmission, CAD geometry or mass changes.

Without Path, saved closed_box and closed_mesh intent execute only when the captured mesh passes the strict axis-aligned-box certificate, including valid planar tilings within its 65,536-triangle bound. The saved boundary is retained unchanged; a sphere, cylinder, rotated box or arbitrary closed mesh does not gain support. Thin sheets retain the strict one/two-triangle planar certificate and authored virtual thickness. All overlap, near-plane, camera/lens, coat and per-path guards still apply.

CPU glass supports the authored IOR and per-mm Beer extinction for at most eight targets. Extinction is 0..100 per mm and virtual thickness 0..10000 mm. Authored transmissive shadows and any subsurface value refuse explicitly. Common thin recipes retain their transmissive-shadow flag: turn that flag off explicitly before rendering on this legacy route. CPU/GPU opaque coats and saved built-in maps are separate supported contracts; they cannot share a Glass target. Explicit Path provides general Glass/nested media and diffuse GI under its own certificate; this legacy route does not gain those features, and no caustic claim is made.

The common Glass editor creates temporary request-local presets and can save compatible numerical optics with the existing reviewed bind command in one undo. Reopening a selected stable saved target shows its actual enabled state, IOR, Beer extinction and boundary without allocating a temporary override on paint. Edits to those saved controls replace or remove only their exact target in a cloned image request, so live feedback uses the prospective values immediately. The document changes only through explicit Save/replacement or Remove. Discard returns to the saved values. Other saved rows remain intact; stale identities or changed sources refuse instead of targeting a new body. Mixed legacy temporary bindings or file-only HDR inputs retain an explicit conflict where they cannot share this typed request.