Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions crates/renderide/shaders/materials/billboardunlit.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#import renderide::frame::fog as rfog
#import renderide::draw::per_draw as pd
#import renderide::draw::types as dt
#import renderide::lighting::diffuse as dl
#import renderide::material::alpha as ma
#import renderide::material::variant_bits as vb
#import renderide::material::vertex_color as vc
Expand Down Expand Up @@ -69,8 +70,9 @@ 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_UNLIT_MASK_TEXTURE_CLIP: u32 = 1u << 17u;
const BILLBOARDUNLIT_KW_UNLIT_MASK_TEXTURE_MUL: u32 = 1u << 18u;
const BILLBOARDUNLIT_KW_SIMPLE_LIT: u32 = 1u << 17u;
const BILLBOARDUNLIT_KW_UNLIT_MASK_TEXTURE_CLIP: u32 = 1u << 18u;
const BILLBOARDUNLIT_KW_UNLIT_MASK_TEXTURE_MUL: u32 = 1u << 19u;

@group(1) @binding(0) var<uniform> mat: BillboardUnlitMaterial;
@group(1) @binding(1) var _Tex: texture_2d<f32>;
Expand Down Expand Up @@ -152,12 +154,18 @@ fn kw_VERTEXCOLORS() -> bool {
return bb_kw(BILLBOARDUNLIT_KW_VERTEXCOLORS);
}

fn kw_SIMPLE_LIT() -> bool {
return bb_kw(BILLBOARDUNLIT_KW_SIMPLE_LIT);
}

struct VertexOutput {
@builtin(position) clip_pos: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) color: vec4<f32>,
@location(2) @interpolate(flat) view_layer: u32,
@location(3) fog_coord: f32,
@location(4) world_p: vec3<f32>,
@location(5) n: vec3<f32>,
}

struct RenderBufferBillboardBasis {
Expand Down Expand Up @@ -364,6 +372,10 @@ fn vs_main(
out.color = color;
out.view_layer = layer;
out.fog_coord = rfog::coord_from_world_pos(world_p, layer);
if (kw_SIMPLE_LIT()) {
out.world_p = world_p;
out.n = rmath::safe_normalize(cross(axes.right, axes.up), vec3<f32>(0.0, 0.0, 1.0));
}
return out;
}

Expand Down Expand Up @@ -422,9 +434,17 @@ fn vertex_color(color: vec4<f32>) -> vec4<f32> {
return color;
}

fn two_sided_geometric_normal(world_n: vec3<f32>, front_facing: bool) -> vec3<f32> {
let n = normalize(world_n);
return select(-n, n, front_facing);
}

//#pass type=forward name=forward_billboard blend=material_filter offset=0,0
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
fn fs_main(
in: VertexOutput,
@builtin(front_facing) front_facing: bool,
) -> @location(0) vec4<f32> {
let use_texture = kw_TEXTURE();
let use_color = kw_COLOR();

Expand Down Expand Up @@ -473,5 +493,12 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
col = vec4<f32>(col.rgb, ma::alpha_intensity(col.a, col.rgb));
}

if (kw_SIMPLE_LIT()) {
let n = two_sided_geometric_normal(in.n, front_facing);
let base = saturate(col.rgb);
let lit = dl::shade_clustered_diffuse(in.clip_pos.xy, in.world_p, n, base, in.view_layer);
col = vec4<f32>(lit, col.a);
}

return rg::retain_globals_additive(rfog::apply_rgba(col, in.fog_coord));
}
3 changes: 1 addition & 2 deletions crates/renderide/src/materials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,7 @@ pub(crate) use pipeline_property_resolver::PipelinePropertyResolver;
/// Shader route table, optional material asset registry, and WGSL composition patches.
pub(crate) use registry::{MaterialPipelineResolution, MaterialRegistry};
pub(crate) use render_buffer_shader::{
ensure_render_buffer_billboard_variant_bits, remap_unlit_variant_bits_for_billboard,
should_remap_unlit_variant_bits_for_billboard_draw,
ensure_render_buffer_billboard_variant_bits, remap_variant_bits_for_billboard,
};
pub(crate) use router::{MaterialRouter, resolve_raster_pipeline};

