bevy_pbr2: Add support for most of the StandardMaterial textures (#4)

* bevy_pbr2: Add support for most of the StandardMaterial textures

Normal maps are not included here as they require tangents in a vertex attribute.

* bevy_pbr2: Ensure RenderCommandQueue is ready for PbrShaders init

* texture_pipelined: Add a light to the scene so we can see stuff

* WIP bevy_pbr2: back to front sorting hack

* bevy_pbr2: Uniform control flow for texture sampling in pbr.frag

From 'fintelia' on the Bevy Render Rework Round 2 discussion:

"My understanding is that GPUs these days never use the "execute both branches
and select the result" strategy. Rather, what they do is evaluate the branch
condition on all threads of a warp, and jump over it if all of them evaluate to
false. If even a single thread needs to execute the if statement body, however,
then the remaining threads are paused until that is completed."

* bevy_pbr2: Simplify texture and sampler names

The StandardMaterial_ prefix is no longer needed

* bevy_pbr2: Match default 'AmbientColor' of current bevy_pbr for now

* bevy_pbr2: Convert from non-linear to linear sRGB for the color uniform

* bevy_pbr2: Add pbr_pipelined example

* Fix view vector in pbr frag to work in ortho

* bevy_pbr2: Use a 90 degree y fov and light range projection for lights

* bevy_pbr2: Add AmbientLight resource

* bevy_pbr2: Convert PointLight color to linear sRGB for use in fragment shader

* bevy_pbr2: pbr.frag: Rename PointLight.projection to view_projection

The uniform contains the view_projection matrix so this was incorrect.

* bevy_pbr2: PointLight is an OmniLight as it has a radius

* bevy_pbr2: Factoring out duplicated code

* bevy_pbr2: Implement RenderAsset for StandardMaterial

* Remove unnecessary texture and sampler clones

* fix comment formatting

* remove redundant Buffer:from

* Don't extract meshes when their material textures aren't ready

* make missing textures in the queue step an error

Co-authored-by: Aevyrie <aevyrie@gmail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Robert Swain 2021-06-28 01:10:23 +02:00 committed by Carter Anderson
parent 25de2d1819
commit b1a91a823f
26 changed files with 891 additions and 245 deletions

View file

@ -166,6 +166,10 @@ path = "examples/3d/parenting.rs"
name = "pbr"
path = "examples/3d/pbr.rs"
[[example]]
name = "pbr_pipelined"
path = "examples/3d/pbr_pipelined.rs"
[[example]]
name = "render_to_texture"
path = "examples/3d/render_to_texture.rs"
@ -178,6 +182,10 @@ path = "examples/3d/spawner.rs"
name = "texture"
path = "examples/3d/texture.rs"
[[example]]
name = "texture_pipelined"
path = "examples/3d/texture_pipelined.rs"
[[example]]
name = "update_gltf_scene"
path = "examples/3d/update_gltf_scene.rs"

View file

@ -160,7 +160,7 @@ pub struct EventReader<'s, 'w, T: Component> {
#[derive(SystemParam)]
pub struct EventWriter<'s, 'w, T: Component> {
events: ResMut<'w, Events<T>>,
// TODO: this isn't ideal ... maybe the SystemParam derive can be smarter about world and state lifetimes?
// TODO: this isn't ideal ... maybe the SystemParam derive can be smarter about world and state lifetimes?
#[system_param(ignore)]
marker: PhantomData<&'s usize>,
}

View file

@ -87,7 +87,10 @@ impl<Param: SystemParam> SystemState<Param> {
/// Retrieve the [`SystemParam`] values. This can only be called when all parameters are read-only.
#[inline]
pub fn get<'s, 'w>(&'s mut self, world: &'w World) -> <Param::Fetch as SystemParamFetch<'s, 'w>>::Item
pub fn get<'s, 'w>(
&'s mut self,
world: &'w World,
) -> <Param::Fetch as SystemParamFetch<'s, 'w>>::Item
where
Param::Fetch: ReadOnlySystemParamFetch,
{

View file

@ -372,7 +372,7 @@ where
pub fn get_mut(
&mut self,
entity: Entity,
) -> Result<<Q::Fetch as Fetch<'w,'s>>::Item, QueryEntityError> {
) -> Result<<Q::Fetch as Fetch<'w, 's>>::Item, QueryEntityError> {
// SAFE: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict
unsafe {
@ -406,7 +406,10 @@ where
/// entity does not have the given component type or if the given component type does not match
/// this query.
#[inline]
pub fn get_component<T: Component>(&self, entity: Entity) -> Result<&'w T, QueryComponentError> {
pub fn get_component<T: Component>(
&self,
entity: Entity,
) -> Result<&'w T, QueryComponentError> {
let world = self.world;
let entity_ref = world
.get_entity(entity)

View file

@ -146,7 +146,8 @@ where
fn default_config() {}
}
impl<'s,'w, Q: WorldQuery + 'static, F: WorldQuery + 'static> SystemParamFetch<'s, 'w> for QueryState<Q, F>
impl<'s, 'w, Q: WorldQuery + 'static, F: WorldQuery + 'static> SystemParamFetch<'s, 'w>
for QueryState<Q, F>
where
F::Fetch: FilterFetch,
{

View file

@ -72,7 +72,7 @@ impl Command for PushChildren {
}
impl<'s, 'w, 'a> ChildBuilder<'s, 'w, 'a> {
pub fn spawn_bundle(&mut self, bundle: impl Bundle) -> EntityCommands<'s,'w, '_> {
pub fn spawn_bundle(&mut self, bundle: impl Bundle) -> EntityCommands<'s, 'w, '_> {
let e = self.commands.spawn_bundle(bundle);
self.push_children.children.push(e.id());
e

View file

@ -4,7 +4,7 @@ use bevy::{
ecs::prelude::*,
input::Input,
math::Vec3,
pbr2::{PbrBundle, PointLightBundle, StandardMaterial},
pbr2::{OmniLightBundle, PbrBundle, StandardMaterial},
prelude::{App, Assets, KeyCode, Transform},
render2::{
camera::PerspectiveCameraBundle,
@ -36,8 +36,8 @@ fn setup(
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
material: materials.add(StandardMaterial {
color: Color::INDIGO,
roughness: 1.0,
base_color: Color::INDIGO,
perceptual_roughness: 1.0,
..Default::default()
}),
..Default::default()
@ -47,8 +47,8 @@ fn setup(
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(StandardMaterial {
color: Color::PINK,
roughness: 0.0,
base_color: Color::PINK,
perceptual_roughness: 0.0,
metallic: 1.0,
reflectance: 1.0,
..Default::default()
@ -65,7 +65,7 @@ fn setup(
..Default::default()
})),
material: materials.add(StandardMaterial {
color: Color::LIME_GREEN,
base_color: Color::LIME_GREEN,
..Default::default()
}),
transform: Transform::from_xyz(1.5, 1.0, 1.5),
@ -73,7 +73,7 @@ fn setup(
})
.insert(Movable);
// light
commands.spawn_bundle(PointLightBundle {
commands.spawn_bundle(OmniLightBundle {
transform: Transform::from_xyz(5.0, 8.0, 2.0),
..Default::default()
});

View file

@ -0,0 +1,86 @@
use bevy::{
ecs::prelude::*,
math::Vec3,
pbr2::{OmniLight, OmniLightBundle, PbrBundle, StandardMaterial},
prelude::{App, Assets, Transform},
render2::{
camera::{OrthographicCameraBundle, OrthographicProjection},
color::Color,
mesh::{shape, Mesh},
},
PipelinedDefaultPlugins,
};
/// This example shows how to configure Physically Based Rendering (PBR) parameters.
fn main() {
App::new()
.add_plugins(PipelinedDefaultPlugins)
.add_startup_system(setup.system())
.run();
}
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// add entities to the world
for y in -2..=2 {
for x in -5..=5 {
let x01 = (x + 5) as f32 / 10.0;
let y01 = (y + 2) as f32 / 4.0;
// sphere
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Icosphere {
radius: 0.45,
subdivisions: 32,
})),
material: materials.add(StandardMaterial {
base_color: Color::hex("ffd891").unwrap(),
// vary key PBR parameters on a grid of spheres to show the effect
metallic: y01,
perceptual_roughness: x01,
..Default::default()
}),
transform: Transform::from_xyz(x as f32, y as f32 + 0.5, 0.0),
..Default::default()
});
}
}
// unlit sphere
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Icosphere {
radius: 0.45,
subdivisions: 32,
})),
material: materials.add(StandardMaterial {
base_color: Color::hex("ffd891").unwrap(),
// vary key PBR parameters on a grid of spheres to show the effect
unlit: true,
..Default::default()
}),
transform: Transform::from_xyz(-5.0, -2.5, 0.0),
..Default::default()
});
// light
commands.spawn_bundle(OmniLightBundle {
transform: Transform::from_translation(Vec3::new(50.0, 50.0, 50.0)),
omni_light: OmniLight {
intensity: 50000.,
range: 100.,
..Default::default()
},
..Default::default()
});
// camera
commands.spawn_bundle(OrthographicCameraBundle {
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 8.0))
.looking_at(Vec3::default(), Vec3::Y),
orthographic_projection: OrthographicProjection {
scale: 0.01,
..Default::default()
},
..OrthographicCameraBundle::new_3d()
});
}

View file

@ -0,0 +1,101 @@
use bevy::{
ecs::prelude::*,
math::{Quat, Vec2, Vec3},
pbr2::{PbrBundle, StandardMaterial},
prelude::{App, AssetServer, Assets, Transform},
render2::{
camera::PerspectiveCameraBundle,
color::Color,
mesh::{shape, Mesh},
},
PipelinedDefaultPlugins,
};
/// This example shows various ways to configure texture materials in 3D
fn main() {
App::new()
.add_plugins(PipelinedDefaultPlugins)
.add_startup_system(setup.system())
.run();
}
/// sets up a scene with textured entities
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// load a texture and retrieve its aspect ratio
let texture_handle = asset_server.load("branding/bevy_logo_dark_big.png");
let aspect = 0.25;
// create a new quad mesh. this is what we will apply the texture to
let quad_width = 8.0;
let quad_handle = meshes.add(Mesh::from(shape::Quad::new(Vec2::new(
quad_width,
quad_width * aspect,
))));
// this material renders the texture normally
let material_handle = materials.add(StandardMaterial {
base_color_texture: Some(texture_handle.clone()),
unlit: true,
..Default::default()
});
// this material modulates the texture to make it red (and slightly transparent)
let red_material_handle = materials.add(StandardMaterial {
base_color: Color::rgba(1.0, 0.0, 0.0, 0.5),
base_color_texture: Some(texture_handle.clone()),
unlit: true,
..Default::default()
});
// and lets make this one blue! (and also slightly transparent)
let blue_material_handle = materials.add(StandardMaterial {
base_color: Color::rgba(0.0, 0.0, 1.0, 0.5),
base_color_texture: Some(texture_handle),
unlit: true,
..Default::default()
});
// textured quad - normal
commands.spawn_bundle(PbrBundle {
mesh: quad_handle.clone(),
material: material_handle,
transform: Transform {
translation: Vec3::new(0.0, 0.0, 1.5),
rotation: Quat::from_rotation_x(-std::f32::consts::PI / 5.0),
..Default::default()
},
..Default::default()
});
// textured quad - modulated
commands.spawn_bundle(PbrBundle {
mesh: quad_handle.clone(),
material: red_material_handle,
transform: Transform {
translation: Vec3::new(0.0, 0.0, 0.0),
rotation: Quat::from_rotation_x(-std::f32::consts::PI / 5.0),
..Default::default()
},
..Default::default()
});
// textured quad - modulated
commands.spawn_bundle(PbrBundle {
mesh: quad_handle,
material: blue_material_handle,
transform: Transform {
translation: Vec3::new(0.0, 0.0, -1.5),
rotation: Quat::from_rotation_x(-std::f32::consts::PI / 5.0),
..Default::default()
},
..Default::default()
});
// camera
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(3.0, 5.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});
}

