From 407d8a31305f9e06c9dd8f15a43b0d35778a0b26 Mon Sep 17 00:00:00 2001 From: Baplar Date: Wed, 20 May 2026 20:04:45 +0200 Subject: [PATCH 01/12] Use one triangle over entire screen to ensure full rasterization. --- .../shaders/materials/proceduralskybox.wgsl | 9 ++-- .../shaders/modules/skybox/common.wgsl | 23 +++++----- .../shaders/modules/skybox/procedural.wgsl | 43 ++++++++----------- .../passes/backend/skybox_gradientskybox.wgsl | 2 +- .../backend/skybox_proceduralskybox.wgsl | 39 ++++++----------- .../passes/backend/skybox_projection360.wgsl | 2 +- .../passes/backend/skybox_solid_color.wgsl | 2 +- .../world_mesh_forward/skybox/pipeline.rs | 2 +- 8 files changed, 48 insertions(+), 74 deletions(-) diff --git a/crates/renderide/shaders/materials/proceduralskybox.wgsl b/crates/renderide/shaders/materials/proceduralskybox.wgsl index b0ed61643..4ae01d066 100644 --- a/crates/renderide/shaders/materials/proceduralskybox.wgsl +++ b/crates/renderide/shaders/materials/proceduralskybox.wgsl @@ -29,8 +29,7 @@ struct VertexOutput { @location(0) ground_color: vec3, @location(1) sky_color: vec3, @location(2) sun_color: vec3, - @location(3) fragment_ray: vec3, - @location(4) sky_ground_factor: f32, + @location(3) ray: vec3, } @vertex @@ -55,8 +54,7 @@ fn vs_main( out.ground_color = terms.ground_color; out.sky_color = terms.sky_color; out.sun_color = terms.sun_color; - out.fragment_ray = terms.fragment_ray; - out.sky_ground_factor = terms.sky_ground_factor; + out.ray = terms.ray; return out; } @@ -67,8 +65,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { in.ground_color, in.sky_color, in.sun_color, - in.fragment_ray, - in.sky_ground_factor, + in.ray, ); return rg::retain_globals_additive(vec4( ps::visible_fragment_color(psmat::params(), terms), diff --git a/crates/renderide/shaders/modules/skybox/common.wgsl b/crates/renderide/shaders/modules/skybox/common.wgsl index ab58f95ef..f0490d3e0 100644 --- a/crates/renderide/shaders/modules/skybox/common.wgsl +++ b/crates/renderide/shaders/modules/skybox/common.wgsl @@ -26,24 +26,21 @@ fn fullscreen_clip_pos(vertex_index: u32) -> vec4 { return vec4(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0); } +const FULLSCREEN_COVERING_TRIANGLE = mat3x4( + vec4(-1.0, -1.0, 0.0, 1.0), + vec4(3.0, -1.0, 0.0, 1.0), + vec4(-1.0, 3.0, 0.0, 1.0) +); + fn fullscreen_quad_clip_pos(vertex_index: u32) -> vec4 { - let i = vertex_index % 6u; - if (i == 0u) { - return vec4(-1.0, 1.0, 0.0, 1.0); - } + let i = vertex_index % 3u; if (i == 1u) { - return vec4(1.0, 1.0, 0.0, 1.0); + return FULLSCREEN_COVERING_TRIANGLE[1]; } if (i == 2u) { - return vec4(-1.0, -1.0, 0.0, 1.0); - } - if (i == 3u) { - return vec4(-1.0, -1.0, 0.0, 1.0); - } - if (i == 4u) { - return vec4(1.0, 1.0, 0.0, 1.0); + return FULLSCREEN_COVERING_TRIANGLE[2]; } - return vec4(1.0, -1.0, 0.0, 1.0); + return FULLSCREEN_COVERING_TRIANGLE[0]; } fn view_is_orthographic(sky: SkyboxView, view_layer: u32) -> bool { diff --git a/crates/renderide/shaders/modules/skybox/procedural.wgsl b/crates/renderide/shaders/modules/skybox/procedural.wgsl index ee88600b5..08ec80604 100644 --- a/crates/renderide/shaders/modules/skybox/procedural.wgsl +++ b/crates/renderide/shaders/modules/skybox/procedural.wgsl @@ -17,7 +17,7 @@ const KKM_4PI: f32 = KMIE * 4.0 * PI; const KSCALE: f32 = 1.0 / (OUTER_RADIUS - 1.0); const KSCALE_DEPTH: f32 = 0.25; const KSCALE_OVER_SCALE_DEPTH: f32 = (1.0 / (OUTER_RADIUS - 1.0)) / 0.25; -const KSAMPLES: f32 = 2.0; +const KSAMPLES: u32 = 2u; const MIE_G: f32 = -0.990; const MIE_G2: f32 = 0.9801; const SKY_GROUND_THRESHOLD: f32 = 0.02; @@ -41,8 +41,7 @@ struct ProceduralSkyVisibleTerms { ground_color: vec3, sky_color: vec3, sun_color: vec3, - fragment_ray: vec3, - sky_ground_factor: f32, + ray: vec3, } struct ScatteringStep { @@ -145,22 +144,19 @@ fn evaluate_scattering( let start_angle = dot(eye_ray, camera_pos) / height; let start_offset = depth_init * scale_factor(start_angle); - let sample_length = far / KSAMPLES; + let sample_length = far / f32(KSAMPLES); let scaled_length = sample_length * KSCALE; let sample_ray = eye_ray * sample_length; var sample_point = camera_pos + sample_ray * 0.5; var front_color = vec3(0.0); - let s0 = scattering_inscatter_step( - sample_point, eye_ray, sun_dir, inv_wavelength, kkr_4pi, start_offset, scaled_length, - ); - front_color = front_color + s0.contribution; - sample_point = sample_point + sample_ray; - - let s1 = scattering_inscatter_step( - sample_point, eye_ray, sun_dir, inv_wavelength, kkr_4pi, start_offset, scaled_length, - ); - front_color = front_color + s1.contribution; + for (var i = 0u; i < KSAMPLES; i++) { + let step = scattering_inscatter_step( + sample_point, eye_ray, sun_dir, inv_wavelength, kkr_4pi, start_offset, scaled_length, + ); + front_color = front_color + step.contribution; + sample_point = sample_point + sample_ray; + } c_in = front_color * (inv_wavelength * kkr_esun); c_out = front_color * KKM_ESUN; @@ -175,7 +171,7 @@ fn evaluate_scattering( let camera_offset = depth * camera_scale; let temp = light_scale + camera_scale; - let sample_length = far / KSAMPLES; + let sample_length = far / f32(KSAMPLES); let scaled_length = sample_length * KSCALE; let sample_ray = eye_ray * sample_length; let sample_point = camera_pos + sample_ray * 0.5; @@ -220,34 +216,31 @@ fn visible_vertex_terms(params: ProceduralSkyParams, input_eye_ray: vec3) - let ground_color = params.exposure * (scattering.c_in + params.ground_color * scattering.c_out); let sky_color = params.exposure * (scattering.c_in * rayleigh_phase(sun_dir, -eye_ray)); let sun_color = params.exposure * (scattering.c_out * params.sun_color); - let fragment_ray = -eye_ray; - let sky_ground_factor = fragment_ray.y / SKY_GROUND_THRESHOLD; return ProceduralSkyVisibleTerms( ground_color, sky_color, sun_color, - fragment_ray, - sky_ground_factor, + -eye_ray, ); } fn visible_fragment_color(params: ProceduralSkyParams, terms: ProceduralSkyVisibleTerms) -> vec3 { - let sun_dir = safe_normalize(params.sun_direction, vec3(0.0, 1.0, 0.0)); - let fragment_ray = safe_normalize(terms.fragment_ray, vec3(0.0, -1.0, 0.0)); + let sky_ground_factor = terms.ray.y / SKY_GROUND_THRESHOLD; var color = mix( terms.sky_color, terms.ground_color, - clamp(terms.sky_ground_factor, 0.0, 1.0), + clamp(sky_ground_factor, 0.0, 1.0), ); - if (!sun_disk_mode_none(params.sun_disk_mode) && terms.sky_ground_factor < 0.0) { + if (!sun_disk_mode_none(params.sun_disk_mode) && sky_ground_factor < 0.0) { + let sun_dir = safe_normalize(params.sun_direction, vec3(0.0, 1.0, 0.0)); let sun_size = clamp(params.sun_size, 1.0e-4, 1.0); var mie: f32; if (sun_disk_mode_high_quality(params.sun_disk_mode)) { - let eye_cos = dot(sun_dir, fragment_ray); + let eye_cos = dot(sun_dir, terms.ray); mie = mie_phase(eye_cos, eye_cos * eye_cos, sun_size); } else { - mie = calc_sun_spot(sun_dir, -fragment_ray, sun_size); + mie = calc_sun_spot(sun_dir, -terms.ray, sun_size); } color = color + mie * terms.sun_color; } diff --git a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl index 403f83cb6..85055de40 100644 --- a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl @@ -29,7 +29,7 @@ fn vs_main( @builtin(view_index) view_idx: u32, #endif ) -> VertexOutput { - let clip = skybox::fullscreen_clip_pos(vertex_index); + let clip = skybox::fullscreen_quad_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; out.ndc = vec2(clip.x, clip.y * view.ndc_y_sign_pad.x); diff --git a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl index 184cf1ab3..565361275 100644 --- a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl @@ -7,15 +7,12 @@ #import renderide::skybox::procedural as ps #import renderide::skybox::procedural_material as psmat #import renderide::skybox::common as skybox + @group(2) @binding(0) var view: skybox::SkyboxView; struct VertexOutput { @builtin(position) clip_pos: vec4, - @location(0) ground_color: vec3, - @location(1) sky_color: vec3, - @location(2) sun_color: vec3, - @location(3) fragment_ray: vec3, - @location(4) sky_ground_factor: f32, + @location(0) @interpolate(flat) view_layer: u32, } @vertex @@ -33,34 +30,24 @@ fn vs_main( #else let view_layer = 0u; #endif - let ndc = vec2(clip.x, clip.y * view.ndc_y_sign_pad.x); - let proj_params = select(rg::frame.proj_params_left, rg::frame.proj_params_right, view_layer != 0u); - let view_ray = skybox::view_ray_from_ndc( - ndc, - proj_params, - skybox::view_is_orthographic(view, view_layer), - ); - let world_ray = skybox::world_ray_from_view_ray(view_ray, view, view_layer); - let terms = ps::visible_vertex_terms(psmat::params(), world_ray); - out.ground_color = terms.ground_color; - out.sky_color = terms.sky_color; - out.sun_color = terms.sun_color; - out.fragment_ray = terms.fragment_ray; - out.sky_ground_factor = terms.sky_ground_factor; + out.view_layer = view_layer; return out; } @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let terms = ps::ProceduralSkyVisibleTerms( - in.ground_color, - in.sky_color, - in.sun_color, - in.fragment_ray, - in.sky_ground_factor, + let ndc = vec2(in.clip_pos.x, in.clip_pos.y * view.ndc_y_sign_pad.x); + let proj_params = select(rg::frame.proj_params_left, rg::frame.proj_params_right, in.view_layer != 0u); + let view_ray = skybox::view_ray_from_ndc( + ndc, + proj_params, + skybox::view_is_orthographic(view, in.view_layer), ); + let world_ray = skybox::world_ray_from_view_ray(view_ray, view, in.view_layer); + let ps_params = psmat::params(); + let terms = ps::visible_vertex_terms(ps_params, world_ray); return rg::retain_globals_additive(vec4( - ps::visible_fragment_color(psmat::params(), terms), + ps::visible_fragment_color(ps_params, terms), 1.0, )); } diff --git a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl index ea7d375ab..0571cf0b3 100644 --- a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl @@ -102,7 +102,7 @@ fn vs_main( @builtin(view_index) view_idx: u32, #endif ) -> VertexOutput { - let clip = skybox::fullscreen_clip_pos(vertex_index); + let clip = skybox::fullscreen_quad_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; out.ndc = vec2(clip.x, clip.y * view.ndc_y_sign_pad.x); diff --git a/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl b/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl index 024bcd41b..9c72dacb0 100644 --- a/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl @@ -11,7 +11,7 @@ struct VertexOutput { @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { var out: VertexOutput; - out.clip_pos = skybox::fullscreen_clip_pos(vertex_index); + out.clip_pos = skybox::fullscreen_quad_clip_pos(vertex_index); return out; } diff --git a/crates/renderide/src/passes/world_mesh_forward/skybox/pipeline.rs b/crates/renderide/src/passes/world_mesh_forward/skybox/pipeline.rs index 8c0f26ea1..e4cda7ba7 100644 --- a/crates/renderide/src/passes/world_mesh_forward/skybox/pipeline.rs +++ b/crates/renderide/src/passes/world_mesh_forward/skybox/pipeline.rs @@ -49,7 +49,7 @@ impl SkyboxFamily { pub(super) const fn draw_vertex_count(self) -> u32 { match self { Self::Projection360 | Self::Gradient => 3, - Self::Procedural => 6, + Self::Procedural => 3, } } } From c9fd2c0e51055f28bff233493201fd4d6407e103 Mon Sep 17 00:00:00 2001 From: Baplar Date: Wed, 20 May 2026 20:04:45 +0200 Subject: [PATCH 02/12] Move NDC computation to fragment shader --- crates/renderide/shaders/modules/skybox/common.wgsl | 7 ++++--- .../shaders/passes/backend/skybox_gradientskybox.wgsl | 7 +++---- .../shaders/passes/backend/skybox_projection360.wgsl | 7 +++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/renderide/shaders/modules/skybox/common.wgsl b/crates/renderide/shaders/modules/skybox/common.wgsl index f0490d3e0..a36bf501f 100644 --- a/crates/renderide/shaders/modules/skybox/common.wgsl +++ b/crates/renderide/shaders/modules/skybox/common.wgsl @@ -57,9 +57,10 @@ fn view_ray_from_ndc(ndc: vec2, proj_params: vec4, orthographic: bool) } // Asymmetric OpenXR projections store skew on the Z column. With sky rays fixed at z = -1 // and clip_w = -view_z, inverse projection uses ndc + skew. - let view_x = (ndc.x + proj_params.z) / max(abs(proj_params.x), 0.000001); - let view_y = (ndc.y + proj_params.w) / max(abs(proj_params.y), 0.000001); - return normalize(vec3(view_x, view_y, -1.0)); + let view_unscaled = vec2(ndc.x + proj_params.z, ndc.y + proj_params.w); + let proj_params_signs = sign(sign(proj_params.xy) * 2.0 + 1.0); + let proj_params_safe = proj_params_signs * max(abs(proj_params.xy), vec2(0.000001)); + return normalize(vec3(view_unscaled / proj_params_safe, -1.0)); } fn world_ray_from_view_ray(view_ray: vec3, sky: SkyboxView, view_layer: u32) -> vec3 { diff --git a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl index 85055de40..3987608d7 100644 --- a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl @@ -18,8 +18,7 @@ struct GradientSkyboxMaterial { struct VertexOutput { @builtin(position) clip_pos: vec4, - @location(0) ndc: vec2, - @location(1) @interpolate(flat) view_layer: u32, + @location(0) @interpolate(flat) view_layer: u32, } @vertex @@ -32,7 +31,6 @@ fn vs_main( let clip = skybox::fullscreen_quad_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; - out.ndc = vec2(clip.x, clip.y * view.ndc_y_sign_pad.x); #ifdef MULTIVIEW out.view_layer = view_idx; #else @@ -43,9 +41,10 @@ fn vs_main( @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let ndc = vec2(in.clip_pos.x, in.clip_pos.y * view.ndc_y_sign_pad.x); let proj_params = select(rg::frame.proj_params_left, rg::frame.proj_params_right, in.view_layer != 0u); let view_ray = skybox::view_ray_from_ndc( - in.ndc, + ndc, proj_params, skybox::view_is_orthographic(view, in.view_layer), ); diff --git a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl index 0571cf0b3..bcef8eacc 100644 --- a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl @@ -51,8 +51,7 @@ struct Projection360Material { struct VertexOutput { @builtin(position) clip_pos: vec4, - @location(0) ndc: vec2, - @location(1) @interpolate(flat) view_layer: u32, + @location(0) @interpolate(flat) view_layer: u32, } fn projection360_params() -> p360m::Projection360Params { @@ -105,7 +104,6 @@ fn vs_main( let clip = skybox::fullscreen_quad_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; - out.ndc = vec2(clip.x, clip.y * view.ndc_y_sign_pad.x); #ifdef MULTIVIEW out.view_layer = view_idx; #else @@ -117,8 +115,9 @@ fn vs_main( @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { let params = projection360_params(); + let ndc = vec2(in.clip_pos.x, in.clip_pos.y * view.ndc_y_sign_pad.x); let view_dir = p360m::apply_offset( - base_view_dir(in.ndc, in.view_layer), + base_view_dir(ndc, in.view_layer), params, _OffsetTex, _OffsetTex_sampler, From 7fbe3feedb3d05e2bc28d2271b5e25c752e2ed02 Mon Sep 17 00:00:00 2001 From: Baplar Date: Wed, 20 May 2026 20:04:45 +0200 Subject: [PATCH 03/12] Minor refactor --- .../shaders/materials/proceduralskybox.wgsl | 4 +- .../shaders/modules/skybox/common.wgsl | 3 +- .../shaders/modules/skybox/procedural.wgsl | 57 ++++++++++++------- .../backend/skybox_proceduralskybox.wgsl | 3 +- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/crates/renderide/shaders/materials/proceduralskybox.wgsl b/crates/renderide/shaders/materials/proceduralskybox.wgsl index 4ae01d066..e19e5240e 100644 --- a/crates/renderide/shaders/materials/proceduralskybox.wgsl +++ b/crates/renderide/shaders/materials/proceduralskybox.wgsl @@ -50,7 +50,9 @@ fn vs_main( var out: VertexOutput; out.clip_pos = vp * world_p; - let terms = ps::visible_vertex_terms(psmat::params(), mv::model_vector(d, pos.xyz)); + let ps_params = psmat::params(); + let scattering_params = ps::scattering_parameters(ps_params); + let terms = ps::visible_vertex_terms(ps_params, scattering_params, mv::model_vector(d, pos.xyz)); out.ground_color = terms.ground_color; out.sky_color = terms.sky_color; out.sun_color = terms.sun_color; diff --git a/crates/renderide/shaders/modules/skybox/common.wgsl b/crates/renderide/shaders/modules/skybox/common.wgsl index a36bf501f..45e0fded3 100644 --- a/crates/renderide/shaders/modules/skybox/common.wgsl +++ b/crates/renderide/shaders/modules/skybox/common.wgsl @@ -57,7 +57,8 @@ fn view_ray_from_ndc(ndc: vec2, proj_params: vec4, orthographic: bool) } // Asymmetric OpenXR projections store skew on the Z column. With sky rays fixed at z = -1 // and clip_w = -view_z, inverse projection uses ndc + skew. - let view_unscaled = vec2(ndc.x + proj_params.z, ndc.y + proj_params.w); + + let view_unscaled = ndc.xy + proj_params.zw; let proj_params_signs = sign(sign(proj_params.xy) * 2.0 + 1.0); let proj_params_safe = proj_params_signs * max(abs(proj_params.xy), vec2(0.000001)); return normalize(vec3(view_unscaled / proj_params_safe, -1.0)); diff --git a/crates/renderide/shaders/modules/skybox/procedural.wgsl b/crates/renderide/shaders/modules/skybox/procedural.wgsl index 08ec80604..384c927d3 100644 --- a/crates/renderide/shaders/modules/skybox/procedural.wgsl +++ b/crates/renderide/shaders/modules/skybox/procedural.wgsl @@ -8,6 +8,7 @@ const INNER_RADIUS: f32 = 1.0; const OUTER_RADIUS_SQ: f32 = OUTER_RADIUS * OUTER_RADIUS; const INNER_RADIUS_SQ: f32 = INNER_RADIUS * INNER_RADIUS; const CAMERA_HEIGHT: f32 = 0.0001; +const CAMERA_POS_HEIGHT: f32 = INNER_RADIUS +CAMERA_HEIGHT; const KMIE: f32 = 0.0010; const KSUN_BRIGHTNESS: f32 = 20.0; const KMAX_SCATTER: f32 = 50.0; @@ -44,6 +45,11 @@ struct ProceduralSkyVisibleTerms { ray: vec3, } +struct ScatteringParameters { + kkr_in: vec3, + kkr_scatter: vec3, +} + struct ScatteringStep { contribution: vec3, attenuate: vec3, @@ -94,8 +100,7 @@ fn scattering_inscatter_step( sample_point: vec3, eye_ray: vec3, sun_dir: vec3, - inv_wavelength: vec3, - kkr_4pi: f32, + kkr_scatter: vec3, start_offset: f32, scaled_length: f32, ) -> ScatteringStep { @@ -104,14 +109,13 @@ fn scattering_inscatter_step( let light_angle = dot(sun_dir, sample_point) / h; let camera_angle = dot(eye_ray, sample_point) / h; let scatter = start_offset + depth * (scale_factor(light_angle) - scale_factor(camera_angle)); - let attenuate = exp(-clamp(scatter, 0.0, KMAX_SCATTER) * (inv_wavelength * kkr_4pi + KKM_4PI)); + let attenuate = exp(-clamp(scatter, 0.0, KMAX_SCATTER) * kkr_scatter); return ScatteringStep(attenuate * (depth * scaled_length), attenuate); } fn ground_inscatter_step( sample_point: vec3, - inv_wavelength: vec3, - kkr_4pi: f32, + kkr_scatter: vec3, temp: f32, camera_offset: f32, scaled_length: f32, @@ -119,19 +123,16 @@ fn ground_inscatter_step( let h = length(sample_point); let depth = exp(KSCALE_OVER_SCALE_DEPTH * (INNER_RADIUS - h)); let scatter = depth * temp - camera_offset; - let attenuate = exp(-clamp(scatter, 0.0, KMAX_SCATTER) * (inv_wavelength * kkr_4pi + KKM_4PI)); + let attenuate = exp(-clamp(scatter, 0.0, KMAX_SCATTER) * kkr_scatter); return ScatteringStep(attenuate * (depth * scaled_length), attenuate); } fn evaluate_scattering( eye_ray: vec3, sun_dir: vec3, - inv_wavelength: vec3, - krayleigh: f32, + scattering_params: ScatteringParameters, ) -> ScatteringOutput { - let kkr_esun = krayleigh * KSUN_BRIGHTNESS; - let kkr_4pi = krayleigh * 4.0 * PI; - let camera_pos = vec3(0.0, INNER_RADIUS + CAMERA_HEIGHT, 0.0); + let camera_pos = vec3(0.0, CAMERA_POS_HEIGHT, 0.0); var c_in: vec3; var c_out: vec3; @@ -139,7 +140,7 @@ fn evaluate_scattering( if (eye_ray.y >= 0.0) { let far = sqrt(OUTER_RADIUS_SQ + INNER_RADIUS_SQ * eye_ray.y * eye_ray.y - INNER_RADIUS_SQ) - INNER_RADIUS * eye_ray.y; - let height = INNER_RADIUS + CAMERA_HEIGHT; + let height = CAMERA_POS_HEIGHT; let depth_init = exp(KSCALE_OVER_SCALE_DEPTH * (-CAMERA_HEIGHT)); let start_angle = dot(eye_ray, camera_pos) / height; let start_offset = depth_init * scale_factor(start_angle); @@ -152,13 +153,13 @@ fn evaluate_scattering( var front_color = vec3(0.0); for (var i = 0u; i < KSAMPLES; i++) { let step = scattering_inscatter_step( - sample_point, eye_ray, sun_dir, inv_wavelength, kkr_4pi, start_offset, scaled_length, + sample_point, eye_ray, sun_dir, scattering_params.kkr_scatter, start_offset, scaled_length, ); front_color = front_color + step.contribution; sample_point = sample_point + sample_ray; } - c_in = front_color * (inv_wavelength * kkr_esun); + c_in = front_color * (scattering_params.kkr_in); c_out = front_color * KKM_ESUN; } else { let far = (-CAMERA_HEIGHT) / min(-0.001, eye_ray.y); @@ -177,11 +178,11 @@ fn evaluate_scattering( let sample_point = camera_pos + sample_ray * 0.5; let g = ground_inscatter_step( - sample_point, inv_wavelength, kkr_4pi, temp, camera_offset, scaled_length, + sample_point, scattering_params.kkr_scatter, temp, camera_offset, scaled_length, ); let front_color = g.contribution; - c_in = front_color * (inv_wavelength * kkr_esun + KKM_ESUN); + c_in = front_color * (scattering_params.kkr_in + KKM_ESUN); c_out = clamp(g.attenuate, vec3(0.0), vec3(1.0)); } @@ -205,13 +206,25 @@ fn sun_disk_mode_high_quality(mode: f32) -> bool { return mode > 1.5; } -fn visible_vertex_terms(params: ProceduralSkyParams, input_eye_ray: vec3) -> ProceduralSkyVisibleTerms { - let eye_ray = safe_normalize(input_eye_ray, vec3(0.0, 1.0, 0.0)); - let sun_dir = safe_normalize(params.sun_direction, vec3(0.0, 1.0, 0.0)); +fn scattering_parameters(params: ProceduralSkyParams) -> ScatteringParameters { let scattering_wavelength = scattering_wavelength_from_tint(params.sky_tint); let inv_wavelength = 1.0 / pow(scattering_wavelength, vec3(4.0)); let krayleigh = mix(0.0, 0.0025, pow(max(params.atmosphere_thickness, 0.0), 2.5)); - let scattering = evaluate_scattering(eye_ray, sun_dir, inv_wavelength, krayleigh); + let kkr_esun = krayleigh * KSUN_BRIGHTNESS; + let kkr_4pi = krayleigh * 4.0 * PI; + let kkr_in = inv_wavelength * kkr_esun; + let kkr_scatter = inv_wavelength * kkr_4pi + KKM_4PI; + return ScatteringParameters(kkr_in, kkr_scatter); +} + +fn visible_vertex_terms( + params: ProceduralSkyParams, + scattering_params: ScatteringParameters, + input_eye_ray: vec3 +) -> ProceduralSkyVisibleTerms { + let eye_ray = safe_normalize(input_eye_ray, vec3(0.0, 1.0, 0.0)); + let sun_dir = safe_normalize(params.sun_direction, vec3(0.0, 1.0, 0.0)); + let scattering = evaluate_scattering(eye_ray, sun_dir, scattering_params); let ground_color = params.exposure * (scattering.c_in + params.ground_color * scattering.c_out); let sky_color = params.exposure * (scattering.c_in * rayleigh_phase(sun_dir, -eye_ray)); @@ -249,5 +262,7 @@ fn visible_fragment_color(params: ProceduralSkyParams, terms: ProceduralSkyVisib } fn sample(params: ProceduralSkyParams, input_eye_ray: vec3) -> vec3 { - return visible_fragment_color(params, visible_vertex_terms(params, input_eye_ray)); + let scattering_params = scattering_parameters(params); + let vertex_terms = visible_vertex_terms(params, scattering_params, input_eye_ray); + return visible_fragment_color(params, vertex_terms); } diff --git a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl index 565361275..5cf5ec973 100644 --- a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl @@ -45,7 +45,8 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { ); let world_ray = skybox::world_ray_from_view_ray(view_ray, view, in.view_layer); let ps_params = psmat::params(); - let terms = ps::visible_vertex_terms(ps_params, world_ray); + let scattering_params = ps::scattering_parameters(ps_params); + let terms = ps::visible_vertex_terms(ps_params, scattering_params, world_ray); return rg::retain_globals_additive(vec4( ps::visible_fragment_color(ps_params, terms), 1.0, From fc1e295bdc439389fa7b767abf17b2ebbb289237 Mon Sep 17 00:00:00 2001 From: Baplar Date: Wed, 20 May 2026 20:04:45 +0200 Subject: [PATCH 04/12] Use same fullscreen vertex generation as overlays --- .../shaders/modules/skybox/common.wgsl | 23 ------------------- .../passes/backend/skybox_gradientskybox.wgsl | 3 ++- .../backend/skybox_proceduralskybox.wgsl | 3 ++- .../passes/backend/skybox_projection360.wgsl | 3 ++- .../passes/backend/skybox_solid_color.wgsl | 3 ++- 5 files changed, 8 insertions(+), 27 deletions(-) diff --git a/crates/renderide/shaders/modules/skybox/common.wgsl b/crates/renderide/shaders/modules/skybox/common.wgsl index 45e0fded3..53ff3c42e 100644 --- a/crates/renderide/shaders/modules/skybox/common.wgsl +++ b/crates/renderide/shaders/modules/skybox/common.wgsl @@ -20,29 +20,6 @@ struct SkyboxView { ndc_y_sign_pad: vec4, } -fn fullscreen_clip_pos(vertex_index: u32) -> vec4 { - let x = f32((vertex_index << 1u) & 2u); - let y = f32(vertex_index & 2u); - return vec4(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0); -} - -const FULLSCREEN_COVERING_TRIANGLE = mat3x4( - vec4(-1.0, -1.0, 0.0, 1.0), - vec4(3.0, -1.0, 0.0, 1.0), - vec4(-1.0, 3.0, 0.0, 1.0) -); - -fn fullscreen_quad_clip_pos(vertex_index: u32) -> vec4 { - let i = vertex_index % 3u; - if (i == 1u) { - return FULLSCREEN_COVERING_TRIANGLE[1]; - } - if (i == 2u) { - return FULLSCREEN_COVERING_TRIANGLE[2]; - } - return FULLSCREEN_COVERING_TRIANGLE[0]; -} - fn view_is_orthographic(sky: SkyboxView, view_layer: u32) -> bool { if (view_layer == 0u) { return sky.ndc_y_sign_pad.y > 0.5; diff --git a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl index 3987608d7..f15843e5b 100644 --- a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl @@ -1,6 +1,7 @@ //! Fullscreen GradientSkybox sky draw. #import renderide::frame::globals as rg +#import renderide::core::fullscreen as fs #import renderide::skybox::common as skybox #import renderide::skybox::gradient as skygrad @@ -28,7 +29,7 @@ fn vs_main( @builtin(view_index) view_idx: u32, #endif ) -> VertexOutput { - let clip = skybox::fullscreen_quad_clip_pos(vertex_index); + let clip = fs::fullscreen_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; #ifdef MULTIVIEW diff --git a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl index 5cf5ec973..b8e4f0ce1 100644 --- a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl @@ -4,6 +4,7 @@ //! shader-specific keyword decoding for both this pass-side sky draw and the material root. #import renderide::frame::globals as rg +#import renderide::core::fullscreen as fs #import renderide::skybox::procedural as ps #import renderide::skybox::procedural_material as psmat #import renderide::skybox::common as skybox @@ -22,7 +23,7 @@ fn vs_main( @builtin(view_index) view_idx: u32, #endif ) -> VertexOutput { - let clip = skybox::fullscreen_quad_clip_pos(vertex_index); + let clip = fs::fullscreen_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; #ifdef MULTIVIEW diff --git a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl index bcef8eacc..2ce7a4883 100644 --- a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl @@ -4,6 +4,7 @@ //! sampling for both this pass-side sky draw and the material root. #import renderide::frame::globals as rg +#import renderide::core::fullscreen as fs #import renderide::skybox::common as skybox #import renderide::skybox::projection360 as p360 #import renderide::skybox::projection360_material as p360m @@ -101,7 +102,7 @@ fn vs_main( @builtin(view_index) view_idx: u32, #endif ) -> VertexOutput { - let clip = skybox::fullscreen_quad_clip_pos(vertex_index); + let clip = fs::fullscreen_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; #ifdef MULTIVIEW diff --git a/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl b/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl index 9c72dacb0..990900144 100644 --- a/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl @@ -1,6 +1,7 @@ //! Fullscreen solid background for host `CameraClearMode::Color`. #import renderide::skybox::common as skybox +#import renderide::core::fullscreen as fs @group(0) @binding(0) var view: skybox::SkyboxView; @@ -11,7 +12,7 @@ struct VertexOutput { @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { var out: VertexOutput; - out.clip_pos = skybox::fullscreen_quad_clip_pos(vertex_index); + out.clip_pos = fs::fullscreen_clip_pos(vertex_index); return out; } From 91fef4ed5462ff5d6dc65fb90537570754824081 Mon Sep 17 00:00:00 2001 From: Baplar Date: Wed, 20 May 2026 20:04:45 +0200 Subject: [PATCH 05/12] Refactor skybox view matrices --- .../shaders/modules/skybox/common.wgsl | 43 ++++++++----------- .../passes/backend/skybox_gradientskybox.wgsl | 5 +-- .../backend/skybox_proceduralskybox.wgsl | 5 +-- .../passes/backend/skybox_projection360.wgsl | 13 +++--- .../passes/backend/skybox_solid_color.wgsl | 3 +- .../src/passes/world_mesh_forward/skybox.rs | 36 +++------------- 6 files changed, 36 insertions(+), 69 deletions(-) diff --git a/crates/renderide/shaders/modules/skybox/common.wgsl b/crates/renderide/shaders/modules/skybox/common.wgsl index 53ff3c42e..d19aa9cf0 100644 --- a/crates/renderide/shaders/modules/skybox/common.wgsl +++ b/crates/renderide/shaders/modules/skybox/common.wgsl @@ -3,12 +3,8 @@ #define_import_path renderide::skybox::common struct SkyboxView { - view_x_left: vec4, - view_y_left: vec4, - view_z_left: vec4, - view_x_right: vec4, - view_y_right: vec4, - view_z_right: vec4, + view_left: mat4x4, + view_right: mat4x4, clear_color: vec4, /// `.x`: ndc Y sign passed to the fragment shader (1.0 normal, -1.0 for offscreen-RT views). /// Offscreen-RT views pre-multiply a clip-space Y flip into the world view-projection so the @@ -20,6 +16,19 @@ struct SkyboxView { ndc_y_sign_pad: vec4, } +fn select_view_proj(view: SkyboxView, view_idx: u32) -> mat4x4 { + if (view_idx == 0u) { + return view.view_left; + } + return view.view_right; +} + +fn fullscreen_clip_pos(vertex_index: u32) -> vec4 { + let x = f32(vertex_index % 3u); + let y = f32(vertex_index % 2u); + return vec4(3.0 * (x - 1.0), y * 4.0 - 1.0, 0.0, 1.0); +} + fn view_is_orthographic(sky: SkyboxView, view_layer: u32) -> bool { if (view_layer == 0u) { return sky.ndc_y_sign_pad.y > 0.5; @@ -30,28 +39,14 @@ fn view_is_orthographic(sky: SkyboxView, view_layer: u32) -> bool { /// Reconstructs a view-space sky ray from NDC and packed projection coefficients. fn view_ray_from_ndc(ndc: vec2, proj_params: vec4, orthographic: bool) -> vec3 { if (orthographic) { - return vec3(0.0, 0.0, -1.0); + return vec3(0.0, 0.0, 1.0); } // Asymmetric OpenXR projections store skew on the Z column. With sky rays fixed at z = -1 // and clip_w = -view_z, inverse projection uses ndc + skew. - - let view_unscaled = ndc.xy + proj_params.zw; - let proj_params_signs = sign(sign(proj_params.xy) * 2.0 + 1.0); - let proj_params_safe = proj_params_signs * max(abs(proj_params.xy), vec2(0.000001)); - return normalize(vec3(view_unscaled / proj_params_safe, -1.0)); + return vec3((ndc.xy + proj_params.zw) / max(abs(proj_params.xy), vec2(0.000001)), 1.0); } fn world_ray_from_view_ray(view_ray: vec3, sky: SkyboxView, view_layer: u32) -> vec3 { - if (view_layer == 0u) { - return normalize( - view_ray.x * sky.view_x_left.xyz + - view_ray.y * sky.view_y_left.xyz + - view_ray.z * sky.view_z_left.xyz - ); - } - return normalize( - view_ray.x * sky.view_x_right.xyz + - view_ray.y * sky.view_y_right.xyz + - view_ray.z * sky.view_z_right.xyz - ); + let view_matrix = select_view_proj(sky, view_layer); + return normalize(view_matrix * vec4(view_ray, 1.0)).xyz; } diff --git a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl index f15843e5b..7ab220231 100644 --- a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl @@ -1,7 +1,6 @@ //! Fullscreen GradientSkybox sky draw. #import renderide::frame::globals as rg -#import renderide::core::fullscreen as fs #import renderide::skybox::common as skybox #import renderide::skybox::gradient as skygrad @@ -29,7 +28,7 @@ fn vs_main( @builtin(view_index) view_idx: u32, #endif ) -> VertexOutput { - let clip = fs::fullscreen_clip_pos(vertex_index); + let clip = skybox::fullscreen_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; #ifdef MULTIVIEW @@ -42,7 +41,7 @@ fn vs_main( @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let ndc = vec2(in.clip_pos.x, in.clip_pos.y * view.ndc_y_sign_pad.x); + let ndc = in.clip_pos.xy * vec2(1.0, view.ndc_y_sign_pad.x); let proj_params = select(rg::frame.proj_params_left, rg::frame.proj_params_right, in.view_layer != 0u); let view_ray = skybox::view_ray_from_ndc( ndc, diff --git a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl index b8e4f0ce1..74ae7acd5 100644 --- a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl @@ -4,7 +4,6 @@ //! shader-specific keyword decoding for both this pass-side sky draw and the material root. #import renderide::frame::globals as rg -#import renderide::core::fullscreen as fs #import renderide::skybox::procedural as ps #import renderide::skybox::procedural_material as psmat #import renderide::skybox::common as skybox @@ -23,7 +22,7 @@ fn vs_main( @builtin(view_index) view_idx: u32, #endif ) -> VertexOutput { - let clip = fs::fullscreen_clip_pos(vertex_index); + let clip = skybox::fullscreen_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; #ifdef MULTIVIEW @@ -37,7 +36,7 @@ fn vs_main( @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let ndc = vec2(in.clip_pos.x, in.clip_pos.y * view.ndc_y_sign_pad.x); + let ndc = in.clip_pos.xy * vec2(1.0, view.ndc_y_sign_pad.x); let proj_params = select(rg::frame.proj_params_left, rg::frame.proj_params_right, in.view_layer != 0u); let view_ray = skybox::view_ray_from_ndc( ndc, diff --git a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl index 2ce7a4883..67f0cce56 100644 --- a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl @@ -4,7 +4,6 @@ //! sampling for both this pass-side sky draw and the material root. #import renderide::frame::globals as rg -#import renderide::core::fullscreen as fs #import renderide::skybox::common as skybox #import renderide::skybox::projection360 as p360 #import renderide::skybox::projection360_material as p360m @@ -81,6 +80,10 @@ fn projection360_params() -> p360m::Projection360Params { } fn base_view_dir(ndc: vec2, view_layer: u32) -> vec3 { + if (p360m::kw_PERSPECTIVE(mat._RenderideVariantBits)) { + return p360::perspective_view_dir_from_ndc(ndc.xy, mat._PerspectiveFOV); + } + let proj_params = select(rg::frame.proj_params_left, rg::frame.proj_params_right, view_layer != 0u); let camera_ray_view = skybox::view_ray_from_ndc( ndc, @@ -88,10 +91,6 @@ fn base_view_dir(ndc: vec2, view_layer: u32) -> vec3 { skybox::view_is_orthographic(view, view_layer), ); let camera_ray_world = skybox::world_ray_from_view_ray(camera_ray_view, view, view_layer); - - if (p360m::kw_PERSPECTIVE(mat._RenderideVariantBits)) { - return p360::perspective_view_dir_from_ndc(ndc, mat._PerspectiveFOV); - } return normalize(-camera_ray_world); } @@ -102,7 +101,7 @@ fn vs_main( @builtin(view_index) view_idx: u32, #endif ) -> VertexOutput { - let clip = fs::fullscreen_clip_pos(vertex_index); + let clip = skybox::fullscreen_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; #ifdef MULTIVIEW @@ -116,7 +115,7 @@ fn vs_main( @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { let params = projection360_params(); - let ndc = vec2(in.clip_pos.x, in.clip_pos.y * view.ndc_y_sign_pad.x); + let ndc = in.clip_pos.xy * vec2(1.0, view.ndc_y_sign_pad.x); let view_dir = p360m::apply_offset( base_view_dir(ndc, in.view_layer), params, diff --git a/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl b/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl index 990900144..024bcd41b 100644 --- a/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl @@ -1,7 +1,6 @@ //! Fullscreen solid background for host `CameraClearMode::Color`. #import renderide::skybox::common as skybox -#import renderide::core::fullscreen as fs @group(0) @binding(0) var view: skybox::SkyboxView; @@ -12,7 +11,7 @@ struct VertexOutput { @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { var out: VertexOutput; - out.clip_pos = fs::fullscreen_clip_pos(vertex_index); + out.clip_pos = skybox::fullscreen_clip_pos(vertex_index); return out; } diff --git a/crates/renderide/src/passes/world_mesh_forward/skybox.rs b/crates/renderide/src/passes/world_mesh_forward/skybox.rs index 3956e925f..3fa28524d 100644 --- a/crates/renderide/src/passes/world_mesh_forward/skybox.rs +++ b/crates/renderide/src/passes/world_mesh_forward/skybox.rs @@ -42,18 +42,10 @@ struct SkyboxViewBinding { #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] struct SkyboxViewUniforms { - /// View-to-world X basis for the left eye or mono view. - view_x_left: [f32; 4], - /// View-to-world Y basis for the left eye or mono view. - view_y_left: [f32; 4], - /// View-to-world Z basis for the left eye or mono view. - view_z_left: [f32; 4], - /// View-to-world X basis for the right eye. - view_x_right: [f32; 4], - /// View-to-world Y basis for the right eye. - view_y_right: [f32; 4], - /// View-to-world Z basis for the right eye. - view_z_right: [f32; 4], + /// View-to-world basis for the left eye or mono view. + view_left: [f32; 16], + /// View-to-world basis for the right eye. + view_right: [f32; 16], /// Background color for `CameraClearMode::Color`. clear_color: [f32; 4], /// `.x`: ndc Y sign passed to the fragment shader (1.0 normal, -1.0 for offscreen-RT views). @@ -70,8 +62,6 @@ impl SkyboxViewUniforms { /// Builds view bases and clear color for the current view. fn from_frame(frame: &GraphPassFrame<'_>) -> Self { let (left, right) = skybox_world_to_view_pair(frame); - let (lx, ly, lz) = view_to_world_basis(left); - let (rx, ry, rz) = view_to_world_basis(right); let ndc_y_sign = if frame.view.offscreen_write_render_texture_asset_id.is_some() { -1.0 } else { @@ -79,12 +69,8 @@ impl SkyboxViewUniforms { }; let ortho_flag = projection_kind_orthographic_flag(frame.view.host_camera.projection_kind); Self { - view_x_left: lx, - view_y_left: ly, - view_z_left: lz, - view_x_right: rx, - view_y_right: ry, - view_z_right: rz, + view_left: left.inverse().to_cols_array(), + view_right: right.inverse().to_cols_array(), clear_color: frame.view.clear.color.to_array(), ndc_y_sign_pad: [ndc_y_sign, ortho_flag, ortho_flag, 0.0], } @@ -427,16 +413,6 @@ fn skybox_world_to_view_pair(frame: &GraphPassFrame<'_>) -> (glam::Mat4, glam::M world_to_view_pair_for_skybox(frame.shared.scene, &frame.view.host_camera) } -/// Converts a world-to-view matrix into packed view-to-world basis vectors. -fn view_to_world_basis(world_to_view: glam::Mat4) -> ([f32; 4], [f32; 4], [f32; 4]) { - let view_to_world = world_to_view.inverse(); - ( - view_to_world.x_axis.truncate().extend(0.0).to_array(), - view_to_world.y_axis.truncate().extend(0.0).to_array(), - view_to_world.z_axis.truncate().extend(0.0).to_array(), - ) -} - fn projection_kind_orthographic_flag(kind: CameraProjectionKind) -> f32 { if kind == CameraProjectionKind::Orthographic { 1.0 From 980d5d81a561d572f0c64af1dd473a9099eed697 Mon Sep 17 00:00:00 2001 From: Baplar Date: Wed, 20 May 2026 20:04:45 +0200 Subject: [PATCH 06/12] Convert from clip to ndc using viewport coordinates --- .../renderide/shaders/modules/skybox/common.wgsl | 15 ++++++++++++--- .../passes/backend/skybox_gradientskybox.wgsl | 2 +- .../passes/backend/skybox_proceduralskybox.wgsl | 2 +- .../passes/backend/skybox_projection360.wgsl | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/renderide/shaders/modules/skybox/common.wgsl b/crates/renderide/shaders/modules/skybox/common.wgsl index d19aa9cf0..be880a25d 100644 --- a/crates/renderide/shaders/modules/skybox/common.wgsl +++ b/crates/renderide/shaders/modules/skybox/common.wgsl @@ -2,6 +2,8 @@ #define_import_path renderide::skybox::common +#import renderide::frame::globals as rg + struct SkyboxView { view_left: mat4x4, view_right: mat4x4, @@ -29,6 +31,13 @@ fn fullscreen_clip_pos(vertex_index: u32) -> vec4 { return vec4(3.0 * (x - 1.0), y * 4.0 - 1.0, 0.0, 1.0); } +fn ndc_from_clip(clip_pos: vec4) -> vec2 { + return vec2( + clip_pos.x / f32(rg::frame.viewport_width), + 1 - clip_pos.y / f32(rg::frame.viewport_height), + ) * 2.0 - 1.0; +} + fn view_is_orthographic(sky: SkyboxView, view_layer: u32) -> bool { if (view_layer == 0u) { return sky.ndc_y_sign_pad.y > 0.5; @@ -39,14 +48,14 @@ fn view_is_orthographic(sky: SkyboxView, view_layer: u32) -> bool { /// Reconstructs a view-space sky ray from NDC and packed projection coefficients. fn view_ray_from_ndc(ndc: vec2, proj_params: vec4, orthographic: bool) -> vec3 { if (orthographic) { - return vec3(0.0, 0.0, 1.0); + return vec3(0.0, 0.0, -1.0); } // Asymmetric OpenXR projections store skew on the Z column. With sky rays fixed at z = -1 // and clip_w = -view_z, inverse projection uses ndc + skew. - return vec3((ndc.xy + proj_params.zw) / max(abs(proj_params.xy), vec2(0.000001)), 1.0); + return vec3((ndc.xy + proj_params.zw) / max(abs(proj_params.xy), vec2(0.000001)), -1.0); } fn world_ray_from_view_ray(view_ray: vec3, sky: SkyboxView, view_layer: u32) -> vec3 { let view_matrix = select_view_proj(sky, view_layer); - return normalize(view_matrix * vec4(view_ray, 1.0)).xyz; + return normalize(view_matrix * vec4(view_ray, 0.0)).xyz; } diff --git a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl index 7ab220231..9b6d2139c 100644 --- a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl @@ -41,7 +41,7 @@ fn vs_main( @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let ndc = in.clip_pos.xy * vec2(1.0, view.ndc_y_sign_pad.x); + let ndc = skybox::ndc_from_clip(in.clip_pos); let proj_params = select(rg::frame.proj_params_left, rg::frame.proj_params_right, in.view_layer != 0u); let view_ray = skybox::view_ray_from_ndc( ndc, diff --git a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl index 74ae7acd5..6a2a2043a 100644 --- a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl @@ -36,7 +36,7 @@ fn vs_main( @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let ndc = in.clip_pos.xy * vec2(1.0, view.ndc_y_sign_pad.x); + let ndc = skybox::ndc_from_clip(in.clip_pos); let proj_params = select(rg::frame.proj_params_left, rg::frame.proj_params_right, in.view_layer != 0u); let view_ray = skybox::view_ray_from_ndc( ndc, diff --git a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl index 67f0cce56..19f4ea7d2 100644 --- a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl @@ -115,7 +115,7 @@ fn vs_main( @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { let params = projection360_params(); - let ndc = in.clip_pos.xy * vec2(1.0, view.ndc_y_sign_pad.x); + let ndc = skybox::ndc_from_clip(in.clip_pos); let view_dir = p360m::apply_offset( base_view_dir(ndc, in.view_layer), params, From 3af9f680ac92625407667c075ba0cf6cbbfd2fb4 Mon Sep 17 00:00:00 2001 From: Baplar Date: Wed, 20 May 2026 20:47:52 +0200 Subject: [PATCH 07/12] Use common fullscreen_clip_pos --- crates/renderide/shaders/modules/skybox/common.wgsl | 6 ------ .../shaders/passes/backend/skybox_gradientskybox.wgsl | 3 ++- .../shaders/passes/backend/skybox_proceduralskybox.wgsl | 3 ++- .../shaders/passes/backend/skybox_projection360.wgsl | 3 ++- .../shaders/passes/backend/skybox_solid_color.wgsl | 3 ++- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/renderide/shaders/modules/skybox/common.wgsl b/crates/renderide/shaders/modules/skybox/common.wgsl index be880a25d..74d931e15 100644 --- a/crates/renderide/shaders/modules/skybox/common.wgsl +++ b/crates/renderide/shaders/modules/skybox/common.wgsl @@ -25,12 +25,6 @@ fn select_view_proj(view: SkyboxView, view_idx: u32) -> mat4x4 { return view.view_right; } -fn fullscreen_clip_pos(vertex_index: u32) -> vec4 { - let x = f32(vertex_index % 3u); - let y = f32(vertex_index % 2u); - return vec4(3.0 * (x - 1.0), y * 4.0 - 1.0, 0.0, 1.0); -} - fn ndc_from_clip(clip_pos: vec4) -> vec2 { return vec2( clip_pos.x / f32(rg::frame.viewport_width), diff --git a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl index 9b6d2139c..bcfb2fb7e 100644 --- a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl @@ -1,6 +1,7 @@ //! Fullscreen GradientSkybox sky draw. #import renderide::frame::globals as rg +#import renderide::core::fullscreen as fs #import renderide::skybox::common as skybox #import renderide::skybox::gradient as skygrad @@ -28,7 +29,7 @@ fn vs_main( @builtin(view_index) view_idx: u32, #endif ) -> VertexOutput { - let clip = skybox::fullscreen_clip_pos(vertex_index); + let clip = fs::fullscreen_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; #ifdef MULTIVIEW diff --git a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl index 6a2a2043a..53003e819 100644 --- a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl @@ -4,6 +4,7 @@ //! shader-specific keyword decoding for both this pass-side sky draw and the material root. #import renderide::frame::globals as rg +#import renderide::core::fullscreen as fs #import renderide::skybox::procedural as ps #import renderide::skybox::procedural_material as psmat #import renderide::skybox::common as skybox @@ -22,7 +23,7 @@ fn vs_main( @builtin(view_index) view_idx: u32, #endif ) -> VertexOutput { - let clip = skybox::fullscreen_clip_pos(vertex_index); + let clip = fs::fullscreen_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; #ifdef MULTIVIEW diff --git a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl index 19f4ea7d2..8fc14c60c 100644 --- a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl @@ -4,6 +4,7 @@ //! sampling for both this pass-side sky draw and the material root. #import renderide::frame::globals as rg +#import renderide::core::fullscreen as fs #import renderide::skybox::common as skybox #import renderide::skybox::projection360 as p360 #import renderide::skybox::projection360_material as p360m @@ -101,7 +102,7 @@ fn vs_main( @builtin(view_index) view_idx: u32, #endif ) -> VertexOutput { - let clip = skybox::fullscreen_clip_pos(vertex_index); + let clip = fs::fullscreen_clip_pos(vertex_index); var out: VertexOutput; out.clip_pos = clip; #ifdef MULTIVIEW diff --git a/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl b/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl index 024bcd41b..e4398bed5 100644 --- a/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl @@ -1,5 +1,6 @@ //! Fullscreen solid background for host `CameraClearMode::Color`. +#import renderide::core::fullscreen as fs #import renderide::skybox::common as skybox @group(0) @binding(0) var view: skybox::SkyboxView; @@ -11,7 +12,7 @@ struct VertexOutput { @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { var out: VertexOutput; - out.clip_pos = skybox::fullscreen_clip_pos(vertex_index); + out.clip_pos = fs::fullscreen_clip_pos(vertex_index); return out; } From e9eeb70490188253a0e3d94ddb14bc2c2faa1f16 Mon Sep 17 00:00:00 2001 From: Baplar Date: Wed, 20 May 2026 21:37:16 +0200 Subject: [PATCH 08/12] Disable scattering with zero atmosphere Mitigates issue with ring of light in local home --- crates/renderide/shaders/modules/skybox/procedural.wgsl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/renderide/shaders/modules/skybox/procedural.wgsl b/crates/renderide/shaders/modules/skybox/procedural.wgsl index 384c927d3..76d6f5d8e 100644 --- a/crates/renderide/shaders/modules/skybox/procedural.wgsl +++ b/crates/renderide/shaders/modules/skybox/procedural.wgsl @@ -137,7 +137,10 @@ fn evaluate_scattering( var c_in: vec3; var c_out: vec3; - if (eye_ray.y >= 0.0) { + if (length(scattering_params.kkr_in) == 0) { + c_in = vec3(0.0); + c_out = vec3(0.0); + } else if (eye_ray.y >= 0.0) { let far = sqrt(OUTER_RADIUS_SQ + INNER_RADIUS_SQ * eye_ray.y * eye_ray.y - INNER_RADIUS_SQ) - INNER_RADIUS * eye_ray.y; let height = CAMERA_POS_HEIGHT; From c96b6ef8bd4c1d708eab0fd9320f97f57b96a050 Mon Sep 17 00:00:00 2001 From: Baplar Date: Wed, 20 May 2026 23:19:38 +0200 Subject: [PATCH 09/12] Less constraining fix for the Local Home skyline --- .../renderide/shaders/modules/skybox/procedural.wgsl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/renderide/shaders/modules/skybox/procedural.wgsl b/crates/renderide/shaders/modules/skybox/procedural.wgsl index 76d6f5d8e..a038af053 100644 --- a/crates/renderide/shaders/modules/skybox/procedural.wgsl +++ b/crates/renderide/shaders/modules/skybox/procedural.wgsl @@ -130,6 +130,7 @@ fn ground_inscatter_step( fn evaluate_scattering( eye_ray: vec3, sun_dir: vec3, + params: ProceduralSkyParams, scattering_params: ScatteringParameters, ) -> ScatteringOutput { let camera_pos = vec3(0.0, CAMERA_POS_HEIGHT, 0.0); @@ -137,10 +138,7 @@ fn evaluate_scattering( var c_in: vec3; var c_out: vec3; - if (length(scattering_params.kkr_in) == 0) { - c_in = vec3(0.0); - c_out = vec3(0.0); - } else if (eye_ray.y >= 0.0) { + if (eye_ray.y >= 0.0) { let far = sqrt(OUTER_RADIUS_SQ + INNER_RADIUS_SQ * eye_ray.y * eye_ray.y - INNER_RADIUS_SQ) - INNER_RADIUS * eye_ray.y; let height = CAMERA_POS_HEIGHT; @@ -185,7 +183,8 @@ fn evaluate_scattering( ); let front_color = g.contribution; - c_in = front_color * (scattering_params.kkr_in + KKM_ESUN); + let sun_contrib = select(KKM_ESUN, 0, sun_disk_mode_none(params.sun_disk_mode)); + c_in = front_color * (scattering_params.kkr_in + sun_contrib); c_out = clamp(g.attenuate, vec3(0.0), vec3(1.0)); } @@ -227,7 +226,7 @@ fn visible_vertex_terms( ) -> ProceduralSkyVisibleTerms { let eye_ray = safe_normalize(input_eye_ray, vec3(0.0, 1.0, 0.0)); let sun_dir = safe_normalize(params.sun_direction, vec3(0.0, 1.0, 0.0)); - let scattering = evaluate_scattering(eye_ray, sun_dir, scattering_params); + let scattering = evaluate_scattering(eye_ray, sun_dir, params, scattering_params); let ground_color = params.exposure * (scattering.c_in + params.ground_color * scattering.c_out); let sky_color = params.exposure * (scattering.c_in * rayleigh_phase(sun_dir, -eye_ray)); From ce13d0cd7b439601eaa05742da457ec440c3a08d Mon Sep 17 00:00:00 2001 From: Baplar Date: Wed, 20 May 2026 23:30:54 +0200 Subject: [PATCH 10/12] Fix test expectations --- crates/renderide/src/camera/projection.rs | 3 +-- crates/renderide/src/passes/world_mesh_forward/skybox.rs | 3 +-- .../src/passes/world_mesh_forward/skybox/pipeline.rs | 8 +++----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/renderide/src/camera/projection.rs b/crates/renderide/src/camera/projection.rs index 7b625be87..534bb294a 100644 --- a/crates/renderide/src/camera/projection.rs +++ b/crates/renderide/src/camera/projection.rs @@ -304,8 +304,7 @@ mod projection_math_tests { #[test] fn skybox_shader_uses_positive_asymmetric_skew_sign() { let source = include_str!("../../shaders/modules/skybox/common.wgsl"); - assert!(source.contains("ndc.x + proj_params.z")); - assert!(source.contains("ndc.y + proj_params.w")); + assert!(source.contains("ndc.xy + proj_params.zw")); } /// Orthographic skyboxes use a parallel view ray instead of unprojecting NDC as a perspective diff --git a/crates/renderide/src/passes/world_mesh_forward/skybox.rs b/crates/renderide/src/passes/world_mesh_forward/skybox.rs index 3fa28524d..6a9eb8c1c 100644 --- a/crates/renderide/src/passes/world_mesh_forward/skybox.rs +++ b/crates/renderide/src/passes/world_mesh_forward/skybox.rs @@ -428,8 +428,7 @@ mod tests { #[test] fn skybox_view_uniforms_are_16_byte_aligned() { - assert_eq!(size_of::() % 16, 0); - assert_eq!(SKYBOX_VIEW_UNIFORM_SIZE, 128); + assert_eq!(SKYBOX_VIEW_UNIFORM_SIZE % 16, 0); } #[test] diff --git a/crates/renderide/src/passes/world_mesh_forward/skybox/pipeline.rs b/crates/renderide/src/passes/world_mesh_forward/skybox/pipeline.rs index e4cda7ba7..95044947c 100644 --- a/crates/renderide/src/passes/world_mesh_forward/skybox/pipeline.rs +++ b/crates/renderide/src/passes/world_mesh_forward/skybox/pipeline.rs @@ -47,10 +47,8 @@ impl SkyboxFamily { /// Vertex count used by the fullscreen background draw. pub(super) const fn draw_vertex_count(self) -> u32 { - match self { - Self::Projection360 | Self::Gradient => 3, - Self::Procedural => 3, - } + // A single triangle encompassing the viewport + 3 } } @@ -211,7 +209,7 @@ mod tests { fn procedural_skybox_draws_fullscreen_quad() { assert_eq!(SkyboxFamily::Projection360.draw_vertex_count(), 3); assert_eq!(SkyboxFamily::Gradient.draw_vertex_count(), 3); - assert_eq!(SkyboxFamily::Procedural.draw_vertex_count(), 6); + assert_eq!(SkyboxFamily::Procedural.draw_vertex_count(), 3); } #[test] From 06faf5ad5122bf18fa2257700922b0d0011d3b43 Mon Sep 17 00:00:00 2001 From: Baplar Date: Thu, 21 May 2026 17:56:02 +0200 Subject: [PATCH 11/12] Add defaults and pass types for skybox materials --- .../passes/backend/skybox_gradientskybox.wgsl | 1 + .../backend/skybox_proceduralskybox.wgsl | 9 +++++++++ .../passes/backend/skybox_projection360.wgsl | 18 ++++++++++++++++++ .../passes/backend/skybox_solid_color.wgsl | 1 + 4 files changed, 29 insertions(+) diff --git a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl index bcfb2fb7e..258c4d3f3 100644 --- a/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_gradientskybox.wgsl @@ -40,6 +40,7 @@ fn vs_main( return out; } +//#pass type=forward blend=off zwrite=off ztest=main @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { let ndc = skybox::ndc_from_clip(in.clip_pos); diff --git a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl index 53003e819..5d9482a29 100644 --- a/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_proceduralskybox.wgsl @@ -3,6 +3,14 @@ //! The shared ProceduralSkybox material module owns the reflected `@group(1)` contract and //! shader-specific keyword decoding for both this pass-side sky draw and the material root. +//#mat_default _SkyTint vec4 0.5 0.5 0.5 1.0 +//#mat_default _GroundColor vec4 0.369 0.349 0.341 1.0 +//#mat_default _SunColor vec4 1.0 1.0 1.0 1.0 +//#mat_default _SunDirection vec4 0.577 0.577 0.577 0.0 +//#mat_default _Exposure float 1.3 +//#mat_default _SunSize float 0.04 +//#mat_default _AtmosphereThickness float 1.0 + #import renderide::frame::globals as rg #import renderide::core::fullscreen as fs #import renderide::skybox::procedural as ps @@ -35,6 +43,7 @@ fn vs_main( return out; } +//#pass type=forward blend=off zwrite=off ztest=main @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { let ndc = skybox::ndc_from_clip(in.clip_pos); diff --git a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl index 8fc14c60c..e692d3f22 100644 --- a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl @@ -3,6 +3,23 @@ //! The shared Projection360 module owns shader-specific keyword decoding and projection //! sampling for both this pass-side sky draw and the material root. +//#texture_default _MainTex black +//#texture_default _SecondTex black +//#texture_default _TintTex white +//#texture_default _OffsetTex black +//#texture_default _OffsetMask white +//#texture_default _MainCube black +//#texture_default _SecondCube black +//#mat_default _Exposure float 1.0 +//#mat_default _FOV vec4 6.283185 3.141593 0.0 0.0 +//#mat_default _Gamma float 1.0 +//#mat_default _MaxIntensity float 4.0 +//#mat_default _OffsetMagnitude vec4 0.1 0.1 0.0 0.0 +//#mat_default _PerspectiveFOV vec4 0.785398 0.785398 0.0 0.0 +//#mat_default _Tint vec4 1.0 1.0 1.0 1.0 +//#mat_default _Tint0 vec4 1.0 0.0 0.0 1.0 +//#mat_default _Tint1 vec4 0.0 1.0 0.0 1.0 + #import renderide::frame::globals as rg #import renderide::core::fullscreen as fs #import renderide::skybox::common as skybox @@ -113,6 +130,7 @@ fn vs_main( return out; } +//#pass type=forward blend=off zwrite=off ztest=material(main) @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { let params = projection360_params(); diff --git a/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl b/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl index e4398bed5..d05f36b51 100644 --- a/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_solid_color.wgsl @@ -16,6 +16,7 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { return out; } +//#pass type=forward blend=off zwrite=off ztest=main @fragment fn fs_main(_in: VertexOutput) -> @location(0) vec4 { return view.clear_color; From 29af598d2a998583ffe3443ad612deb5eb70eca3 Mon Sep 17 00:00:00 2001 From: Baplar Date: Thu, 21 May 2026 17:57:46 +0200 Subject: [PATCH 12/12] Workaround Projection 360 skybox z-fighting with clip far plane --- crates/renderide/shaders/modules/core/math.wgsl | 2 ++ .../shaders/passes/backend/skybox_projection360.wgsl | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/renderide/shaders/modules/core/math.wgsl b/crates/renderide/shaders/modules/core/math.wgsl index fa03433f6..1296f564d 100644 --- a/crates/renderide/shaders/modules/core/math.wgsl +++ b/crates/renderide/shaders/modules/core/math.wgsl @@ -4,6 +4,8 @@ const EPSILON: f32 = 1e-6; +const MIN_FLOAT: f32 = 0x1p-126f; + fn saturate(v: f32) -> f32 { return clamp(v, 0.0, 1.0); } diff --git a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl index e692d3f22..aba5e2432 100644 --- a/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl +++ b/crates/renderide/shaders/passes/backend/skybox_projection360.wgsl @@ -22,6 +22,7 @@ #import renderide::frame::globals as rg #import renderide::core::fullscreen as fs +#import renderide::core::math as math #import renderide::skybox::common as skybox #import renderide::skybox::projection360 as p360 #import renderide::skybox::projection360_material as p360m @@ -121,7 +122,10 @@ fn vs_main( ) -> VertexOutput { let clip = fs::fullscreen_clip_pos(vertex_index); var out: VertexOutput; - out.clip_pos = clip; + /// Add MIN_FLOAT to avoid Z-fighting with far plane + /// when using wgpu::CompareFunction::Greater + /// i.e. with FrooxEngine ZTest enum LessOrEqual + out.clip_pos = clip + vec4(0.0, 0.0, math::MIN_FLOAT, 0.0); #ifdef MULTIVIEW out.view_layer = view_idx; #else