Skip to content

Commit 9a78add

Browse files
mtsrcart
andcommitted
Add PBR textures (#1632)
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 <mcanders1@gmail.com>
1 parent 0c374df commit 9a78add

File tree

10 files changed

+264
-12
lines changed

10 files changed

+264
-12
lines changed

crates/bevy_gltf/src/loader.rs

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ async fn load_gltf<'a, 'b>(
9595
if let Some(texture) = material.occlusion_texture() {
9696
linear_textures.insert(texture.texture().index());
9797
}
98+
if let Some(texture) = material
99+
.pbr_metallic_roughness()
100+
.metallic_roughness_texture()
101+
{
102+
linear_textures.insert(texture.texture().index());
103+
}
98104
}
99105

100106
let mut meshes = vec![];
@@ -122,6 +128,13 @@ async fn load_gltf<'a, 'b>(
122128
mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute);
123129
}
124130

131+
if let Some(vertex_attribute) = reader
132+
.read_tangents()
133+
.map(|v| VertexAttributeValues::Float4(v.collect()))
134+
{
135+
mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute);
136+
}
137+
125138
if let Some(vertex_attribute) = reader
126139
.read_tex_coords(0)
127140
.map(|v| VertexAttributeValues::Float2(v.into_f32().collect()))
@@ -279,23 +292,72 @@ async fn load_gltf<'a, 'b>(
279292

280293
fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<StandardMaterial> {
281294
let material_label = material_label(&material);
295+
282296
let pbr = material.pbr_metallic_roughness();
283-
let texture_handle = if let Some(info) = pbr.base_color_texture() {
297+
298+
let color = pbr.base_color_factor();
299+
let base_color_texture = if let Some(info) = pbr.base_color_texture() {
300+
// TODO: handle info.tex_coord() (the *set* index for the right texcoords)
301+
let label = texture_label(&info.texture());
302+
let path = AssetPath::new_ref(load_context.path(), Some(&label));
303+
Some(load_context.get_handle(path))
304+
} else {
305+
None
306+
};
307+
308+
let normal_map = if let Some(normal_texture) = material.normal_texture() {
309+
// TODO: handle normal_texture.scale
310+
// TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords)
311+
let label = texture_label(&normal_texture.texture());
312+
let path = AssetPath::new_ref(load_context.path(), Some(&label));
313+
Some(load_context.get_handle(path))
314+
} else {
315+
None
316+
};
317+
318+
let metallic_roughness_texture = if let Some(info) = pbr.metallic_roughness_texture() {
319+
// TODO: handle info.tex_coord() (the *set* index for the right texcoords)
320+
let label = texture_label(&info.texture());
321+
let path = AssetPath::new_ref(load_context.path(), Some(&label));
322+
Some(load_context.get_handle(path))
323+
} else {
324+
None
325+
};
326+
327+
let occlusion_texture = if let Some(occlusion_texture) = material.occlusion_texture() {
328+
// TODO: handle occlusion_texture.tex_coord() (the *set* index for the right texcoords)
329+
// TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength)
330+
let label = texture_label(&occlusion_texture.texture());
331+
let path = AssetPath::new_ref(load_context.path(), Some(&label));
332+
Some(load_context.get_handle(path))
333+
} else {
334+
None
335+
};
336+
337+
let emissive = material.emissive_factor();
338+
let emissive_texture = if let Some(info) = material.emissive_texture() {
339+
// TODO: handle occlusion_texture.tex_coord() (the *set* index for the right texcoords)
340+
// TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength)
284341
let label = texture_label(&info.texture());
285342
let path = AssetPath::new_ref(load_context.path(), Some(&label));
286343
Some(load_context.get_handle(path))
287344
} else {
288345
None
289346
};
290347

291-
let color = pbr.base_color_factor();
292348
load_context.set_labeled_asset(
293349
&material_label,
294350
LoadedAsset::new(StandardMaterial {
295351
base_color: Color::rgba(color[0], color[1], color[2], color[3]),
296-
base_color_texture: texture_handle,
352+
base_color_texture,
297353
roughness: pbr.roughness_factor(),
298354
metallic: pbr.metallic_factor(),
355+
metallic_roughness_texture,
356+
normal_map,
357+
double_sided: material.double_sided(),
358+
occlusion_texture,
359+
emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0),
360+
emissive_texture,
299361
unlit: material.unlit(),
300362
..Default::default()
301363
}),

crates/bevy_pbr/src/material.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,21 @@ pub struct StandardMaterial {
2424
pub metallic: f32,
2525
/// Specular intensity for non-metals on a linear scale of [0.0, 1.0]
2626
/// defaults to 0.5 which is mapped to 4% reflectance in the shader
27+
#[shader_def]
28+
pub metallic_roughness_texture: Option<Handle<Texture>>,
2729
pub reflectance: f32,
30+
#[shader_def]
31+
pub normal_map: Option<Handle<Texture>>,
32+
#[render_resources(ignore)]
33+
#[shader_def]
34+
pub double_sided: bool,
35+
#[shader_def]
36+
pub occlusion_texture: Option<Handle<Texture>>,
37+
// Use a color for user friendliness even though we technically don't use the alpha channel
38+
// Might be used in the future for exposure correction in HDR
39+
pub emissive: Color,
40+
#[shader_def]
41+
pub emissive_texture: Option<Handle<Texture>>,
2842
#[render_resources(ignore)]
2943
#[shader_def]
3044
pub unlit: bool,
@@ -45,7 +59,13 @@ impl Default for StandardMaterial {
4559
metallic: 0.01,
4660
// Minimum real-world reflectance is 2%, most materials between 2-5%
4761
// Expressed in a linear scale and equivalent to 4% reflectance see https://google.github.io/filament/Material%20Properties.pdf
62+
metallic_roughness_texture: None,
4863
reflectance: 0.5,
64+
normal_map: None,
65+
double_sided: false,
66+
occlusion_texture: None,
67+
emissive: Color::BLACK,
68+
emissive_texture: None,
4969
unlit: false,
5070
}
5171
}

crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ layout(location = 0) in vec3 v_WorldPosition;
4848
layout(location = 1) in vec3 v_WorldNormal;
4949
layout(location = 2) in vec2 v_Uv;
5050

51+
#ifdef STANDARDMATERIAL_NORMAL_MAP
52+
layout(location = 3) in vec4 v_WorldTangent;
53+
#endif
54+
5155
layout(location = 0) out vec4 o_Target;
5256

5357
layout(set = 0, binding = 0) uniform CameraViewProj {
@@ -83,10 +87,38 @@ layout(set = 3, binding = 4) uniform StandardMaterial_metallic {
8387
float metallic;
8488
};
8589

86-
layout(set = 3, binding = 5) uniform StandardMaterial_reflectance {
90+
# ifdef STANDARDMATERIAL_METALLIC_ROUGHNESS_TEXTURE
91+
layout(set = 3, binding = 5) uniform texture2D StandardMaterial_metallic_roughness_texture;
92+
layout(set = 3,
93+
binding = 6) uniform sampler StandardMaterial_metallic_roughness_texture_sampler;
94+
# endif
95+
96+
layout(set = 3, binding = 7) uniform StandardMaterial_reflectance {
8797
float reflectance;
8898
};
8999

100+
# ifdef STANDARDMATERIAL_NORMAL_MAP
101+
layout(set = 3, binding = 8) uniform texture2D StandardMaterial_normal_map;
102+
layout(set = 3,
103+
binding = 9) uniform sampler StandardMaterial_normal_map_sampler;
104+
# endif
105+
106+
# if defined(STANDARDMATERIAL_OCCLUSION_TEXTURE)
107+
layout(set = 3, binding = 10) uniform texture2D StandardMaterial_occlusion_texture;
108+
layout(set = 3,
109+
binding = 11) uniform sampler StandardMaterial_occlusion_texture_sampler;
110+
# endif
111+
112+
layout(set = 3, binding = 12) uniform StandardMaterial_emissive {
113+
vec4 emissive;
114+
};
115+
116+
# if defined(STANDARDMATERIAL_EMISSIVE_TEXTURE)
117+
layout(set = 3, binding = 13) uniform texture2D StandardMaterial_emissive_texture;
118+
layout(set = 3,
119+
binding = 14) uniform sampler StandardMaterial_emissive_texture_sampler;
120+
# endif
121+
90122
# define saturate(x) clamp(x, 0.0, 1.0)
91123
const float PI = 3.141592653589793;
92124

@@ -258,10 +290,46 @@ void main() {
258290

259291
#ifndef STANDARDMATERIAL_UNLIT
260292
// calculate non-linear roughness from linear perceptualRoughness
293+
# ifdef STANDARDMATERIAL_METALLIC_ROUGHNESS_TEXTURE
294+
vec4 metallic_roughness = texture(sampler2D(StandardMaterial_metallic_roughness_texture, StandardMaterial_metallic_roughness_texture_sampler), v_Uv);
295+
// Sampling from GLTF standard channels for now
296+
float metallic = metallic * metallic_roughness.b;
297+
float perceptual_roughness = perceptual_roughness * metallic_roughness.g;
298+
# endif
299+
261300
float roughness = perceptualRoughnessToRoughness(perceptual_roughness);
262301

263302
vec3 N = normalize(v_WorldNormal);
264303

304+
# ifdef STANDARDMATERIAL_NORMAL_MAP
305+
vec3 T = normalize(v_WorldTangent.xyz);
306+
vec3 B = cross(N, T) * v_WorldTangent.w;
307+
# endif
308+
309+
# ifdef STANDARDMATERIAL_DOUBLE_SIDED
310+
N = gl_FrontFacing ? N : -N;
311+
# ifdef STANDARDMATERIAL_NORMAL_MAP
312+
T = gl_FrontFacing ? T : -T;
313+
B = gl_FrontFacing ? B : -B;
314+
# endif
315+
# endif
316+
317+
# ifdef STANDARDMATERIAL_NORMAL_MAP
318+
mat3 TBN = mat3(T, B, N);
319+
N = TBN * normalize(texture(sampler2D(StandardMaterial_normal_map, StandardMaterial_normal_map_sampler), v_Uv).rgb * 2.0 - 1.0);
320+
# endif
321+
322+
# ifdef STANDARDMATERIAL_OCCLUSION_TEXTURE
323+
float occlusion = texture(sampler2D(StandardMaterial_occlusion_texture, StandardMaterial_occlusion_texture_sampler), v_Uv).r;
324+
# else
325+
float occlusion = 1.0;
326+
# endif
327+
328+
# ifdef STANDARDMATERIAL_EMISSIVE_TEXTURE
329+
// TODO use .a for exposure compensation in HDR
330+
emissive.rgb *= texture(sampler2D(StandardMaterial_emissive_texture, StandardMaterial_emissive_texture_sampler), v_Uv).rgb;
331+
# endif
332+
265333
vec3 V = normalize(CameraPos.xyz - v_WorldPosition.xyz);
266334
// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
267335
float NdotV = max(dot(N, V), 1e-4);
@@ -310,7 +378,9 @@ void main() {
310378
vec3 diffuse_ambient = EnvBRDFApprox(diffuseColor, 1.0, NdotV);
311379
vec3 specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV);
312380

313-
output_color.rgb = light_accum + (diffuse_ambient + specular_ambient) * AmbientColor;
381+
output_color.rgb = light_accum;
382+
output_color.rgb += (diffuse_ambient + specular_ambient) * AmbientColor * occlusion;
383+
output_color.rgb += emissive.rgb * output_color.a;
314384

315385
// tone_mapping
316386
output_color.rgb = reinhard_luminance(output_color.rgb);

crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.vert

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ layout(location = 0) in vec3 Vertex_Position;
44
layout(location = 1) in vec3 Vertex_Normal;
55
layout(location = 2) in vec2 Vertex_Uv;
66

7+
#ifdef STANDARDMATERIAL_NORMAL_MAP
8+
layout(location = 3) in vec4 Vertex_Tangent;
9+
#endif
10+
711
layout(location = 0) out vec3 v_WorldPosition;
812
layout(location = 1) out vec3 v_WorldNormal;
913
layout(location = 2) out vec2 v_Uv;
@@ -12,6 +16,10 @@ layout(set = 0, binding = 0) uniform CameraViewProj {
1216
mat4 ViewProj;
1317
};
1418

19+
#ifdef STANDARDMATERIAL_NORMAL_MAP
20+
layout(location = 3) out vec4 v_WorldTangent;
21+
#endif
22+
1523
layout(set = 2, binding = 0) uniform Transform {
1624
mat4 Model;
1725
};
@@ -21,5 +29,8 @@ void main() {
2129
v_WorldPosition = world_position.xyz;
2230
v_WorldNormal = mat3(Model) * Vertex_Normal;
2331
v_Uv = Vertex_Uv;
32+
#ifdef STANDARDMATERIAL_NORMAL_MAP
33+
v_WorldTangent = vec4(mat3(Model) * Vertex_Tangent.xyz, Vertex_Tangent.w);
34+
#endif
2435
gl_Position = ViewProj * world_position;
2536
}

crates/bevy_render/src/mesh/mesh.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ impl Mesh {
237237
/// The direction the vertex normal is facing in.
238238
/// Use in conjunction with [`Mesh::set_attribute`]
239239
pub const ATTRIBUTE_NORMAL: &'static str = "Vertex_Normal";
240+
/// The direction of the vertex tangent. Used for normal mapping
241+
pub const ATTRIBUTE_TANGENT: &'static str = "Vertex_Tangent";
242+
240243
/// Where the vertex is located in space. Use in conjunction with [`Mesh::set_attribute`]
241244
pub const ATTRIBUTE_POSITION: &'static str = "Vertex_Position";
242245
/// Texture coordinates for the vertex. Use in conjunction with [`Mesh::set_attribute`]

crates/bevy_render/src/shader/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ pub struct ShaderLayout {
2323

2424
pub const GL_VERTEX_INDEX: &str = "gl_VertexIndex";
2525
pub const GL_INSTANCE_INDEX: &str = "gl_InstanceIndex";
26+
pub const GL_FRONT_FACING: &str = "gl_FrontFacing";

crates/bevy_render/src/shader/shader_reflect.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
BindGroupDescriptor, BindType, BindingDescriptor, BindingShaderStage, InputStepMode,
44
UniformProperty, VertexAttribute, VertexBufferLayout, VertexFormat,
55
},
6-
shader::{ShaderLayout, GL_INSTANCE_INDEX, GL_VERTEX_INDEX},
6+
shader::{ShaderLayout, GL_FRONT_FACING, GL_INSTANCE_INDEX, GL_VERTEX_INDEX},
77
texture::{TextureSampleType, TextureViewDimension},
88
};
99
use bevy_core::AsBytes;
@@ -33,6 +33,7 @@ impl ShaderLayout {
3333
for input_variable in module.enumerate_input_variables(None).unwrap() {
3434
if input_variable.name == GL_VERTEX_INDEX
3535
|| input_variable.name == GL_INSTANCE_INDEX
36+
|| input_variable.name == GL_FRONT_FACING
3637
{
3738
continue;
3839
}

examples/3d/load_gltf.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,39 @@
1-
use bevy::prelude::*;
1+
use bevy::{pbr::AmbientLight, prelude::*};
22

33
fn main() {
44
App::build()
5+
.insert_resource(AmbientLight {
6+
color: Color::WHITE,
7+
brightness: 1.0 / 5.0f32,
8+
})
59
.insert_resource(Msaa { samples: 4 })
610
.add_plugins(DefaultPlugins)
711
.add_startup_system(setup.system())
12+
.add_system(rotator_system.system())
813
.run();
914
}
1015

1116
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
1217
commands.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"));
13-
commands.spawn_bundle(LightBundle {
14-
transform: Transform::from_xyz(4.0, 5.0, 4.0),
15-
..Default::default()
16-
});
1718
commands.spawn_bundle(PerspectiveCameraBundle {
1819
transform: Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
1920
..Default::default()
2021
});
22+
commands
23+
.spawn_bundle(LightBundle {
24+
transform: Transform::from_xyz(3.0, 5.0, 3.0),
25+
..Default::default()
26+
})
27+
.insert(Rotates);
28+
}
29+
30+
/// this component indicates what entities should rotate
31+
struct Rotates;
32+
33+
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Rotates>>) {
34+
for mut transform in query.iter_mut() {
35+
*transform = Transform::from_rotation(Quat::from_rotation_y(
36+
(4.0 * std::f32::consts::PI / 20.0) * time.delta_seconds(),
37+
)) * *transform;
38+
}
2139
}

0 commit comments

Comments
 (0)