mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
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>
This commit is contained in:
parent
0c374df712
commit
9a78addff0
10 changed files with 264 additions and 12 deletions
|
@ -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<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))
|
||||
|
@ -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()
|
||||
}),
|
||||
|
|
|
@ -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,
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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`]
|
||||
|
|
|
@ -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,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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
66
examples/3d/pbr_textures.rs
Normal file
66
examples/3d/pbr_textures.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use std::f32::consts::PI;
|
||||
|
||||
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>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
commands.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"));
|
||||
|
||||
// Add a rotating light with a sphere to show it's position
|
||||
commands
|
||||
.spawn_bundle((Transform::default(), GlobalTransform::default(), Rotates))
|
||||
.with_children(|parent| {
|
||||
parent
|
||||
.spawn_bundle(LightBundle {
|
||||
transform: Transform::from_translation(Vec3::new(0.0, 0.7, 2.0)),
|
||||
..Default::default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
parent.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Icosphere {
|
||||
radius: 0.05,
|
||||
subdivisions: 32,
|
||||
})),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::YELLOW,
|
||||
emissive: Color::WHITE * 10.0f32,
|
||||
..Default::default()
|
||||
}),
|
||||
transform: Transform::default(),
|
||||
..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()
|
||||
});
|
||||
}
|
||||
|
||||
/// this component indicates what entities should rotate
|
||||
struct Rotates;
|
||||
|
||||
/// rotates the parent, which will result in the child also rotating
|
||||
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Rotates>>) {
|
||||
for mut transform in query.iter_mut() {
|
||||
transform.rotation *= Quat::from_rotation_y((2.0 * PI / 20.0) * time.delta_seconds());
|
||||
}
|
||||
}
|
|
@ -88,7 +88,7 @@ Example | File | Description
|
|||
`msaa` | [`3d/msaa.rs`](./3d/msaa.rs) | Configures MSAA (Multi-Sample Anti-Aliasing) for smoother edges
|
||||
`orthographic` | [`3d/orthographic.rs`](./3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look games or CAD applications)
|
||||
`parenting` | [`3d/parenting.rs`](./3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations
|
||||
`pbr` | [`3d/pbr.rs`](./3d/[pbr].rs) | Demonstrates PBR properties Roughness/Metallic
|
||||
`pbr` | [`3d/pbr.rs`](./3d/[pbr].rs) | Demonstrates use of Physically Based Rendering (PBR) properties
|
||||
`spawner` | [`3d/spawner.rs`](./3d/spawner.rs) | Renders a large number of cubes with changing position and material
|
||||
`texture` | [`3d/texture.rs`](./3d/texture.rs) | Shows configuration of texture materials
|
||||
`update_gltf_scene` | [`3d/update_gltf_scene.rs`](./3d/update_gltf_scene.rs) | Update a scene from a gltf file, either by spawning the scene as a child of another entity, or by accessing the entities of the scene
|
||||
|
|
Loading…
Reference in a new issue