Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Add PBR textures #1632

Closed
wants to merge 14 commits into from
68 changes: 65 additions & 3 deletions crates/bevy_gltf/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![];
Expand Down Expand Up @@ -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()))
Expand Down Expand Up @@ -279,23 +292,72 @@ async fn load_gltf<'a, 'b>(

fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<StandardMaterial> {
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))
} else {
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()
}),
Expand Down
20 changes: 20 additions & 0 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Handle<Texture>>,
pub reflectance: f32,
#[shader_def]
pub normal_map: Option<Handle<Texture>>,
#[render_resources(ignore)]
#[shader_def]
pub double_sided: bool,
#[shader_def]
pub occlusion_texture: Option<Handle<Texture>>,
// 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<Handle<Texture>>,
#[render_resources(ignore)]
#[shader_def]
pub unlit: bool,
Expand All @@ -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,
}
}
Expand Down
74 changes: 72 additions & 2 deletions crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny comments here would make it easier for folks new to rendering to figure out exactly what's happening here.

See the // tone_mapping comment just below.

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);
Expand Down
11 changes: 11 additions & 0 deletions crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.vert
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
};
Expand All @@ -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;
}
3 changes: 3 additions & 0 deletions crates/bevy_render/src/mesh/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`]
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_render/src/shader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
3 changes: 2 additions & 1 deletion crates/bevy_render/src/shader/shader_reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Comment on lines 33 to 39
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might misunderstand the point of this condition, if so I'd love to learn. But wouldn't a more long-term solution be (snipped some use changes for clarity):

diff --git a/crates/bevy_render/src/shader/shader_reflect.rs b/crates/bevy_render/src/shader/shader_reflect.rs
index 238da1ef..95761fed 100644
--- a/crates/bevy_render/src/shader/shader_reflect.rs
+++ b/crates/bevy_render/src/shader/shader_reflect.rs
@@ -31,8 +32,9 @@ impl ShaderLayout {
                 // obtain attribute descriptors from reflection
                 let mut vertex_attributes = Vec::new();
                 for input_variable in module.enumerate_input_variables(None).unwrap() {
-                    if input_variable.name == GL_VERTEX_INDEX
-                        || input_variable.name == GL_INSTANCE_INDEX
+                    if input_variable
+                        .decoration_flags
+                        .contains(ReflectDecorationFlags::BUILT_IN)
                     {
                         continue;
                     }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooh that does seem like a win / appears to do what we want it to.

Expand Down
28 changes: 23 additions & 5 deletions examples/3d/load_gltf.rs
Original file line number Diff line number Diff line change
@@ -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<AssetServer>) {
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<Time>, mut query: Query<&mut Transform, With<Rotates>>) {
for mut transform in query.iter_mut() {
*transform = Transform::from_rotation(Quat::from_rotation_y(
(4.0 * std::f32::consts::PI / 20.0) * time.delta_seconds(),
)) * *transform;
}
}
Loading