diff --git a/crates/renderide/shaders/materials/billboardunlit.wgsl b/crates/renderide/shaders/materials/billboardunlit.wgsl index f89d5999d..402fe4bf4 100644 --- a/crates/renderide/shaders/materials/billboardunlit.wgsl +++ b/crates/renderide/shaders/materials/billboardunlit.wgsl @@ -12,6 +12,7 @@ //#render_queue AlphaTest+200 //#texture_default _Tex white //#texture_default _OffsetTex black +//#texture_default _MaskTex white //#mat_default _Color vec4 1.0 1.0 1.0 1.0 //#mat_default _OffsetMagnitude vec4 0.1 0.1 0.0 0.0 //#mat_default _PointSize vec4 0.1 0.1 0.0 0.0 @@ -19,6 +20,7 @@ //#mat_default _Cutoff float 0.5 //#mat_default _Tex_LodBias float 0.0 //#mat_default _OffsetTex_LodBias float 0.0 +//#mat_default _MaskTex_LodBias float 0.0 #import renderide::core::texture_sampling as ts #import renderide::core::uv as uvu @@ -27,6 +29,7 @@ #import renderide::frame::fog as rfog #import renderide::draw::per_draw as pd #import renderide::draw::types as dt +#import renderide::material::alpha as ma #import renderide::material::variant_bits as vb #import renderide::material::vertex_color as vc #import renderide::mesh::billboard as mb @@ -36,6 +39,7 @@ struct BillboardUnlitMaterial { _Color: vec4, _Tex_ST: vec4, _RightEye_ST: vec4, + _MaskTex_ST: vec4, _OffsetTex_ST: vec4, _OffsetMagnitude: vec4, _PointSize: vec4, @@ -44,32 +48,37 @@ struct BillboardUnlitMaterial { _RenderideVariantBits: u32, _Tex_LodBias: f32, _OffsetTex_LodBias: f32, - _pad0: vec2, + _MaskTex_LodBias: f32, + _pad0: f32, } const BILLBOARDUNLIT_KW_ALPHATEST: u32 = 1u << 0u; const BILLBOARDUNLIT_KW_COLOR: u32 = 1u << 1u; -const BILLBOARDUNLIT_KW_MUL_ALPHA_INTENSITY: u32 = 1u << 2u; -const BILLBOARDUNLIT_KW_MUL_RGB_BY_ALPHA: u32 = 1u << 3u; -const BILLBOARDUNLIT_KW_OFFSET_TEXTURE: u32 = 1u << 4u; -const BILLBOARDUNLIT_KW_POINT_ROTATION: u32 = 1u << 5u; -const BILLBOARDUNLIT_KW_POINT_SIZE: u32 = 1u << 6u; -const BILLBOARDUNLIT_KW_POINT_UV: u32 = 1u << 7u; -const BILLBOARDUNLIT_KW_POLARUV: u32 = 1u << 8u; -const BILLBOARDUNLIT_KW_RIGHT_EYE_ST: u32 = 1u << 9u; -const BILLBOARDUNLIT_KW_TEXTURE: u32 = 1u << 10u; -const BILLBOARDUNLIT_KW_VERTEX_HDRSRGB_COLOR: u32 = 1u << 11u; -const BILLBOARDUNLIT_KW_VERTEX_HDRSRGBALPHA_COLOR: u32 = 1u << 12u; -const BILLBOARDUNLIT_KW_VERTEX_LINEAR_COLOR: u32 = 1u << 13u; -const BILLBOARDUNLIT_KW_VERTEX_SRGB_COLOR: u32 = 1u << 14u; -const BILLBOARDUNLIT_KW_VERTEXCOLORS: u32 = 1u << 15u; -const BILLBOARDUNLIT_KW_RENDER_BUFFER: u32 = 1u << 16u; +const BILLBOARDUNLIT_KW_MASK_TEXTURE_CLIP: u32 = 1u << 2u; +const BILLBOARDUNLIT_KW_MASK_TEXTURE_MUL: u32 = 1u << 3u; +const BILLBOARDUNLIT_KW_MUL_ALPHA_INTENSITY: u32 = 1u << 4u; +const BILLBOARDUNLIT_KW_MUL_RGB_BY_ALPHA: u32 = 1u << 5u; +const BILLBOARDUNLIT_KW_OFFSET_TEXTURE: u32 = 1u << 6u; +const BILLBOARDUNLIT_KW_POINT_ROTATION: u32 = 1u << 7u; +const BILLBOARDUNLIT_KW_POINT_SIZE: u32 = 1u << 8u; +const BILLBOARDUNLIT_KW_POINT_UV: u32 = 1u << 9u; +const BILLBOARDUNLIT_KW_POLARUV: u32 = 1u << 10u; +const BILLBOARDUNLIT_KW_RIGHT_EYE_ST: u32 = 1u << 11u; +const BILLBOARDUNLIT_KW_TEXTURE: u32 = 1u << 12u; +const BILLBOARDUNLIT_KW_VERTEX_HDRSRGB_COLOR: u32 = 1u << 13u; +const BILLBOARDUNLIT_KW_VERTEX_HDRSRGBALPHA_COLOR: u32 = 1u << 14u; +const BILLBOARDUNLIT_KW_VERTEX_LINEAR_COLOR: u32 = 1u << 15u; +const BILLBOARDUNLIT_KW_VERTEX_SRGB_COLOR: u32 = 1u << 16u; +const BILLBOARDUNLIT_KW_VERTEXCOLORS: u32 = 1u << 17u; +const BILLBOARDUNLIT_KW_RENDER_BUFFER: u32 = 1u << 18u; @group(1) @binding(0) var mat: BillboardUnlitMaterial; @group(1) @binding(1) var _Tex: texture_2d; @group(1) @binding(2) var _Tex_sampler: sampler; @group(1) @binding(3) var _OffsetTex: texture_2d; @group(1) @binding(4) var _OffsetTex_sampler: sampler; +@group(1) @binding(5) var _MaskTex: texture_2d; +@group(1) @binding(6) var _MaskTex_sampler: sampler; fn bb_kw(mask: u32) -> bool { return vb::enabled(mat._RenderideVariantBits, mask); @@ -83,6 +92,14 @@ fn kw_COLOR() -> bool { return bb_kw(BILLBOARDUNLIT_KW_COLOR); } +fn kw_MASK_TEXTURE_CLIP() -> bool { + return bb_kw(BILLBOARDUNLIT_KW_MASK_TEXTURE_CLIP); +} + +fn kw_MASK_TEXTURE_MUL() -> bool { + return bb_kw(BILLBOARDUNLIT_KW_MASK_TEXTURE_MUL); +} + fn kw_MUL_ALPHA_INTENSITY() -> bool { return bb_kw(BILLBOARDUNLIT_KW_MUL_ALPHA_INTENSITY); } @@ -402,25 +419,35 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { let use_color = kw_COLOR(); var col: vec4; - var clip_alpha: f32; if (use_texture) { let tex = sample_main_texture(in.uv, in.view_layer); - clip_alpha = tex.a; if (use_color) { col = tex * mat._Color; - clip_alpha = clip_alpha * mat._Color.a; } else { col = tex; } } else if (use_color) { col = mat._Color; - clip_alpha = mat._Color.a; } else { col = vec4(1.0); - clip_alpha = 1.0; } - if (kw_ALPHATEST() && clip_alpha < mat._Cutoff) { + let mask_clip = kw_MASK_TEXTURE_CLIP(); + let mask_mul = kw_MASK_TEXTURE_MUL(); + if (mask_mul || mask_clip) { + let uv_mask = uvu::apply_st(in.uv, mat._MaskTex_ST); + let mask_sample = ts::sample_tex_2d(_MaskTex, _MaskTex_sampler, uv_mask, mat._MaskTex_LodBias); + let mask_lum = ma::mask_luminance(mask_sample); + + if (mask_mul) { + col.a = col.a * mask_lum; + } + if (mask_clip && mask_lum < mat._Cutoff) { + discard; + } + } + + if (kw_ALPHATEST() && !mask_clip && col.a < mat._Cutoff) { discard; } @@ -433,7 +460,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { } if (kw_MUL_ALPHA_INTENSITY()) { - col = vec4(col.rgb, col.a * dot(col.rgb, vec3(0.3333333))); + col = vec4(col.rgb, ma::alpha_intensity(col.a, col.rgb)); } return rg::retain_globals_additive(rfog::apply_rgba(col, in.fog_coord)); diff --git a/crates/renderide/shaders/materials/unlit.wgsl b/crates/renderide/shaders/materials/unlit.wgsl index d6fb76d60..58acfdfc8 100644 --- a/crates/renderide/shaders/materials/unlit.wgsl +++ b/crates/renderide/shaders/materials/unlit.wgsl @@ -228,12 +228,10 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { let alpha_test = kw_ALPHATEST(); let mask_clip = kw_MASK_TEXTURE_CLIP(); let mask_mul = kw_MASK_TEXTURE_MUL(); - let mul_rgb_by_alpha = kw_MUL_RGB_BY_ALPHA(); - let uv_mask = uvu::apply_st(in.uv, mat._MaskTex_ST); var mask_lum_for_clip = 1.0; - if (mask_mul || mask_clip) { + let uv_mask = uvu::apply_st(in.uv, mat._MaskTex_ST); let mask_sample = ts::sample_tex_2d(_MaskTex, _MaskTex_sampler, uv_mask, mat._MaskTex_LodBias); let mask_lum = ma::mask_luminance(mask_sample); mask_lum_for_clip = mask_lum; @@ -266,7 +264,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { color = color * vertex_color_to_linear(in.color); } - if (mul_rgb_by_alpha) { + if (kw_MUL_RGB_BY_ALPHA()) { color = vec4(ma::apply_premultiply(color.rgb, color.a, true), color.a); } diff --git a/crates/renderide/src/materials/render_buffer_shader.rs b/crates/renderide/src/materials/render_buffer_shader.rs index da9b5e5c6..dbf29fa65 100644 --- a/crates/renderide/src/materials/render_buffer_shader.rs +++ b/crates/renderide/src/materials/render_buffer_shader.rs @@ -4,9 +4,9 @@ /// /// When set, `billboardunlit.wgsl` treats per-vertex point sizes as final particle sizes instead /// of multiplying them by the material `_PointSize`. -pub(crate) const BILLBOARD_RENDER_BUFFER_ABSOLUTE_SIZE_BIT: u32 = 1u32 << 16; +pub(crate) const BILLBOARD_RENDER_BUFFER_ABSOLUTE_SIZE_BIT: u32 = 1u32 << 18; /// Billboard/Unlit variant bit that enables per-particle color and alpha. -pub(crate) const BILLBOARD_RENDER_BUFFER_VERTEX_COLORS_BIT: u32 = 1u32 << 15; +pub(crate) const BILLBOARD_RENDER_BUFFER_VERTEX_COLORS_BIT: u32 = 1u32 << 17; /// Returns whether `stem` names an embedded Unlit-family shader other than Billboard/Unlit. pub(crate) fn is_unlit_family_embedded_stem(stem: &str) -> bool { @@ -34,15 +34,17 @@ pub(crate) fn remap_unlit_variant_bits_for_billboard(unlit_bits: u32) -> u32 { const PAIRS: &[(u32, u32)] = &[ (0, 0), (1, 1), - (4, 2), - (5, 3), - (6, 4), - (7, 8), - (8, 9), - (9, 10), - (11, 13), - (12, 14), - (13, 15), + (2, 2), + (3, 3), + (4, 4), + (5, 5), + (6, 6), + (7, 10), + (8, 11), + (9, 12), + (11, 15), + (12, 16), + (13, 17), ]; let mut out = 0u32; for &(from, to) in PAIRS { @@ -70,8 +72,8 @@ mod tests { let billboard = remap_unlit_variant_bits_for_billboard(unlit); assert_eq!(billboard & (1u32 << 1), 1u32 << 1); - assert_eq!(billboard & (1u32 << 10), 1u32 << 10); - assert_eq!(billboard & (1u32 << 9), 0); + assert_eq!(billboard & (1u32 << 12), 1u32 << 12); + assert_eq!(billboard & (1u32 << 11), 0); } #[test] diff --git a/crates/renderide/tests/shader_module_audit.rs b/crates/renderide/tests/shader_module_audit.rs index 8c53587e9..6f5d354a7 100644 --- a/crates/renderide/tests/shader_module_audit.rs +++ b/crates/renderide/tests/shader_module_audit.rs @@ -374,8 +374,9 @@ fn billboard_render_buffer_uses_indexed_corner_separate_from_sample_uv() -> io:: "Render-buffer billboards must apply renderer alignment and screen-size clamp metadata" ); assert!( - src.contains("if (kw_ALPHATEST() && clip_alpha < mat._Cutoff)") - && !src.contains("clip_alpha <= mat._Cutoff"), + src.contains("if (kw_ALPHATEST() && !mask_clip && col.a < mat._Cutoff)") + && src.contains("if (mask_clip && mask_lum < mat._Cutoff)") + && !src.contains("<= mat._Cutoff"), "Billboard/Unlit alpha test must match Unity clip(col.a - _Cutoff) equality semantics" ); assert!(