View file

@ -25,6 +25,8 @@ bevy_transform = { path = "../../crates/bevy_transform", version = "0.5.0" }
bevy_utils = { path = "../../crates/bevy_utils", version = "0.5.0" }
# other
bitflags = "1.2"
# direct dependency required for derive macro
bytemuck = { version = "1", features = ["derive"] }
crevice = { path = "../../crates/crevice" }
crevice = { path = "../../crates/crevice" }
wgpu = "0.8"

View file

@ -1,4 +1,4 @@
use crate::{PointLight, StandardMaterial};
use crate::{OmniLight, StandardMaterial};
use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle;
use bevy_render2::mesh::Mesh;
@ -25,8 +25,8 @@ impl Default for PbrBundle {
/// A component bundle for "light" entities
#[derive(Debug, Bundle, Default)]
pub struct PointLightBundle {
pub point_light: PointLight,
pub struct OmniLightBundle {
pub omni_light: OmniLight,
pub transform: Transform,
pub global_transform: GlobalTransform,
}

View file

@ -28,7 +28,8 @@ pub struct PbrPlugin;
impl Plugin for PbrPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(StandardMaterialPlugin);
app.add_plugin(StandardMaterialPlugin)
.init_resource::<AmbientLight>();
let render_app = app.sub_app_mut(0);
render_app
@ -46,6 +47,8 @@ impl Plugin for PbrPlugin {
RenderStage::PhaseSort,
sort_phase_system::<ShadowPhase>.system(),
)
// FIXME: Hack to ensure RenderCommandQueue is initialized when PbrShaders is being initialized
// .init_resource::<RenderCommandQueue>()
.init_resource::<PbrShaders>()
.init_resource::<ShadowShaders>()
.init_resource::<MeshMeta>()

View file

@ -2,19 +2,19 @@ use bevy_ecs::reflect::ReflectComponent;
use bevy_reflect::Reflect;
use bevy_render2::color::Color;
/// A point light
/// An omnidirectional light
#[derive(Debug, Clone, Copy, Reflect)]
#[reflect(Component)]
pub struct PointLight {
pub struct OmniLight {
pub color: Color,
pub intensity: f32,
pub range: f32,
pub radius: f32,
}
impl Default for PointLight {
impl Default for OmniLight {
fn default() -> Self {
PointLight {
OmniLight {
color: Color::rgb(1.0, 1.0, 1.0),
intensity: 200.0,
range: 20.0,
@ -22,3 +22,20 @@ impl Default for PointLight {
}
}
}
// Ambient light color.
#[derive(Debug)]
pub struct AmbientLight {
pub color: Color,
/// Color is premultiplied by brightness before being passed to the shader
pub brightness: f32,
}
impl Default for AmbientLight {
fn default() -> Self {
Self {
color: Color::rgb(1.0, 1.0, 1.0),
brightness: 0.05,
}
}
}

View file

@ -1,20 +1,29 @@
use bevy_app::{App, CoreStage, EventReader, Plugin};
use bevy_asset::{AddAsset, AssetEvent, Assets};
use bevy_ecs::prelude::*;
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, Handle};
use bevy_math::Vec4;
use bevy_reflect::TypeUuid;
use bevy_render2::{
color::Color,
render_asset::{RenderAsset, RenderAssetPlugin},
render_resource::{Buffer, BufferInitDescriptor, BufferUsage},
renderer::RenderDevice,
renderer::{RenderDevice, RenderQueue},
texture::Image,
};
use bevy_utils::HashSet;
use crevice::std140::{AsStd140, Std140};
// TODO: this shouldn't live in the StandardMaterial type
#[derive(Debug, Clone)]
pub struct StandardMaterialGpuData {
pub buffer: Buffer,
// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.frag!
bitflags::bitflags! {
#[repr(transparent)]
struct StandardMaterialFlags: u32 {
const BASE_COLOR_TEXTURE = (1 << 0);
const EMISSIVE_TEXTURE = (1 << 1);
const METALLIC_ROUGHNESS_TEXTURE = (1 << 2);
const OCCLUSION_TEXTURE = (1 << 3);
const DOUBLE_SIDED = (1 << 4);
const UNLIT = (1 << 5);
const NONE = 0;
const UNINITIALIZED = 0xFFFF;
}
}
/// A material with "standard" properties used in PBR lighting
@ -23,46 +32,55 @@ pub struct StandardMaterialGpuData {
#[uuid = "7494888b-c082-457b-aacf-517228cc0c22"]
pub struct StandardMaterial {
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
/// in between.
pub color: Color,
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
/// Defaults to minimum of 0.089
pub roughness: f32,
/// From [0.0, 1.0], dielectric to pure metallic
pub metallic: f32,
/// Specular intensity for non-metals on a linear scale of [0.0, 1.0]
/// defaults to 0.5 which is mapped to 4% reflectance in the shader
pub reflectance: f32,
/// in between. If used together with a base_color_texture, this is factored into the final
/// base color as `base_color * base_color_texture_value`
pub base_color: Color,
pub base_color_texture: Option<Handle<Image>>,
// Use a color for user friendliness even though we technically don't use the alpha channel
// Might be used in the future for exposure correction in HDR
pub emissive: Color,
pub gpu_data: Option<StandardMaterialGpuData>,
}
impl StandardMaterial {
pub fn gpu_data(&self) -> Option<&StandardMaterialGpuData> {
self.gpu_data.as_ref()
}
pub emissive_texture: Option<Handle<Image>>,
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
/// Defaults to minimum of 0.089
/// If used together with a roughness/metallic texture, this is factored into the final base
/// color as `roughness * roughness_texture_value`
pub perceptual_roughness: f32,
/// From [0.0, 1.0], dielectric to pure metallic
/// If used together with a roughness/metallic texture, this is factored into the final base
/// color as `metallic * metallic_texture_value`
pub metallic: f32,
pub metallic_roughness_texture: Option<Handle<Image>>,
/// Specular intensity for non-metals on a linear scale of [0.0, 1.0]
/// defaults to 0.5 which is mapped to 4% reflectance in the shader
pub reflectance: f32,
pub occlusion_texture: Option<Handle<Image>>,
pub double_sided: bool,
pub unlit: bool,
}
impl Default for StandardMaterial {
fn default() -> Self {
StandardMaterial {
color: Color::rgb(1.0, 1.0, 1.0),
base_color: Color::rgb(1.0, 1.0, 1.0),
base_color_texture: None,
emissive: Color::BLACK,
emissive_texture: None,
// This is the minimum the roughness is clamped to in shader code
// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/
// It's the minimum floating point value that won't be rounded down to 0 in the
// calculations used. Although technically for 32-bit floats, 0.045 could be
// used.
roughness: 0.089,
perceptual_roughness: 0.089,
// Few materials are purely dielectric or metallic
// This is just a default for mostly-dielectric
metallic: 0.01,
metallic_roughness_texture: None,
// Minimum real-world reflectance is 2%, most materials between 2-5%
// Expressed in a linear scale and equivalent to 4% reflectance see https://google.github.io/filament/Material%20Properties.pdf
reflectance: 0.5,
emissive: Color::BLACK,
gpu_data: None,
occlusion_texture: None,
double_sided: false,
unlit: false,
}
}
}
@ -70,7 +88,16 @@ impl Default for StandardMaterial {
impl From<Color> for StandardMaterial {
fn from(color: Color) -> Self {
StandardMaterial {
color,
base_color: color,
..Default::default()
}
}
}
impl From<Handle<Image>> for StandardMaterial {
fn from(texture: Handle<Image>) -> Self {
StandardMaterial {
base_color_texture: Some(texture),
..Default::default()
}
}
@ -80,7 +107,10 @@ impl From<Color> for StandardMaterial {
pub struct StandardMaterialUniformData {
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
/// in between.
pub color: Vec4,
pub base_color: Vec4,
// Use a color for user friendliness even though we technically don't use the alpha channel
// Might be used in the future for exposure correction in HDR
pub emissive: Vec4,
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
/// Defaults to minimum of 0.089
pub roughness: f32,
@ -89,71 +119,81 @@ pub struct StandardMaterialUniformData {
/// Specular intensity for non-metals on a linear scale of [0.0, 1.0]
/// defaults to 0.5 which is mapped to 4% reflectance in the shader
pub reflectance: f32,
// Use a color for user friendliness even though we technically don't use the alpha channel
// Might be used in the future for exposure correction in HDR
pub emissive: Vec4,
pub flags: u32,
}
pub struct StandardMaterialPlugin;
impl Plugin for StandardMaterialPlugin {
fn build(&self, app: &mut App) {
app.add_asset::<StandardMaterial>().add_system_to_stage(
CoreStage::PostUpdate,
standard_material_resource_system.system(),
);
app.add_plugin(RenderAssetPlugin::<StandardMaterial>::default())
.add_asset::<StandardMaterial>();
}
}
pub fn standard_material_resource_system(
render_device: Res<RenderDevice>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut material_events: EventReader<AssetEvent<StandardMaterial>>,
) {
let mut changed_materials = HashSet::default();
for event in material_events.iter() {
match event {
AssetEvent::Created { ref handle } => {
changed_materials.insert(handle.clone_weak());
}
AssetEvent::Modified { ref handle } => {
changed_materials.insert(handle.clone_weak());
// TODO: uncomment this to support mutated materials
// remove_current_material_resources(render_resource_context, handle, &mut materials);
}
AssetEvent::Removed { ref handle } => {
// if material was modified and removed in the same update, ignore the modification
// events are ordered so future modification events are ok
changed_materials.remove(handle);
}
}
#[derive(Debug, Clone)]
pub struct GpuStandardMaterial {
pub buffer: Buffer,
// FIXME: image handles feel unnecessary here but the extracted asset is discarded
pub base_color_texture: Option<Handle<Image>>,
pub emissive_texture: Option<Handle<Image>>,
pub metallic_roughness_texture: Option<Handle<Image>>,
pub occlusion_texture: Option<Handle<Image>>,
}
impl RenderAsset for StandardMaterial {
type ExtractedAsset = StandardMaterial;
type PreparedAsset = GpuStandardMaterial;
fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone()
}
// update changed material data
for changed_material_handle in changed_materials.iter() {
if let Some(material) = materials.get_mut(changed_material_handle) {
// TODO: this avoids creating new materials each frame because storing gpu data in the material flags it as
// modified. this prevents hot reloading and therefore can't be used in an actual impl.
if material.gpu_data.is_some() {
continue;
}
fn prepare_asset(
material: Self::ExtractedAsset,
render_device: &RenderDevice,
_render_queue: &RenderQueue,
) -> Self::PreparedAsset {
let mut flags = StandardMaterialFlags::NONE;
if material.base_color_texture.is_some() {
flags |= StandardMaterialFlags::BASE_COLOR_TEXTURE;
}
if material.emissive_texture.is_some() {
flags |= StandardMaterialFlags::EMISSIVE_TEXTURE;
}
if material.metallic_roughness_texture.is_some() {
flags |= StandardMaterialFlags::METALLIC_ROUGHNESS_TEXTURE;
}
if material.occlusion_texture.is_some() {
flags |= StandardMaterialFlags::OCCLUSION_TEXTURE;
}
if material.double_sided {
flags |= StandardMaterialFlags::DOUBLE_SIDED;
}
if material.unlit {
flags |= StandardMaterialFlags::UNLIT;
}
let value = StandardMaterialUniformData {
base_color: material.base_color.as_rgba_linear().into(),
emissive: material.emissive.into(),
roughness: material.perceptual_roughness,
metallic: material.metallic,
reflectance: material.reflectance,
flags: flags.bits,
};
let value_std140 = value.as_std140();
let value = StandardMaterialUniformData {
color: material.color.into(),
roughness: material.roughness,
metallic: material.metallic,
reflectance: material.reflectance,
emissive: material.emissive.into(),
};
let value_std140 = value.as_std140();
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
label: None,
usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
contents: value_std140.as_bytes(),
});
material.gpu_data = Some(StandardMaterialGpuData { buffer });
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
label: None,
usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
contents: value_std140.as_bytes(),
});
GpuStandardMaterial {
buffer,
base_color_texture: material.base_color_texture,
emissive_texture: material.emissive_texture,
metallic_roughness_texture: material.metallic_roughness_texture,
occlusion_texture: material.occlusion_texture,
}
}
}

View file

@ -1,4 +1,4 @@
use crate::{ExtractedMeshes, MeshMeta, PbrShaders, PointLight};
use crate::{AmbientLight, ExtractedMeshes, MeshMeta, OmniLight, PbrShaders};
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_math::{Mat4, Vec3, Vec4};
use bevy_render2::{
@ -17,6 +17,11 @@ use bevy_transform::components::GlobalTransform;
use crevice::std140::AsStd140;
use std::num::NonZeroU32;
pub struct ExtractedAmbientLight {
color: Color,
brightness: f32,
}
pub struct ExtractedPointLight {
color: Color,
intensity: f32,
@ -38,16 +43,17 @@ pub struct GpuLight {
#[repr(C)]
#[derive(Copy, Clone, Debug, AsStd140)]
pub struct GpuLights {
ambient_color: Vec4,
len: u32,
lights: [GpuLight; MAX_POINT_LIGHTS],
lights: [GpuLight; MAX_OMNI_LIGHTS],
}
// NOTE: this must be kept in sync MAX_POINT_LIGHTS in pbr.frag
pub const MAX_POINT_LIGHTS: usize = 10;
// NOTE: this must be kept in sync MAX_OMNI_LIGHTS in pbr.frag
pub const MAX_OMNI_LIGHTS: usize = 10;
pub const SHADOW_SIZE: Extent3d = Extent3d {
width: 1024,
height: 1024,
depth_or_array_layers: MAX_POINT_LIGHTS as u32,
depth_or_array_layers: MAX_OMNI_LIGHTS as u32,
};
pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float;
@ -167,8 +173,13 @@ impl FromWorld for ShadowShaders {
// TODO: ultimately these could be filtered down to lights relevant to actual views
pub fn extract_lights(
mut commands: Commands,
lights: Query<(Entity, &PointLight, &GlobalTransform)>,
ambient_light: Res<AmbientLight>,
lights: Query<(Entity, &OmniLight, &GlobalTransform)>,
) {
commands.insert_resource(ExtractedAmbientLight {
color: ambient_light.color,
brightness: ambient_light.brightness,
});
for (entity, light, transform) in lights.iter() {
commands.get_or_spawn(entity).insert(ExtractedPointLight {
color: light.color,
@ -203,6 +214,7 @@ pub fn prepare_lights(
render_device: Res<RenderDevice>,
mut light_meta: ResMut<LightMeta>,
views: Query<Entity, With<RenderPhase<Transparent3dPhase>>>,
ambient_light: Res<ExtractedAmbientLight>,
lights: Query<&ExtractedPointLight>,
) {
// PERF: view.iter().count() could be views.iter().len() if we implemented ExactSizeIterator for archetype-only filters
@ -210,6 +222,7 @@ pub fn prepare_lights(
.view_gpu_lights
.reserve_and_clear(views.iter().count(), &render_device);
let ambient_color = ambient_light.color.as_rgba_linear() * ambient_light.brightness;
// set up light data for each view
for entity in views.iter() {
let light_depth_texture = texture_cache.get(
@ -227,12 +240,13 @@ pub fn prepare_lights(
let mut view_lights = Vec::new();
let mut gpu_lights = GpuLights {
ambient_color: ambient_color.into(),
len: lights.iter().len() as u32,
lights: [GpuLight::default(); MAX_POINT_LIGHTS],
lights: [GpuLight::default(); MAX_OMNI_LIGHTS],
};
// TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query
for (i, light) in lights.iter().enumerate().take(MAX_POINT_LIGHTS) {
for (i, light) in lights.iter().enumerate().take(MAX_OMNI_LIGHTS) {
let depth_texture_view =
light_depth_texture
.texture
@ -250,12 +264,13 @@ pub fn prepare_lights(
let view_transform = GlobalTransform::from_translation(light.transform.translation)
.looking_at(Vec3::default(), Vec3::Y);
// TODO: configure light projection based on light configuration
let projection = Mat4::perspective_rh(1.0472, 1.0, 1.0, 20.0);
let projection =
Mat4::perspective_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1, light.range);
gpu_lights.lights[i] = GpuLight {
// premultiply color by intensity
// we don't use the alpha at all, so no reason to multiply only [0..3]
color: (light.color * light.intensity).into(),
color: (light.color.as_rgba_linear() * light.intensity).into(),
radius: light.radius.into(),
position: light.transform.translation.into(),
range: 1.0 / (light.range * light.range),

View file

@ -11,15 +11,19 @@ use bevy_render2::{
render_graph::{Node, NodeRunError, RenderGraphContext},
render_phase::{Draw, DrawFunctions, Drawable, RenderPhase, TrackedRenderPass},
render_resource::*,
renderer::{RenderContext, RenderDevice},
renderer::{RenderContext, RenderDevice, RenderQueue},
shader::Shader,
texture::BevyDefault,
view::{ViewMeta, ViewUniform, ViewUniformOffset},
texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo},
view::{ExtractedView, ViewMeta, ViewUniform, ViewUniformOffset},
};
use bevy_transform::components::GlobalTransform;
use bevy_utils::slab::{FrameSlabMap, FrameSlabMapKey};
use crevice::std140::AsStd140;
use std::borrow::Cow;
use wgpu::{
Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDimension, TextureFormat,
TextureViewDescriptor,
};
use crate::{StandardMaterial, StandardMaterialUniformData};
@ -29,6 +33,8 @@ pub struct PbrShaders {
view_layout: BindGroupLayout,
material_layout: BindGroupLayout,
mesh_layout: BindGroupLayout,
// This dummy white texture is to be used in place of optional StandardMaterial textures
dummy_white_gpu_image: GpuImage,
}
// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system
@ -122,18 +128,104 @@ impl FromWorld for PbrShaders {
});
let material_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: BufferSize::new(
StandardMaterialUniformData::std140_size_static() as u64,
),
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: BufferSize::new(
StandardMaterialUniformData::std140_size_static() as u64,
),
},
count: None,
},
count: None,
}],
// Base Color Texture
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
},
count: None,
},
// Base Color Texture Sampler
BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Sampler {
comparison: false,
filtering: true,
},
count: None,
},
// Emissive Texture
BindGroupLayoutEntry {
binding: 3,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
},
count: None,
},
// Emissive Texture Sampler
BindGroupLayoutEntry {
binding: 4,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Sampler {
comparison: false,
filtering: true,
},
count: None,
},
// Metallic Roughness Texture
BindGroupLayoutEntry {
binding: 5,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
},
count: None,
},
// Metallic Roughness Texture Sampler
BindGroupLayoutEntry {
binding: 6,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Sampler {
comparison: false,
filtering: true,
},
count: None,
},
// Occlusion Texture
BindGroupLayoutEntry {
binding: 7,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
},
count: None,
},
// Occlusion Texture Sampler
BindGroupLayoutEntry {
binding: 8,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Sampler {
comparison: false,
filtering: true,
},
count: None,
},
],
label: None,
});
@ -222,12 +314,53 @@ impl FromWorld for PbrShaders {
},
});
// A 1x1x1 'all 1.0' texture to use as a dummy texture to use in place of optional StandardMaterial textures
let dummy_white_gpu_image = {
let image = Image::new_fill(
Extent3d::default(),
TextureDimension::D2,
&[255u8; 4],
TextureFormat::bevy_default(),
);
let texture = render_device.create_texture(&image.texture_descriptor);
let sampler = render_device.create_sampler(&image.sampler_descriptor);
let format_size = image.texture_descriptor.format.pixel_size();
let render_queue = world.get_resource_mut::<RenderQueue>().unwrap();
render_queue.write_texture(
ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: Origin3d::ZERO,
},
&image.data,
ImageDataLayout {
offset: 0,
bytes_per_row: Some(
std::num::NonZeroU32::new(
image.texture_descriptor.size.width * format_size as u32,
)
.unwrap(),
),
rows_per_image: None,
},
image.texture_descriptor.size,
);
let texture_view = texture.create_view(&TextureViewDescriptor::default());
GpuImage {
texture,
texture_view,
sampler,
}
};
PbrShaders {
pipeline,
view_layout,
material_layout,
mesh_layout,
vertex_shader_module,
dummy_white_gpu_image,
}
}
}
@ -235,8 +368,8 @@ impl FromWorld for PbrShaders {
struct ExtractedMesh {
transform: Mat4,
mesh: Handle<Mesh>,
material_buffer: Buffer,
transform_binding_offset: u32,
material_handle: Handle<StandardMaterial>,
}
pub struct ExtractedMeshes {
@ -247,6 +380,7 @@ pub fn extract_meshes(
mut commands: Commands,
meshes: Res<Assets<Mesh>>,
materials: Res<Assets<StandardMaterial>>,
images: Res<Assets<Image>>,
query: Query<(&GlobalTransform, &Handle<Mesh>, &Handle<StandardMaterial>)>,
) {
let mut extracted_meshes = Vec::new();
@ -254,15 +388,37 @@ pub fn extract_meshes(
if !meshes.contains(mesh_handle) {
continue;
}
if let Some(material) = materials.get(material_handle) {
if let Some(material_gpu_data) = &material.gpu_data() {
extracted_meshes.push(ExtractedMesh {
transform: transform.compute_matrix(),
mesh: mesh_handle.clone_weak(),
material_buffer: material_gpu_data.buffer.clone(),
transform_binding_offset: 0,
});
if let Some(ref image) = material.base_color_texture {
if !images.contains(image) {
continue;
}
}
if let Some(ref image) = material.emissive_texture {
if !images.contains(image) {
continue;
}
}
if let Some(ref image) = material.metallic_roughness_texture {
if !images.contains(image) {
continue;
}
}
if let Some(ref image) = material.occlusion_texture {
if !images.contains(image) {
continue;
}
}
extracted_meshes.push(ExtractedMesh {
transform: transform.compute_matrix(),
mesh: mesh_handle.clone_weak(),
transform_binding_offset: 0,
material_handle: material_handle.clone_weak(),
});
} else {
continue;
}
}
@ -306,6 +462,23 @@ pub struct MeshViewBindGroups {
view: BindGroup,
}
fn image_handle_to_view_sampler<'a>(
pbr_shaders: &'a PbrShaders,
gpu_images: &'a RenderAssets<Image>,
image_option: &Option<Handle<Image>>,
) -> (&'a TextureView, &'a Sampler) {
image_option.as_ref().map_or(
(
&pbr_shaders.dummy_white_gpu_image.texture_view,
&pbr_shaders.dummy_white_gpu_image.sampler,
),
|image_handle| {
let gpu_image = gpu_images.get(image_handle).expect("only materials with valid textures should be drawn");
(&gpu_image.texture_view, &gpu_image.sampler)
},
)
}
pub fn queue_meshes(
mut commands: Commands,
draw_functions: Res<DrawFunctions>,
@ -316,7 +489,14 @@ pub fn queue_meshes(
mut light_meta: ResMut<LightMeta>,
view_meta: Res<ViewMeta>,
mut extracted_meshes: ResMut<ExtractedMeshes>,
mut views: Query<(Entity, &ViewLights, &mut RenderPhase<Transparent3dPhase>)>,
gpu_images: Res<RenderAssets<Image>>,
render_materials: Res<RenderAssets<StandardMaterial>>,
mut views: Query<(
Entity,
&ExtractedView,
&ViewLights,
&mut RenderPhase<Transparent3dPhase>,
)>,
mut view_light_shadow_phases: Query<&mut RenderPhase<ShadowPhase>>,
) {
let mesh_meta = mesh_meta.into_inner();
@ -346,7 +526,7 @@ pub fn queue_meshes(
layout: &pbr_shaders.mesh_layout,
})
});
for (entity, view_lights, mut transparent_phase) in views.iter_mut() {
for (entity, view, view_lights, mut transparent_phase) in views.iter_mut() {
// TODO: cache this?
let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
@ -379,30 +559,107 @@ pub fn queue_meshes(
mesh_meta.mesh_draw_info.clear();
mesh_meta.material_bind_groups.next_frame();
let view_matrix = view.transform.compute_matrix();
let view_row_2 = view_matrix.row(2);
for (i, mesh) in extracted_meshes.meshes.iter_mut().enumerate() {
let material_bind_group_key = mesh_meta.material_bind_groups.get_or_insert_with(
mesh.material_buffer.id(),
|| {
render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: mesh.material_buffer.as_entire_binding(),
}],
label: None,
layout: &pbr_shaders.material_layout,
})
},
);
let gpu_material = &render_materials
.get(&mesh.material_handle)
.expect("Failed to get StandardMaterial PreparedAsset");
let material_bind_group_key =
mesh_meta
.material_bind_groups
.get_or_insert_with(gpu_material.buffer.id(), || {
let (base_color_texture_view, base_color_sampler) =
image_handle_to_view_sampler(
&pbr_shaders,
&gpu_images,
&gpu_material.base_color_texture,
);
let (emissive_texture_view, emissive_sampler) =
image_handle_to_view_sampler(
&pbr_shaders,
&gpu_images,
&gpu_material.emissive_texture,
);
let (metallic_roughness_texture_view, metallic_roughness_sampler) =
image_handle_to_view_sampler(
&pbr_shaders,
&gpu_images,
&gpu_material.metallic_roughness_texture,
);
let (occlusion_texture_view, occlusion_sampler) =
image_handle_to_view_sampler(
&pbr_shaders,
&gpu_images,
&gpu_material.occlusion_texture,
);
render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
binding: 0,
resource: gpu_material.buffer.as_entire_binding(),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(
&base_color_texture_view,
),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::Sampler(&base_color_sampler),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::TextureView(&emissive_texture_view),
},
BindGroupEntry {
binding: 4,
resource: BindingResource::Sampler(&emissive_sampler),
},
BindGroupEntry {
binding: 5,
resource: BindingResource::TextureView(
&metallic_roughness_texture_view,
),
},
BindGroupEntry {
binding: 6,
resource: BindingResource::Sampler(&metallic_roughness_sampler),
},
BindGroupEntry {
binding: 7,
resource: BindingResource::TextureView(&occlusion_texture_view),
},
BindGroupEntry {
binding: 8,
resource: BindingResource::Sampler(&occlusion_sampler),
},
],
label: None,
layout: &pbr_shaders.material_layout,
})
});
mesh_meta.mesh_draw_info.push(MeshDrawInfo {
material_bind_group_key,
});
// NOTE: row 2 of the view matrix dotted with column 3 of the model matrix
// gives the z component of translation of the mesh in view space
let mesh_z = view_row_2.dot(mesh.transform.col(3));
// FIXME: Switch from usize to u64 for portability and use sort key encoding
// similar to https://realtimecollisiondetection.net/blog/?p=86 as appropriate
// FIXME: What is the best way to map from view space z to a number of bits of unsigned integer?
let sort_key = (((mesh_z * 1000.0) as usize) << 10)
| (material_bind_group_key.index() & ((1 << 10) - 1));
// TODO: currently there is only "transparent phase". this should pick transparent vs opaque according to the mesh material
transparent_phase.add(Drawable {
draw_function: draw_pbr,
draw_key: i,
sort_key: material_bind_group_key.index(), // TODO: sort back-to-front, sorting by material for now
sort_key,
});
}
@ -499,6 +756,7 @@ impl Draw for DrawPbr {
let mesh_draw_info = &mesh_meta.mesh_draw_info[draw_key];
pass.set_bind_group(
2,
// &mesh_meta.material_bind_groups[sort_key & ((1 << 10) - 1)],
&mesh_meta.material_bind_groups[mesh_draw_info.material_bind_group_key],
&[],
);

View file

@ -1,22 +1,74 @@
#version 450
// From the Filament design doc
// https://google.github.io/filament/Filament.html#table_symbols
// Symbol Definition
// v View unit vector
// l Incident light unit vector
// n Surface normal unit vector
// h Half unit vector between l and v
// f BRDF
// f_d Diffuse component of a BRDF
// f_r Specular component of a BRDF
// α Roughness, remapped from using input perceptualRoughness
// σ Diffuse reflectance
// Ω Spherical domain
// f0 Reflectance at normal incidence
// f90 Reflectance at grazing angle
// χ+(a) Heaviside function (1 if a>0 and 0 otherwise)
// nior Index of refraction (IOR) of an interface
// ⟨n⋅l⟩ Dot product clamped to [0..1]
// ⟨a⟩ Saturated value (clamped to [0..1])
// The Bidirectional Reflectance Distribution Function (BRDF) describes the surface response of a standard material
// and consists of two components, the diffuse component (f_d) and the specular component (f_r):
// f(v,l) = f_d(v,l) + f_r(v,l)
//
// The form of the microfacet model is the same for diffuse and specular
// f_r(v,l) = f_d(v,l) = 1 / { |n⋅v||n⋅l| } ∫_Ω D(m,α) G(v,l,m) f_m(v,l,m) (v⋅m) (l⋅m) dm
//
// In which:
// D, also called the Normal Distribution Function (NDF) models the distribution of the microfacets
// G models the visibility (or occlusion or shadow-masking) of the microfacets
// f_m is the microfacet BRDF and differs between specular and diffuse components
//
// The above integration needs to be approximated.
layout(location = 0) in vec4 v_WorldPosition;
layout(location = 1) in vec3 v_WorldNormal;
layout(location = 2) in vec2 v_Uv;
layout(location = 0) out vec4 o_Target;
struct PointLight {
struct OmniLight {
vec4 color;
float range;
float radius;
vec3 position;
mat4 projection;
mat4 view_projection;
};
// NOTE: this must be kept in sync with lights::MAX_LIGHTS
// NOTE: this must be kept in sync with the constants defined bevy_pbr2/src/render/light.rs
// TODO: this can be removed if we move to storage buffers for light arrays
const int MAX_POINT_LIGHTS = 10;
const int MAX_OMNI_LIGHTS = 10;
struct StandardMaterial_t {
vec4 base_color;
vec4 emissive;
float perceptual_roughness;
float metallic;
float reflectance;
// 'flags' is a bit field indicating various option. uint is 32 bits so we have up to 32 options.
uint flags;
};
// NOTE: These must match those defined in bevy_pbr2/src/material.rs
const uint FLAGS_BASE_COLOR_TEXTURE_BIT = (1 << 0);
const uint FLAGS_EMISSIVE_TEXTURE_BIT = (1 << 1);
const uint FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT = (1 << 2);
const uint FLAGS_OCCLUSION_TEXTURE_BIT = (1 << 3);
const uint FLAGS_DOUBLE_SIDED_BIT = (1 << 4);
const uint FLAGS_UNLIT_BIT = (1 << 5);
// View bindings - set 0
layout(set = 0, binding = 0) uniform View {
@ -24,24 +76,30 @@ layout(set = 0, binding = 0) uniform View {
vec3 ViewWorldPosition;
};
layout(std140, set = 0, binding = 1) uniform Lights {
vec4 AmbientColor;
uint NumLights;
PointLight PointLights[MAX_POINT_LIGHTS];
OmniLight OmniLights[MAX_OMNI_LIGHTS];
};
layout(set = 0, binding = 2) uniform texture2DArray t_Shadow;
layout(set = 0, binding = 3) uniform samplerShadow s_Shadow;
// Material bindings - set 2
struct StandardMaterial_t {
vec4 color;
float roughness;
float metallic;
float reflectance;
vec4 emissive;
};
layout(set = 2, binding = 0) uniform StandardMaterial {
StandardMaterial_t Material;
};
layout(set = 2, binding = 1) uniform texture2D base_color_texture;
layout(set = 2, binding = 2) uniform sampler base_color_sampler;
layout(set = 2, binding = 3) uniform texture2D emissive_texture;
layout(set = 2, binding = 4) uniform sampler emissive_sampler;
layout(set = 2, binding = 5) uniform texture2D metallic_roughness_texture;
layout(set = 2, binding = 6) uniform sampler metallic_roughness_sampler;
layout(set = 2, binding = 7) uniform texture2D occlusion_texture;
layout(set = 2, binding = 8) uniform sampler occlusion_sampler;
# define saturate(x) clamp(x, 0.0, 1.0)
const float PI = 3.141592653589793;
@ -200,7 +258,7 @@ vec3 reinhard_extended_luminance(vec3 color, float max_white_l) {
return change_luminance(color, l_new);
}
vec3 point_light(PointLight light, float roughness, float NdotV, vec3 N, vec3 V, vec3 R, vec3 F0, vec3 diffuseColor) {
vec3 omni_light(OmniLight light, float roughness, float NdotV, vec3 N, vec3 V, vec3 R, vec3 F0, vec3 diffuseColor) {
vec3 light_to_frag = light.position.xyz - v_WorldPosition.xyz;
float distance_square = dot(light_to_frag, light_to_frag);
float rangeAttenuation =
@ -265,47 +323,101 @@ float fetch_shadow(int light_id, vec4 homogeneous_coords) {
}
void main() {
vec4 color = Material.color;
float metallic = Material.metallic;
float reflectance = Material.reflectance;
float perceptual_roughness = Material.roughness;
vec3 emissive = Material.emissive.xyz;
vec3 ambient_color = vec3(0.1, 0.1, 0.1);
float occlusion = 1.0;
float roughness = perceptualRoughnessToRoughness(perceptual_roughness);
vec3 N = normalize(v_WorldNormal);
vec3 V = normalize(ViewWorldPosition.xyz - v_WorldPosition.xyz);
vec3 R = reflect(-V, N);
// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
float NdotV = max(dot(N, V), 1e-4);
// Remapping [0,1] reflectance to F0
// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping
vec3 F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + color.rgb * metallic;
// Diffuse strength inversely related to metallicity
vec3 diffuse_color = color.rgb * (1.0 - metallic);
vec3 output_color = vec3(0.0);
for (int i = 0; i < int(NumLights); ++i) {
PointLight light = PointLights[i];
vec3 light_contrib = point_light(light, roughness, NdotV, N, V, R, F0, diffuse_color);
float shadow = fetch_shadow(i, light.projection * v_WorldPosition);
output_color += light_contrib * shadow;
vec4 output_color = Material.base_color;
if ((Material.flags & FLAGS_BASE_COLOR_TEXTURE_BIT) != 0) {
output_color *= texture(sampler2D(base_color_texture, base_color_sampler), v_Uv);
}
vec3 diffuse_ambient = EnvBRDFApprox(diffuse_color, 1.0, NdotV);
vec3 specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV);
// NOTE: Unlit bit not set means == 0 is true, so the true case is if lit
if ((Material.flags & FLAGS_UNLIT_BIT) == 0) {
// TODO use .a for exposure compensation in HDR
vec4 emissive = Material.emissive;
if ((Material.flags & FLAGS_EMISSIVE_TEXTURE_BIT) != 0) {
emissive.rgb *= texture(sampler2D(emissive_texture, emissive_sampler), v_Uv).rgb;
}
output_color += (diffuse_ambient + specular_ambient) * ambient_color * occlusion;
output_color += emissive * color.a;
// calculate non-linear roughness from linear perceptualRoughness
float metallic = Material.metallic;
float perceptual_roughness = Material.perceptual_roughness;
if ((Material.flags & FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0) {
vec4 metallic_roughness = texture(sampler2D(metallic_roughness_texture, metallic_roughness_sampler), v_Uv);
// Sampling from GLTF standard channels for now
metallic *= metallic_roughness.b;
perceptual_roughness *= metallic_roughness.g;
}
float roughness = perceptualRoughnessToRoughness(perceptual_roughness);
// tone_mapping
output_color = reinhard_luminance(output_color);
// Gamma correction.
// Not needed with sRGB buffer
// output_color = pow(output_color, vec3(1.0 / 2.2));
float occlusion = 1.0;
if ((Material.flags & FLAGS_OCCLUSION_TEXTURE_BIT) != 0) {
occlusion = texture(sampler2D(occlusion_texture, occlusion_sampler), v_Uv).r;
}
o_Target = vec4(output_color, 1.0);
vec3 N = normalize(v_WorldNormal);
// FIXME: Normal maps need an additional vertex attribute and vertex stage output/fragment stage input
// Just use a separate shader for lit with normal maps?
// # ifdef STANDARDMATERIAL_NORMAL_MAP
// vec3 T = normalize(v_WorldTangent.xyz);
// vec3 B = cross(N, T) * v_WorldTangent.w;
// # endif
if ((Material.flags & FLAGS_DOUBLE_SIDED_BIT) != 0) {
N = gl_FrontFacing ? N : -N;
// # ifdef STANDARDMATERIAL_NORMAL_MAP
// T = gl_FrontFacing ? T : -T;
// B = gl_FrontFacing ? B : -B;
// # endif
}
// # ifdef STANDARDMATERIAL_NORMAL_MAP
// mat3 TBN = mat3(T, B, N);
// N = TBN * normalize(texture(sampler2D(normal_map, normal_map_sampler), v_Uv).rgb * 2.0 - 1.0);
// # endif
vec3 V;
if (ViewProj[3][3] != 1.0) { // If the projection is not orthographic
// Only valid for a perpective projection
V = normalize(ViewWorldPosition.xyz - v_WorldPosition.xyz);
} else {
// Ortho view vec
V = normalize(vec3(-ViewProj[0][2], -ViewProj[1][2], -ViewProj[2][2]));
}
// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
float NdotV = max(dot(N, V), 1e-4);
// Remapping [0,1] reflectance to F0
// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping
float reflectance = Material.reflectance;
vec3 F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + output_color.rgb * metallic;
// Diffuse strength inversely related to metallicity
vec3 diffuse_color = output_color.rgb * (1.0 - metallic);
vec3 R = reflect(-V, N);
// accumulate color
vec3 light_accum = vec3(0.0);
for (int i = 0; i < int(NumLights); ++i) {
OmniLight light = OmniLights[i];
vec3 light_contrib = omni_light(light, roughness, NdotV, N, V, R, F0, diffuse_color);
float shadow = fetch_shadow(i, light.view_projection * v_WorldPosition);
light_accum += light_contrib * shadow;
}
vec3 diffuse_ambient = EnvBRDFApprox(diffuse_color, 1.0, NdotV);
vec3 specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV);
output_color.rgb = light_accum;
output_color.rgb += (diffuse_ambient + specular_ambient) * AmbientColor.rgb * occlusion;
output_color.rgb += emissive.rgb * output_color.a;
// tone_mapping
output_color.rgb = reinhard_luminance(output_color.rgb);
// Gamma correction.
// Not needed with sRGB buffer
// output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2));
}
o_Target = output_color;
}

View file

@ -19,7 +19,10 @@ layout(set = 1, binding = 0) uniform MeshTransform {
void main() {
v_Uv = Vertex_Uv;
v_WorldPosition = Model * vec4(Vertex_Position, 1.0);
vec4 world_position = Model * vec4(Vertex_Position, 1.0);
v_WorldPosition = world_position;
// FIXME: The inverse transpose of the model matrix should be used to correctly handle scaling
// of normals
v_WorldNormal = mat3(Model) * Vertex_Normal;
gl_Position = ViewProj * v_WorldPosition;
gl_Position = ViewProj * world_position;
}

View file

@ -544,22 +544,18 @@ impl RenderAsset for Mesh {
_render_queue: &RenderQueue,
) -> Self::PreparedAsset {
let vertex_buffer_data = mesh.get_vertex_buffer_data();
let vertex_buffer = Buffer::from(render_device.create_buffer_with_data(
&BufferInitDescriptor {
usage: BufferUsage::VERTEX,
label: None,
contents: &vertex_buffer_data,
},
));
let vertex_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsage::VERTEX,
label: None,
contents: &vertex_buffer_data,
});
let index_info = mesh.get_index_buffer_bytes().map(|data| GpuIndexInfo {
buffer: Buffer::from(render_device.create_buffer_with_data(
&BufferInitDescriptor {
usage: BufferUsage::INDEX,
contents: &data,
label: None,
},
)),
buffer: render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsage::INDEX,
contents: &data,
label: None,
}),
count: mesh.indices().unwrap().len() as u32,
});

View file

@ -1,6 +1,4 @@
use crate::{
mesh::{Indices, Mesh},
};
use crate::mesh::{Indices, Mesh};
use bevy_math::{Vec2, Vec3};
use wgpu::PrimitiveTopology;

View file

@ -1,5 +1,8 @@
use bevy_utils::Uuid;
use std::{ops::{Bound, Deref, RangeBounds}, sync::Arc};
use std::{
ops::{Bound, Deref, RangeBounds},
sync::Arc,
};
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
pub struct BufferId(Uuid);
@ -41,7 +44,7 @@ impl From<wgpu::Buffer> for Buffer {
id: BufferId(Uuid::new_v4()),
value: Arc::new(value),
}
}
}
}
impl Deref for Buffer {
@ -79,4 +82,4 @@ impl<'a> Deref for BufferSlice<'a> {
fn deref(&self) -> &Self::Target {
&self.value
}
}
}

View file

@ -21,11 +21,12 @@ pub use wgpu::{
BindGroupLayoutEntry, BindingResource, BindingType, BlendComponent, BlendFactor,
BlendOperation, BlendState, BufferAddress, BufferBindingType, BufferSize, BufferUsage,
ColorTargetState, ColorWrite, CompareFunction, DepthBiasState, DepthStencilState, Extent3d,
Face, FilterMode, FragmentState, FrontFace, IndexFormat, InputStepMode, MultisampleState,
PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology,
RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, SamplerDescriptor,
ShaderFlags, ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStage, StencilFaceState,
StencilState, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat,
TextureSampleType, TextureUsage, TextureViewDescriptor, TextureViewDimension, VertexAttribute,
VertexBufferLayout, VertexFormat, VertexState,RenderPassDepthStencilAttachment, Operations, LoadOp
Face, FilterMode, FragmentState, FrontFace, IndexFormat, InputStepMode, LoadOp,
MultisampleState, Operations, PipelineLayoutDescriptor, PolygonMode, PrimitiveState,
PrimitiveTopology, RenderPassColorAttachment, RenderPassDepthStencilAttachment,
RenderPassDescriptor, RenderPipelineDescriptor, SamplerDescriptor, ShaderFlags, ShaderModule,
ShaderModuleDescriptor, ShaderSource, ShaderStage, StencilFaceState, StencilState,
TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType,
TextureUsage, TextureViewDescriptor, TextureViewDimension, VertexAttribute, VertexBufferLayout,
VertexFormat, VertexState,
};

View file

@ -154,4 +154,4 @@ impl Deref for SwapChainFrame {
fn deref(&self) -> &Self::Target {
&self.value
}
}
}

View file

@ -1,4 +1,4 @@
#[allow(clippy::module_inception)]
mod shader;
pub use shader::*;
pub use shader::*;

View file

@ -1,10 +1,9 @@
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
use bevy_reflect::{TypeUuid, Uuid};
use bevy_utils::{tracing::error, BoxedFuture};
use wgpu::ShaderStage;
use std::marker::Copy;
use thiserror::Error;
use wgpu::ShaderStage;
/// An error that occurs during shader handling.
#[derive(Error, Debug)]

View file

@ -7,18 +7,15 @@ mod texture_cache;
pub(crate) mod image_texture_conversion;
pub use self::image::*;
#[cfg(feature = "hdr")]
pub use hdr_texture_loader::*;
pub use self::image::*;
pub use image_texture_loader::*;
pub use texture_cache::*;
use crate::{
render_asset::RenderAssetPlugin,
RenderStage,
};
use crate::{render_asset::RenderAssetPlugin, RenderStage};
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset};
use bevy_asset::AddAsset;
use bevy_ecs::prelude::*;
// TODO: replace Texture names with Image names?