Expand Down
140 changes: 118 additions & 22 deletions crates/renderide/src/materials/render_buffer_shader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,45 @@
pub(crate) const BILLBOARD_RENDER_BUFFER_ABSOLUTE_SIZE_BIT: u32 = 1u32 << 16;
/// Billboard/Unlit variant bit that enables per-particle color and alpha.
pub(crate) const BILLBOARD_RENDER_BUFFER_VERTEX_COLORS_BIT: u32 = 1u32 << 15;
/// Billboard/Unlit variant bit that enables texture sampling.
pub(crate) const BILLBOARD_RENDER_BUFFER_TEXTURE_BIT: u32 = 1u32 << 10;
/// Billboard/Unlit variant bit that enables base color.
pub(crate) const BILLBOARD_RENDER_BUFFER_COLOR_BIT: u32 = 1u32 << 1;
/// Billboard variant bit that enables simple lighting for non-unlit source materials.
pub(crate) const BILLBOARD_RENDER_BUFFER_SIMPLE_LIT_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 {
let lower = stem.to_ascii_lowercase();
(lower.starts_with("unlit") || lower.contains("_unlit") || lower.contains("unlit_"))
&& !lower.contains("billboard")
}

/// Returns whether an embedded draw should remap Unlit keyword bits to Billboard/Unlit bits.
pub(crate) fn should_remap_unlit_variant_bits_for_billboard_draw(
draw_stem: &str,
source_shader_stem: Option<&str>,
) -> bool {
draw_stem.starts_with("billboardunlit")
&& source_shader_stem.is_some_and(is_unlit_family_embedded_stem)
pub(crate) fn remap_variant_bits_for_billboard(stem: &str, source_bits: u32) -> u32 {
if is_unlit_family_embedded_stem(stem) {
BILLBOARD_RENDER_BUFFER_VERTEX_COLORS_BIT
| remap_unlit_variant_bits_for_billboard(source_bits)
} else {
BILLBOARD_RENDER_BUFFER_TEXTURE_BIT
| BILLBOARD_RENDER_BUFFER_COLOR_BIT
| BILLBOARD_RENDER_BUFFER_SIMPLE_LIT_BIT
| map_billboard_vertex_color_variant_bits(stem, source_bits)
| map_billboard_alpha_clip_variant_bits(stem, source_bits)
}
}

/// Enables render-buffer sizing and particle color semantics for synthetic billboard draws.
pub(crate) fn ensure_render_buffer_billboard_variant_bits(bits: u32) -> u32 {
bits | BILLBOARD_RENDER_BUFFER_ABSOLUTE_SIZE_BIT | BILLBOARD_RENDER_BUFFER_VERTEX_COLORS_BIT
bits | BILLBOARD_RENDER_BUFFER_ABSOLUTE_SIZE_BIT
}

/// Returns whether `stem` names an embedded Unlit-family shader other than Billboard/Unlit.
fn is_unlit_family_embedded_stem(stem: &str) -> bool {
let lower = stem.to_ascii_lowercase();
(lower.starts_with("unlit") || lower.contains("_unlit") || lower.contains("unlit_"))
&& !lower.contains("billboard")
}

/// Remaps Froox Unlit keyword bits to Billboard/Unlit keyword bits for material binding.
pub(crate) fn remap_unlit_variant_bits_for_billboard(unlit_bits: u32) -> u32 {
fn remap_unlit_variant_bits_for_billboard(unlit_bits: u32) -> u32 {
const PAIRS: &[(u32, u32)] = &[
(0, 0),
(1, 1),
(2, 17),
(3, 18),
(2, 18),
(3, 19),
(4, 2),
(5, 3),
(6, 4),
Expand All @@ -55,12 +65,71 @@ pub(crate) fn remap_unlit_variant_bits_for_billboard(unlit_bits: u32) -> u32 {
out
}

/// Remaps Froox AlphaClip or Cutoff keyword bits to Billboard/Unlit keyword bits
/// for material binding with non-Unlit materials.
fn map_billboard_alpha_clip_variant_bits(stem: &str, source_bits: u32) -> u32 {
let lower = stem.to_ascii_lowercase();
if lower.starts_with("xstoon2.0-cutout") {
return 1;
}
const ALPHA_CLIP_TWO: &[&str] = &["pbsmetallic", "pbsspecular"];
if ALPHA_CLIP_TWO
.iter()
.any(|prefix| lower.starts_with(prefix))
{
return (source_bits >> 2) & 1;
}
const ALPHA_CLIP_ONE: &[&str] = &[
"pbsdisplace",
"pbsdualsided_",
"pbsdualsidedspecular_",
"pbslerp",
"pbsslice_",
"pbsslicespecular_",
"pbsvertexcolor",
"xstoon",
];
if ALPHA_CLIP_ONE
.iter()
.any(|prefix| lower.starts_with(prefix))
{
return (source_bits >> 1) & 1;
}
const ALPHA_CLIP_ZERO: &[&str] = &["fresnel_", "pbsmultiuv", "reflection"];
if ALPHA_CLIP_ZERO
.iter()
.any(|prefix| lower.starts_with(prefix))
{
return source_bits & 1;
}
0u32
}

/// Remaps Froox Vertex Colors keyword bits to Billboard/Unlit keyword bits
/// for material binding with non-Unlit materials.
fn map_billboard_vertex_color_variant_bits(stem: &str, source_bits: u32) -> u32 {
let lower = stem.to_ascii_lowercase();
if lower.starts_with("fresnel_") {
return ((source_bits >> 10) & 1) << 17;
}
if lower.starts_with("pbsdualsidedtransparent") {
return ((source_bits >> 5) & 1) << 17;
}
if lower.starts_with("pbsvertexcolor") || lower.starts_with("pbsdualsided") {
return ((source_bits >> 6) & 1) << 17;
}
if lower.starts_with("xstoon") {
return ((source_bits >> 8) & 1) << 17;
}
0u32
}

#[cfg(test)]
mod tests {
use super::*;

const BILLBOARD_UNLIT_MASK_TEXTURE_CLIP_BIT: u32 = 1u32 << 17;
const BILLBOARD_UNLIT_MASK_TEXTURE_MUL_BIT: u32 = 1u32 << 18;
const BILLBOARD_UNLIT_MASK_TEXTURE_CLIP_BIT: u32 = 1u32 << 18;
const BILLBOARD_UNLIT_MASK_TEXTURE_MUL_BIT: u32 = 1u32 << 19;

#[test]
fn detects_unlit_family_stems() {
Expand All @@ -72,7 +141,7 @@ mod tests {
#[test]
fn remaps_unlit_texture_and_color_bits() {
let unlit = (1u32 << 1) | (1u32 << 9);
let billboard = remap_unlit_variant_bits_for_billboard(unlit);
let billboard = remap_variant_bits_for_billboard("unlit_default", unlit);

assert_eq!(billboard & (1u32 << 1), 1u32 << 1);
assert_eq!(billboard & (1u32 << 10), 1u32 << 10);
Expand All @@ -96,11 +165,38 @@ mod tests {
assert_eq!(billboard & (1u32 << 3), 0);
}

#[test]
fn remaps_pbs_alphaclip_bits() {
let pbs = (1u32 << 2) | (1u32 << 7);
let billboard = remap_variant_bits_for_billboard("pbsmetallic_default", pbs);

assert_eq!(
billboard,
BILLBOARD_RENDER_BUFFER_COLOR_BIT
| BILLBOARD_RENDER_BUFFER_TEXTURE_BIT
| BILLBOARD_RENDER_BUFFER_SIMPLE_LIT_BIT
| 1u32
);
}

#[test]
fn remaps_pbs_no_alphaclip_bits() {
let pbs = 1u32 << 7;
let billboard = remap_variant_bits_for_billboard("pbsmetallic_default", pbs);

assert_eq!(
billboard,
BILLBOARD_RENDER_BUFFER_COLOR_BIT
| BILLBOARD_RENDER_BUFFER_TEXTURE_BIT
| BILLBOARD_RENDER_BUFFER_SIMPLE_LIT_BIT
);
}

#[test]
fn render_buffer_variant_bit_is_reserved() {
assert_eq!(
ensure_render_buffer_billboard_variant_bits(0),
BILLBOARD_RENDER_BUFFER_ABSOLUTE_SIZE_BIT | BILLBOARD_RENDER_BUFFER_VERTEX_COLORS_BIT
BILLBOARD_RENDER_BUFFER_ABSOLUTE_SIZE_BIT
);
}
}
21 changes: 10 additions & 11 deletions crates/renderide/src/passes/world_mesh_forward/material_batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ use crate::materials::{
MaterialBlendMode, MaterialPipelineDesc, MaterialPipelineResolution, MaterialPipelineSet,
MaterialPipelineVariantSpec, MaterialRegistry, MaterialRenderState,
MaterialShaderSpecializationKey, RasterFrontFace, RasterPipelineKind, RasterPrimitiveTopology,
ensure_render_buffer_billboard_variant_bits, remap_unlit_variant_bits_for_billboard,
should_remap_unlit_variant_bits_for_billboard_draw,
ensure_render_buffer_billboard_variant_bits, remap_variant_bits_for_billboard,
};
use crate::passes::WorldMeshForwardEncodeRefs;
use crate::render_graph::frame_upload_batch::GraphUploadSink;
Expand Down Expand Up @@ -483,17 +482,17 @@ impl<'a> MaterialDrawResolver<'a> {
let source_bits = self
.registry
.and_then(|registry| registry.variant_bits_for_shader_asset(batch_key.shader_asset_id));
let mut bits = source_bits.unwrap_or(0);
if should_remap_unlit_variant_bits_for_billboard_draw(
stem,
self.registry
.and_then(|registry| registry.stem_for_shader_asset(batch_key.shader_asset_id)),
) {
bits = remap_unlit_variant_bits_for_billboard(bits);
if !stem.starts_with("billboardunlit") {
return source_bits;
}
if crate::particles::is_generated_billboard_mesh_asset_id(item.mesh_asset_id)
&& stem.starts_with("billboardunlit")
let mut bits = source_bits.unwrap_or(0);
if let Some(source_stem) = self
.registry
.and_then(|registry| registry.stem_for_shader_asset(batch_key.shader_asset_id))
{
bits = remap_variant_bits_for_billboard(source_stem, bits);
}
if crate::particles::is_generated_billboard_mesh_asset_id(item.mesh_asset_id) {
Some(ensure_render_buffer_billboard_variant_bits(bits))
} else {
source_bits.map(|_| bits)
Expand Down
6 changes: 6 additions & 0 deletions crates/renderide/src/world_mesh/materials/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,12 @@ pub(crate) fn apply_render_buffer_mesh_pipeline_override(
uses_blended_depth_write: features.uses_blended_depth_write,
uses_two_sided_transparency: features.uses_two_sided_transparency,
});
if batch_key.blend_mode == MaterialBlendMode::StemDefault
&& batch_key.transparent_class == TransparentMaterialClass::OrderedAlpha
{
// Enforce transparent blend mode to match source material transparency
batch_key.blend_mode = MaterialBlendMode::UnityBlend { src: 5, dst: 10 }
}
}

/// Assembles a [`MaterialDrawBatchKey`] from a pre-resolved [`ResolvedMaterialBatch`] entry.
Expand Down
14 changes: 12 additions & 2 deletions crates/renderide/tests/shader_module_audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,12 @@ fn billboard_render_buffer_uses_indexed_corner_separate_from_sample_uv() -> io::
);
}
assert!(
src.contains("const BILLBOARDUNLIT_KW_UNLIT_MASK_TEXTURE_CLIP: u32 = 1u << 17u;")
&& src.contains("const BILLBOARDUNLIT_KW_UNLIT_MASK_TEXTURE_MUL: u32 = 1u << 18u;"),
src.contains("const BILLBOARDUNLIT_KW_SIMPLE_LIT: u32 = 1u << 17u;"),
"Non-Unlit shading support for render-buffer billboards must use compatibility bit after native Billboard/Unlit keywords"
);
assert!(
src.contains("const BILLBOARDUNLIT_KW_UNLIT_MASK_TEXTURE_CLIP: u32 = 1u << 18u;")
&& src.contains("const BILLBOARDUNLIT_KW_UNLIT_MASK_TEXTURE_MUL: u32 = 1u << 19u;"),
"Unlit mask support for render-buffer billboards must use compatibility bits after native Billboard/Unlit keywords"
);
assert!(
Expand Down Expand Up @@ -440,6 +444,12 @@ fn billboard_render_buffer_uses_indexed_corner_separate_from_sample_uv() -> io::
&& src.contains("rfog::apply_rgba(col, in.fog_coord)"),
"Billboard/Unlit must preserve the source-authored UNITY_APPLY_FOG hook"
);
assert!(
src.contains("if (kw_SIMPLE_LIT())")
&& src.contains("out.n = ")
&& src.contains("dl::shade_clustered_diffuse"),
"Billboard/Unlit must offer simple shading capabilities for non-Unlit source materials"
);

Ok(())
}
Expand Down
Loading