Skip to content

Commit

Permalink
Add PBR textures (#1632)
Browse files Browse the repository at this point in the history
This PR adds normal maps on top of PBR #1554. Once that PR lands, the changes should look simpler.

Edit: Turned out to be so little extra work, I added metallic/roughness texture too. And occlusion and emissive.

Co-authored-by: Carter Anderson <[email protected]>
  • Loading branch information
mtsr and cart committed Mar 26, 2021
1 parent 0c374df commit 9a78add
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 12 deletions.
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;
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;
}
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

0 comments on commit 9a78add

Please sign in to comment.