diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index e3f0e4d25851b..25da841eb3664 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -95,6 +95,12 @@ async fn load_gltf<'a, 'b>( if let Some(texture) = material.occlusion_texture() { linear_textures.insert(texture.texture().index()); } + if let Some(texture) = material + .pbr_metallic_roughness() + .metallic_roughness_texture() + { + linear_textures.insert(texture.texture().index()); + } } let mut meshes = vec![]; @@ -122,6 +128,13 @@ async fn load_gltf<'a, 'b>( mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute); } + if let Some(vertex_attribute) = reader + .read_tangents() + .map(|v| VertexAttributeValues::Float4(v.collect())) + { + mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); + } + if let Some(vertex_attribute) = reader .read_tex_coords(0) .map(|v| VertexAttributeValues::Float2(v.into_f32().collect())) @@ -279,8 +292,52 @@ async fn load_gltf<'a, 'b>( fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle { let material_label = material_label(&material); + let pbr = material.pbr_metallic_roughness(); - let texture_handle = if let Some(info) = pbr.base_color_texture() { + + let color = pbr.base_color_factor(); + let base_color_texture = if let Some(info) = pbr.base_color_texture() { + // TODO: handle info.tex_coord() (the *set* index for the right texcoords) + let label = texture_label(&info.texture()); + let path = AssetPath::new_ref(load_context.path(), Some(&label)); + Some(load_context.get_handle(path)) + } else { + None + }; + + let normal_map = if let Some(normal_texture) = material.normal_texture() { + // TODO: handle normal_texture.scale + // TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords) + let label = texture_label(&normal_texture.texture()); + let path = AssetPath::new_ref(load_context.path(), Some(&label)); + Some(load_context.get_handle(path)) + } else { + None + }; + + let metallic_roughness_texture = if let Some(info) = pbr.metallic_roughness_texture() { + // TODO: handle info.tex_coord() (the *set* index for the right texcoords) + let label = texture_label(&info.texture()); + let path = AssetPath::new_ref(load_context.path(), Some(&label)); + Some(load_context.get_handle(path)) + } else { + None + }; + + let occlusion_texture = if let Some(occlusion_texture) = material.occlusion_texture() { + // TODO: handle occlusion_texture.tex_coord() (the *set* index for the right texcoords) + // TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength) + let label = texture_label(&occlusion_texture.texture()); + let path = AssetPath::new_ref(load_context.path(), Some(&label)); + Some(load_context.get_handle(path)) + } else { + None + }; + + let emissive = material.emissive_factor(); + let emissive_texture = if let Some(info) = material.emissive_texture() { + // TODO: handle occlusion_texture.tex_coord() (the *set* index for the right texcoords) + // TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength) let label = texture_label(&info.texture()); let path = AssetPath::new_ref(load_context.path(), Some(&label)); Some(load_context.get_handle(path)) @@ -288,14 +345,19 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle< None }; - let color = pbr.base_color_factor(); load_context.set_labeled_asset( &material_label, LoadedAsset::new(StandardMaterial { base_color: Color::rgba(color[0], color[1], color[2], color[3]), - base_color_texture: texture_handle, + base_color_texture, roughness: pbr.roughness_factor(), metallic: pbr.metallic_factor(), + metallic_roughness_texture, + normal_map, + double_sided: material.double_sided(), + occlusion_texture, + emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0), + emissive_texture, unlit: material.unlit(), ..Default::default() }), diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 7ccf32b8f6783..61e6718da7d3e 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -24,7 +24,21 @@ pub struct StandardMaterial { 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 + #[shader_def] + pub metallic_roughness_texture: Option>, pub reflectance: f32, + #[shader_def] + pub normal_map: Option>, + #[render_resources(ignore)] + #[shader_def] + pub double_sided: bool, + #[shader_def] + pub occlusion_texture: Option>, + // 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, + #[shader_def] + pub emissive_texture: Option>, #[render_resources(ignore)] #[shader_def] pub unlit: bool, @@ -45,7 +59,13 @@ impl Default for StandardMaterial { metallic: 0.01, // 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 + metallic_roughness_texture: None, reflectance: 0.5, + normal_map: None, + double_sided: false, + occlusion_texture: None, + emissive: Color::BLACK, + emissive_texture: None, unlit: false, } } diff --git a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag b/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag index f34b2b222f351..380332e79c71a 100644 --- a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag +++ b/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag @@ -48,6 +48,10 @@ layout(location = 0) in vec3 v_WorldPosition; layout(location = 1) in vec3 v_WorldNormal; layout(location = 2) in vec2 v_Uv; +#ifdef STANDARDMATERIAL_NORMAL_MAP +layout(location = 3) in vec4 v_WorldTangent; +#endif + layout(location = 0) out vec4 o_Target; layout(set = 0, binding = 0) uniform CameraViewProj { @@ -83,10 +87,38 @@ layout(set = 3, binding = 4) uniform StandardMaterial_metallic { float metallic; }; -layout(set = 3, binding = 5) uniform StandardMaterial_reflectance { +# ifdef STANDARDMATERIAL_METALLIC_ROUGHNESS_TEXTURE +layout(set = 3, binding = 5) uniform texture2D StandardMaterial_metallic_roughness_texture; +layout(set = 3, + binding = 6) uniform sampler StandardMaterial_metallic_roughness_texture_sampler; +# endif + +layout(set = 3, binding = 7) uniform StandardMaterial_reflectance { float reflectance; }; +# ifdef STANDARDMATERIAL_NORMAL_MAP +layout(set = 3, binding = 8) uniform texture2D StandardMaterial_normal_map; +layout(set = 3, + binding = 9) uniform sampler StandardMaterial_normal_map_sampler; +# endif + +# if defined(STANDARDMATERIAL_OCCLUSION_TEXTURE) +layout(set = 3, binding = 10) uniform texture2D StandardMaterial_occlusion_texture; +layout(set = 3, + binding = 11) uniform sampler StandardMaterial_occlusion_texture_sampler; +# endif + +layout(set = 3, binding = 12) uniform StandardMaterial_emissive { + vec4 emissive; +}; + +# if defined(STANDARDMATERIAL_EMISSIVE_TEXTURE) +layout(set = 3, binding = 13) uniform texture2D StandardMaterial_emissive_texture; +layout(set = 3, + binding = 14) uniform sampler StandardMaterial_emissive_texture_sampler; +# endif + # define saturate(x) clamp(x, 0.0, 1.0) const float PI = 3.141592653589793; @@ -258,10 +290,46 @@ void main() { #ifndef STANDARDMATERIAL_UNLIT // calculate non-linear roughness from linear perceptualRoughness +# ifdef STANDARDMATERIAL_METALLIC_ROUGHNESS_TEXTURE + vec4 metallic_roughness = texture(sampler2D(StandardMaterial_metallic_roughness_texture, StandardMaterial_metallic_roughness_texture_sampler), v_Uv); + // Sampling from GLTF standard channels for now + float metallic = metallic * metallic_roughness.b; + float perceptual_roughness = perceptual_roughness * metallic_roughness.g; +# endif + float roughness = perceptualRoughnessToRoughness(perceptual_roughness); vec3 N = normalize(v_WorldNormal); +# ifdef STANDARDMATERIAL_NORMAL_MAP + vec3 T = normalize(v_WorldTangent.xyz); + vec3 B = cross(N, T) * v_WorldTangent.w; +# endif + +# ifdef STANDARDMATERIAL_DOUBLE_SIDED + N = gl_FrontFacing ? N : -N; +# ifdef STANDARDMATERIAL_NORMAL_MAP + T = gl_FrontFacing ? T : -T; + B = gl_FrontFacing ? B : -B; +# endif +# endif + +# ifdef STANDARDMATERIAL_NORMAL_MAP + mat3 TBN = mat3(T, B, N); + N = TBN * normalize(texture(sampler2D(StandardMaterial_normal_map, StandardMaterial_normal_map_sampler), v_Uv).rgb * 2.0 - 1.0); +# endif + +# ifdef STANDARDMATERIAL_OCCLUSION_TEXTURE + float occlusion = texture(sampler2D(StandardMaterial_occlusion_texture, StandardMaterial_occlusion_texture_sampler), v_Uv).r; +# else + float occlusion = 1.0; +# endif + +# ifdef STANDARDMATERIAL_EMISSIVE_TEXTURE + // TODO use .a for exposure compensation in HDR + emissive.rgb *= texture(sampler2D(StandardMaterial_emissive_texture, StandardMaterial_emissive_texture_sampler), v_Uv).rgb; +# endif + vec3 V = normalize(CameraPos.xyz - v_WorldPosition.xyz); // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" float NdotV = max(dot(N, V), 1e-4); @@ -310,7 +378,9 @@ void main() { vec3 diffuse_ambient = EnvBRDFApprox(diffuseColor, 1.0, NdotV); vec3 specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV); - output_color.rgb = light_accum + (diffuse_ambient + specular_ambient) * AmbientColor; + output_color.rgb = light_accum; + output_color.rgb += (diffuse_ambient + specular_ambient) * AmbientColor * occlusion; + output_color.rgb += emissive.rgb * output_color.a; // tone_mapping output_color.rgb = reinhard_luminance(output_color.rgb); diff --git a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.vert b/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.vert index 78c1312e1ea76..533a163a1c903 100644 --- a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.vert +++ b/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.vert @@ -4,6 +4,10 @@ layout(location = 0) in vec3 Vertex_Position; layout(location = 1) in vec3 Vertex_Normal; layout(location = 2) in vec2 Vertex_Uv; +#ifdef STANDARDMATERIAL_NORMAL_MAP +layout(location = 3) in vec4 Vertex_Tangent; +#endif + layout(location = 0) out vec3 v_WorldPosition; layout(location = 1) out vec3 v_WorldNormal; layout(location = 2) out vec2 v_Uv; @@ -12,6 +16,10 @@ layout(set = 0, binding = 0) uniform CameraViewProj { mat4 ViewProj; }; +#ifdef STANDARDMATERIAL_NORMAL_MAP +layout(location = 3) out vec4 v_WorldTangent; +#endif + layout(set = 2, binding = 0) uniform Transform { mat4 Model; }; @@ -21,5 +29,8 @@ void main() { v_WorldPosition = world_position.xyz; v_WorldNormal = mat3(Model) * Vertex_Normal; v_Uv = Vertex_Uv; +#ifdef STANDARDMATERIAL_NORMAL_MAP + v_WorldTangent = vec4(mat3(Model) * Vertex_Tangent.xyz, Vertex_Tangent.w); +#endif gl_Position = ViewProj * world_position; } diff --git a/crates/bevy_render/src/mesh/mesh.rs b/crates/bevy_render/src/mesh/mesh.rs index f977f25647954..aece08891876f 100644 --- a/crates/bevy_render/src/mesh/mesh.rs +++ b/crates/bevy_render/src/mesh/mesh.rs @@ -237,6 +237,9 @@ impl Mesh { /// The direction the vertex normal is facing in. /// Use in conjunction with [`Mesh::set_attribute`] pub const ATTRIBUTE_NORMAL: &'static str = "Vertex_Normal"; + /// The direction of the vertex tangent. Used for normal mapping + pub const ATTRIBUTE_TANGENT: &'static str = "Vertex_Tangent"; + /// Where the vertex is located in space. Use in conjunction with [`Mesh::set_attribute`] pub const ATTRIBUTE_POSITION: &'static str = "Vertex_Position"; /// Texture coordinates for the vertex. Use in conjunction with [`Mesh::set_attribute`] diff --git a/crates/bevy_render/src/shader/mod.rs b/crates/bevy_render/src/shader/mod.rs index dee5179b79342..f9d727e147bab 100644 --- a/crates/bevy_render/src/shader/mod.rs +++ b/crates/bevy_render/src/shader/mod.rs @@ -23,3 +23,4 @@ pub struct ShaderLayout { pub const GL_VERTEX_INDEX: &str = "gl_VertexIndex"; pub const GL_INSTANCE_INDEX: &str = "gl_InstanceIndex"; +pub const GL_FRONT_FACING: &str = "gl_FrontFacing"; diff --git a/crates/bevy_render/src/shader/shader_reflect.rs b/crates/bevy_render/src/shader/shader_reflect.rs index 78a5f306f18fd..a300041db66e3 100644 --- a/crates/bevy_render/src/shader/shader_reflect.rs +++ b/crates/bevy_render/src/shader/shader_reflect.rs @@ -3,7 +3,7 @@ use crate::{ BindGroupDescriptor, BindType, BindingDescriptor, BindingShaderStage, InputStepMode, UniformProperty, VertexAttribute, VertexBufferLayout, VertexFormat, }, - shader::{ShaderLayout, GL_INSTANCE_INDEX, GL_VERTEX_INDEX}, + shader::{ShaderLayout, GL_FRONT_FACING, GL_INSTANCE_INDEX, GL_VERTEX_INDEX}, texture::{TextureSampleType, TextureViewDimension}, }; use bevy_core::AsBytes; @@ -33,6 +33,7 @@ impl ShaderLayout { for input_variable in module.enumerate_input_variables(None).unwrap() { if input_variable.name == GL_VERTEX_INDEX || input_variable.name == GL_INSTANCE_INDEX + || input_variable.name == GL_FRONT_FACING { continue; } diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs index a57d1ed47044e..22409d7ffa9a4 100644 --- a/examples/3d/load_gltf.rs +++ b/examples/3d/load_gltf.rs @@ -1,21 +1,39 @@ -use bevy::prelude::*; +use bevy::{pbr::AmbientLight, prelude::*}; fn main() { App::build() + .insert_resource(AmbientLight { + color: Color::WHITE, + brightness: 1.0 / 5.0f32, + }) .insert_resource(Msaa { samples: 4 }) .add_plugins(DefaultPlugins) .add_startup_system(setup.system()) + .add_system(rotator_system.system()) .run(); } fn setup(mut commands: Commands, asset_server: Res) { commands.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")); - commands.spawn_bundle(LightBundle { - transform: Transform::from_xyz(4.0, 5.0, 4.0), - ..Default::default() - }); commands.spawn_bundle(PerspectiveCameraBundle { transform: Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), ..Default::default() }); + commands + .spawn_bundle(LightBundle { + transform: Transform::from_xyz(3.0, 5.0, 3.0), + ..Default::default() + }) + .insert(Rotates); +} + +/// this component indicates what entities should rotate +struct Rotates; + +fn rotator_system(time: Res