Fix varregion scene capture and legacy rendering
This commit is contained in:
@@ -33,6 +33,9 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
const WORLD_AMBIENT_BRIGHTNESS: f32 = 400.0;
|
||||
const WORLD_ENVIRONMENT_BRIGHTNESS: f32 = 600.0;
|
||||
|
||||
enum RenderCommand {
|
||||
Frame {
|
||||
camera: Camera,
|
||||
@@ -291,6 +294,7 @@ struct BevyBackend {
|
||||
mesh_cache: HashMap<u64, Vec<CachedGpuMesh>>,
|
||||
pbr_handles: Vec<Handle<PbrBevyMaterial>>,
|
||||
legacy_handles: Vec<Handle<LegacyBevyMaterial>>,
|
||||
environment_map: EnvironmentMapLight,
|
||||
target: Option<Handle<Image>>,
|
||||
}
|
||||
|
||||
@@ -314,6 +318,22 @@ impl BevyBackend {
|
||||
let plugins = plugins.disable::<bevy::winit::WinitPlugin>();
|
||||
app.add_plugins(plugins)
|
||||
.add_plugins((LegacyMaterialPlugin, PbrMaterialPlugin));
|
||||
app.insert_resource(GlobalAmbientLight {
|
||||
color: Color::WHITE,
|
||||
brightness: WORLD_AMBIENT_BRIGHTNESS,
|
||||
..default()
|
||||
});
|
||||
let environment_map = {
|
||||
let mut images = app.world_mut().resource_mut::<Assets<Image>>();
|
||||
let mut light = EnvironmentMapLight::hemispherical_gradient(
|
||||
&mut images,
|
||||
Color::srgb_u8(150, 180, 220),
|
||||
Color::srgb_u8(175, 175, 165),
|
||||
Color::srgb_u8(95, 90, 70),
|
||||
);
|
||||
light.intensity = WORLD_ENVIRONMENT_BRIGHTNESS;
|
||||
light
|
||||
};
|
||||
app.finish();
|
||||
app.cleanup();
|
||||
if !app.world().contains_resource::<RenderDevice>() {
|
||||
@@ -327,6 +347,7 @@ impl BevyBackend {
|
||||
mesh_cache: HashMap::new(),
|
||||
pbr_handles: Vec::new(),
|
||||
legacy_handles: Vec::new(),
|
||||
environment_map,
|
||||
target: None,
|
||||
})
|
||||
}
|
||||
@@ -422,6 +443,7 @@ impl BevyBackend {
|
||||
..Default::default()
|
||||
}),
|
||||
transform,
|
||||
self.environment_map.clone(),
|
||||
FrameEntity,
|
||||
))
|
||||
.id();
|
||||
@@ -630,22 +652,29 @@ fn rgba_mip_chain(texture: &Texture) -> (Vec<u8>, u32) {
|
||||
let mut next = vec![0_u8; next_width * next_height * 4];
|
||||
for y in 0..next_height {
|
||||
for x in 0..next_width {
|
||||
let mut channels = [0_u32; 4];
|
||||
let mut weighted_rgb = [0_u32; 3];
|
||||
let mut alpha = 0_u32;
|
||||
let mut samples = 0_u32;
|
||||
for source_y in y * 2..(y * 2 + 2).min(height) {
|
||||
for source_x in x * 2..(x * 2 + 2).min(width) {
|
||||
let source = (source_y * width + source_x) * 4;
|
||||
for channel in 0..4 {
|
||||
channels[channel] += u32::from(level[source + channel]);
|
||||
let sample_alpha = u32::from(level[source + 3]);
|
||||
alpha += sample_alpha;
|
||||
for channel in 0..3 {
|
||||
weighted_rgb[channel] +=
|
||||
u32::from(level[source + channel]) * sample_alpha;
|
||||
}
|
||||
samples += 1;
|
||||
}
|
||||
}
|
||||
let target = (y * next_width + x) * 4;
|
||||
for channel in 0..4 {
|
||||
next[target + channel] = u8::try_from(channels[channel] / samples)
|
||||
.expect("the average of bytes fits in a byte");
|
||||
for channel in 0..3 {
|
||||
next[target + channel] =
|
||||
u8::try_from(weighted_rgb[channel].checked_div(alpha).unwrap_or(0))
|
||||
.expect("the alpha-weighted average of bytes fits in a byte");
|
||||
}
|
||||
next[target + 3] =
|
||||
u8::try_from(alpha / samples).expect("the average of bytes fits in a byte");
|
||||
}
|
||||
}
|
||||
output.extend_from_slice(&next);
|
||||
@@ -770,10 +799,13 @@ fn legacy_as_bevy(
|
||||
slot.transform.offset[1],
|
||||
)
|
||||
};
|
||||
let cutoff = match material.alpha_mode {
|
||||
SceneAlphaMode::Mask { cutoff } => cutoff.clamp(0.0, 1.0),
|
||||
_ => -1.0,
|
||||
};
|
||||
let cutoff = material.alpha_cutoff.map_or_else(
|
||||
|| match material.alpha_mode {
|
||||
SceneAlphaMode::Mask { cutoff } => cutoff.clamp(0.0, 1.0),
|
||||
_ => -1.0,
|
||||
},
|
||||
|cutoff| cutoff.clamp(0.0, 1.0),
|
||||
);
|
||||
LegacyBevyMaterial {
|
||||
uniform: LegacyUniform {
|
||||
diffuse_color: scene_color(material.diffuse_color_srgb).to_linear(),
|
||||
@@ -967,6 +999,20 @@ fn validate_scene(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn shaders_use_their_native_texture_transform_conventions() {
|
||||
let legacy = include_str!("legacy_material.wgsl");
|
||||
assert!(legacy.contains("(centered.x * cosine + centered.y * sine) * scale_offset.x"));
|
||||
assert!(legacy.contains("(-centered.x * sine + centered.y * cosine) * scale_offset.y"));
|
||||
assert!(legacy.contains("vec2(uv.x, 1.0 - uv.y)"));
|
||||
assert!(legacy.contains("texel.a < material.surface.w"));
|
||||
|
||||
let pbr = include_str!("pbr_material.wgsl");
|
||||
assert!(pbr.contains("let scaled = uv * scale_offset.xy"));
|
||||
assert!(pbr.contains("scaled.x * cosine - scaled.y * sine"));
|
||||
assert!(pbr.contains("scaled.x * sine + scaled.y * cosine"));
|
||||
}
|
||||
use crate::{Mesh as SceneMesh, TextureSlot, UvTransform, Vertex};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -976,16 +1022,25 @@ mod tests {
|
||||
width: 2,
|
||||
height: 2,
|
||||
rgba: vec![
|
||||
0, 0, 0, 0, 100, 100, 100, 100, 200, 200, 200, 200, 255, 255, 255, 255,
|
||||
0, 0, 0, 255, 100, 100, 100, 255, 200, 200, 200, 255, 255, 255, 255, 255,
|
||||
],
|
||||
};
|
||||
let (mips, levels) = rgba_mip_chain(&texture);
|
||||
assert_eq!(levels, 2);
|
||||
assert_eq!(mips.len(), 20);
|
||||
assert_eq!(&mips[16..], &[138, 138, 138, 138]);
|
||||
assert_eq!(&mips[16..], &[138, 138, 138, 255]);
|
||||
|
||||
let cutout = Texture {
|
||||
width: 2,
|
||||
height: 2,
|
||||
rgba: vec![255, 220, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
};
|
||||
let (mips, _) = rgba_mip_chain(&cutout);
|
||||
assert_eq!(&mips[16..], &[255, 220, 0, 63]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::too_many_lines)] // One public render sequence checks reuse and orientation.
|
||||
fn bevy_headless_renders_repeated_frames_through_the_public_api() {
|
||||
let renderer = match Renderer::new_blocking() {
|
||||
Ok(renderer) => Arc::new(renderer),
|
||||
@@ -1030,6 +1085,67 @@ mod tests {
|
||||
solid_texture([255, 180, 0, 255]),
|
||||
solid_texture([30, 20, 10, 255]),
|
||||
];
|
||||
let background = renderer
|
||||
.render(
|
||||
camera,
|
||||
&[],
|
||||
&[],
|
||||
[40, 80, 120, 255],
|
||||
RenderLimits {
|
||||
width: 96,
|
||||
height: 64,
|
||||
max_triangles: 0,
|
||||
max_texture_bytes: 0,
|
||||
max_texture_pixels: 0,
|
||||
far_distance: 64,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
background
|
||||
.chunks_exact(4)
|
||||
.all(|pixel| pixel == [40, 80, 120, 255]),
|
||||
"empty scene did not preserve its clear color"
|
||||
);
|
||||
let oriented = renderer
|
||||
.render(
|
||||
camera,
|
||||
&[renderable(Material::BlinnPhong(BlinnPhongMaterial {
|
||||
diffuse: TextureSlot {
|
||||
texture: Some(0),
|
||||
..Default::default()
|
||||
},
|
||||
fullbright: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
&[Texture {
|
||||
width: 2,
|
||||
height: 2,
|
||||
rgba: vec![
|
||||
255, 0, 0, 255, 255, 0, 0, 255, 0, 0, 255, 255, 0, 0, 255, 255,
|
||||
],
|
||||
}],
|
||||
[0, 0, 0, 255],
|
||||
RenderLimits {
|
||||
width: 96,
|
||||
height: 64,
|
||||
max_triangles: 2,
|
||||
max_texture_bytes: 16,
|
||||
max_texture_pixels: 4,
|
||||
far_distance: 64,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let top = &oriented[(20 * 96 + 48) * 4..][..4];
|
||||
let bottom = &oriented[(44 * 96 + 48) * 4..][..4];
|
||||
assert!(
|
||||
top[0] > top[2],
|
||||
"legacy texture top row is vertically flipped"
|
||||
);
|
||||
assert!(
|
||||
bottom[2] > bottom[0],
|
||||
"legacy texture bottom row is vertically flipped"
|
||||
);
|
||||
for renderable in frames {
|
||||
let rgba = renderer
|
||||
.render(
|
||||
|
||||
@@ -20,28 +20,36 @@ struct LegacyUniform {
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(6) var specular_sampler: sampler;
|
||||
|
||||
fn transformed_uv(uv: vec2<f32>, scale_offset: vec4<f32>, rotation: f32) -> vec2<f32> {
|
||||
let centered = (uv - vec2(0.5)) * scale_offset.xy;
|
||||
let centered = uv - vec2(0.5);
|
||||
let sine = sin(rotation);
|
||||
let cosine = cos(rotation);
|
||||
return vec2(
|
||||
centered.x * cosine - centered.y * sine,
|
||||
centered.x * sine + centered.y * cosine,
|
||||
(centered.x * cosine + centered.y * sine) * scale_offset.x,
|
||||
(-centered.x * sine + centered.y * cosine) * scale_offset.y,
|
||||
) + vec2(0.5) + scale_offset.zw;
|
||||
}
|
||||
|
||||
fn legacy_sample_uv(uv: vec2<f32>) -> vec2<f32> {
|
||||
return vec2(uv.x, 1.0 - uv.y);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput, @builtin(front_facing) is_front: bool) -> FragmentOutput {
|
||||
let diffuse_uv = transformed_uv(in.uv, material.diffuse_scale_offset, material.rotations.x);
|
||||
let texel = textureSample(diffuse_texture, diffuse_sampler, diffuse_uv);
|
||||
let texel = textureSample(diffuse_texture, diffuse_sampler, legacy_sample_uv(diffuse_uv));
|
||||
var base = material.diffuse_color * texel * in.color;
|
||||
if material.surface.w >= 0.0 && base.a < material.surface.w {
|
||||
if material.surface.w >= 0.0 && texel.a < material.surface.w {
|
||||
discard;
|
||||
}
|
||||
|
||||
var normal = normalize(in.world_normal) * select(-1.0, 1.0, is_front);
|
||||
if material.surface.z > 0.5 {
|
||||
let normal_uv = transformed_uv(in.uv, material.normal_scale_offset, material.rotations.y);
|
||||
let tangent_normal = textureSample(normal_texture, normal_sampler, normal_uv).xyz * 2.0 - 1.0;
|
||||
let tangent_normal = textureSample(
|
||||
normal_texture,
|
||||
normal_sampler,
|
||||
legacy_sample_uv(normal_uv),
|
||||
).xyz * 2.0 - 1.0;
|
||||
let tangent = normalize(in.world_tangent.xyz);
|
||||
let bitangent = normalize(cross(normal, tangent)) * in.world_tangent.w;
|
||||
normal = normalize(mat3x3(tangent, bitangent, normal) * tangent_normal);
|
||||
@@ -52,7 +60,11 @@ fn fragment(in: VertexOutput, @builtin(front_facing) is_front: bool) -> Fragment
|
||||
let half_direction = normalize(light_direction + view_direction);
|
||||
let diffuse_light = max(dot(normal, light_direction), 0.0);
|
||||
let specular_uv = transformed_uv(in.uv, material.specular_scale_offset, material.rotations.z);
|
||||
let specular_map = textureSample(specular_texture, specular_sampler, specular_uv).rgb;
|
||||
let specular_map = textureSample(
|
||||
specular_texture,
|
||||
specular_sampler,
|
||||
legacy_sample_uv(specular_uv),
|
||||
).rgb;
|
||||
let specular_light = pow(max(dot(normal, half_direction), 0.0), material.rotations.w);
|
||||
let lit = base.rgb * (0.55 + 0.45 * diffuse_light)
|
||||
+ material.specular_color.rgb * specular_map * specular_light
|
||||
|
||||
@@ -112,6 +112,8 @@ pub struct BlinnPhongMaterial {
|
||||
pub fullbright: bool,
|
||||
pub double_sided: bool,
|
||||
pub alpha_mode: AlphaMode,
|
||||
/// Optional diffuse-texture cutout applied before face-alpha blending.
|
||||
pub alpha_cutoff: Option<f32>,
|
||||
}
|
||||
|
||||
impl Default for BlinnPhongMaterial {
|
||||
@@ -127,6 +129,7 @@ impl Default for BlinnPhongMaterial {
|
||||
fullbright: false,
|
||||
double_sided: false,
|
||||
alpha_mode: AlphaMode::Opaque,
|
||||
alpha_cutoff: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,13 +25,13 @@ struct PbrExtension {
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(108) var emissive_sampler: sampler;
|
||||
|
||||
fn transformed_uv(uv: vec2<f32>, scale_offset: vec4<f32>, rotation: f32) -> vec2<f32> {
|
||||
let centered = (uv - vec2(0.5)) * scale_offset.xy;
|
||||
let scaled = uv * scale_offset.xy;
|
||||
let sine = sin(rotation);
|
||||
let cosine = cos(rotation);
|
||||
return vec2(
|
||||
centered.x * cosine - centered.y * sine,
|
||||
centered.x * sine + centered.y * cosine,
|
||||
) + vec2(0.5) + scale_offset.zw;
|
||||
scaled.x * cosine - scaled.y * sine,
|
||||
scaled.x * sine + scaled.y * cosine,
|
||||
) + scale_offset.zw;
|
||||
}
|
||||
|
||||
@fragment
|
||||
|
||||
Reference in New Issue
Block a user