Add UV channel selection to StandardMaterial (#13200)

# Objective

- The StandardMaterial always uses ATTRIBUTE_UV_0 for each texture
except lightmap. This is not flexible enough for a lot of gltf Files.
- Fixes #12496
- Fixes #13086
- Fixes #13122
- Closes #13153

## Solution

- The StandardMaterial gets extended for each texture by an UvChannel
enum. It defaults to Uv0 but can also be set to Uv1.
- The gltf loader now handles the texcoord information. If the texcoord
is not supported it creates a warning.
- It uses StandardMaterial shader defs to define which attribute to use.

## Testing

This fixes #12496 for example:

![wall_fixed](https://github.com/bevyengine/bevy/assets/688816/bc37c9e1-72ba-4e59-b092-5ee10dade603)

For testing of all kind of textures I used the TextureTransformMultiTest
from
https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models/TextureTransformMultiTest
Its purpose is to test multiple texture transfroms but it is also a good
test for different texcoords.
It also shows the issue with emission #13133.

Before:

![TextureTransformMultiTest_main](https://github.com/bevyengine/bevy/assets/688816/aa701d04-5a3f-4df1-a65f-fc770ab6f4ab)

After:

![TextureTransformMultiTest_texcoord](https://github.com/bevyengine/bevy/assets/688816/c3f91943-b830-4068-990f-e4f2c97771ee)
This commit is contained in:
Johannes Hackel 2024-05-13 20:23:09 +02:00 committed by GitHub
parent ac1f135e20
commit 3f5a090b1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 459 additions and 53 deletions

View file

@ -13,7 +13,7 @@ use bevy_hierarchy::{BuildWorldChildren, WorldChildBuilder};
use bevy_math::{Affine2, Mat4, Vec3};
use bevy_pbr::{
DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight, PointLightBundle, SpotLight,
SpotLightBundle, StandardMaterial, MAX_JOINTS,
SpotLightBundle, StandardMaterial, UvChannel, MAX_JOINTS,
};
use bevy_render::{
alpha::AlphaMode,
@ -49,7 +49,7 @@ use gltf::{json, Document};
use serde::{Deserialize, Serialize};
#[cfg(feature = "pbr_multi_layer_material_textures")]
use serde_json::value;
use serde_json::{Map, Value};
use serde_json::Value;
#[cfg(feature = "bevy_animation")]
use smallvec::SmallVec;
use std::io::Error;
@ -846,10 +846,13 @@ fn load_material(
// TODO: handle missing label handle errors here?
let color = pbr.base_color_factor();
let base_color_texture = pbr.base_color_texture().map(|info| {
// TODO: handle info.tex_coord() (the *set* index for the right texcoords)
texture_handle(load_context, &info.texture())
});
let base_color_channel = pbr
.base_color_texture()
.map(|info| get_uv_channel(material, "base color", info.tex_coord()))
.unwrap_or_default();
let base_color_texture = pbr
.base_color_texture()
.map(|info| texture_handle(load_context, &info.texture()));
let uv_transform = pbr
.base_color_texture()
@ -859,15 +862,21 @@ fn load_material(
})
.unwrap_or_default();
let normal_map_channel = material
.normal_texture()
.map(|info| get_uv_channel(material, "normal map", info.tex_coord()))
.unwrap_or_default();
let normal_map_texture: Option<Handle<Image>> =
material.normal_texture().map(|normal_texture| {
// TODO: handle normal_texture.scale
// TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords)
texture_handle(load_context, &normal_texture.texture())
});
let metallic_roughness_channel = pbr
.metallic_roughness_texture()
.map(|info| get_uv_channel(material, "metallic/roughness", info.tex_coord()))
.unwrap_or_default();
let metallic_roughness_texture = pbr.metallic_roughness_texture().map(|info| {
// TODO: handle info.tex_coord() (the *set* index for the right texcoords)
warn_on_differing_texture_transforms(
material,
&info,
@ -877,32 +886,49 @@ fn load_material(
texture_handle(load_context, &info.texture())
});
let occlusion_channel = material
.occlusion_texture()
.map(|info| get_uv_channel(material, "occlusion", info.tex_coord()))
.unwrap_or_default();
let occlusion_texture = material.occlusion_texture().map(|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)
texture_handle(load_context, &occlusion_texture.texture())
});
let emissive = material.emissive_factor();
let emissive_channel = material
.emissive_texture()
.map(|info| get_uv_channel(material, "emissive", info.tex_coord()))
.unwrap_or_default();
let emissive_texture = material.emissive_texture().map(|info| {
// TODO: handle occlusion_texture.tex_coord() (the *set* index for the right texcoords)
// TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength)
warn_on_differing_texture_transforms(material, &info, uv_transform, "emissive");
texture_handle(load_context, &info.texture())
});
#[cfg(feature = "pbr_transmission_textures")]
let (specular_transmission, specular_transmission_texture) =
material.transmission().map_or((0.0, None), |transmission| {
let transmission_texture: Option<Handle<Image>> = transmission
.transmission_texture()
.map(|transmission_texture| {
// TODO: handle transmission_texture.tex_coord() (the *set* index for the right texcoords)
texture_handle(load_context, &transmission_texture.texture())
});
let (specular_transmission, specular_transmission_channel, specular_transmission_texture) =
material
.transmission()
.map_or((0.0, UvChannel::Uv0, None), |transmission| {
let specular_transmission_channel = transmission
.transmission_texture()
.map(|info| {
get_uv_channel(material, "specular/transmission", info.tex_coord())
})
.unwrap_or_default();
let transmission_texture: Option<Handle<Image>> = transmission
.transmission_texture()
.map(|transmission_texture| {
texture_handle(load_context, &transmission_texture.texture())
});
(transmission.transmission_factor(), transmission_texture)
});
(
transmission.transmission_factor(),
specular_transmission_channel,
transmission_texture,
)
});
#[cfg(not(feature = "pbr_transmission_textures"))]
let specular_transmission = material
@ -910,22 +936,33 @@ fn load_material(
.map_or(0.0, |transmission| transmission.transmission_factor());
#[cfg(feature = "pbr_transmission_textures")]
let (thickness, thickness_texture, attenuation_distance, attenuation_color) = material
.volume()
.map_or((0.0, None, f32::INFINITY, [1.0, 1.0, 1.0]), |volume| {
let (
thickness,
thickness_channel,
thickness_texture,
attenuation_distance,
attenuation_color,
) = material.volume().map_or(
(0.0, UvChannel::Uv0, None, f32::INFINITY, [1.0, 1.0, 1.0]),
|volume| {
let thickness_channel = volume
.thickness_texture()
.map(|info| get_uv_channel(material, "thickness", info.tex_coord()))
.unwrap_or_default();
let thickness_texture: Option<Handle<Image>> =
volume.thickness_texture().map(|thickness_texture| {
// TODO: handle thickness_texture.tex_coord() (the *set* index for the right texcoords)
texture_handle(load_context, &thickness_texture.texture())
});
(
volume.thickness_factor(),
thickness_channel,
thickness_texture,
volume.attenuation_distance(),
volume.attenuation_color(),
)
});
},
);
#[cfg(not(feature = "pbr_transmission_textures"))]
let (thickness, attenuation_distance, attenuation_color) =
@ -942,8 +979,8 @@ fn load_material(
let ior = material.ior().unwrap_or(1.5);
// Parse the `KHR_materials_clearcoat` extension data if necessary.
let clearcoat = ClearcoatExtension::parse(load_context, document, material.extensions())
.unwrap_or_default();
let clearcoat =
ClearcoatExtension::parse(load_context, document, material).unwrap_or_default();
// We need to operate in the Linear color space and be willing to exceed 1.0 in our channels
let base_emissive = LinearRgba::rgb(emissive[0], emissive[1], emissive[2]);
@ -952,10 +989,13 @@ fn load_material(
StandardMaterial {
base_color: Color::linear_rgba(color[0], color[1], color[2], color[3]),
base_color_channel,
base_color_texture,
perceptual_roughness: pbr.roughness_factor(),
metallic: pbr.metallic_factor(),
metallic_roughness_channel,
metallic_roughness_texture,
normal_map_channel,
normal_map_texture,
double_sided: material.double_sided(),
cull_mode: if material.double_sided() {
@ -965,14 +1005,20 @@ fn load_material(
} else {
Some(Face::Back)
},
occlusion_channel,
occlusion_texture,
emissive,
emissive_channel,
emissive_texture,
specular_transmission,
#[cfg(feature = "pbr_transmission_textures")]
specular_transmission_channel,
#[cfg(feature = "pbr_transmission_textures")]
specular_transmission_texture,
thickness,
#[cfg(feature = "pbr_transmission_textures")]
thickness_channel,
#[cfg(feature = "pbr_transmission_textures")]
thickness_texture,
ior,
attenuation_distance,
@ -988,16 +1034,45 @@ fn load_material(
clearcoat_perceptual_roughness: clearcoat.clearcoat_roughness_factor.unwrap_or_default()
as f32,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_channel: clearcoat.clearcoat_channel,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_texture: clearcoat.clearcoat_texture,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_roughness_channel: clearcoat.clearcoat_roughness_channel,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_roughness_texture: clearcoat.clearcoat_roughness_texture,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_normal_channel: clearcoat.clearcoat_normal_channel,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_normal_texture: clearcoat.clearcoat_normal_texture,
..Default::default()
}
})
}
fn get_uv_channel(material: &Material, texture_kind: &str, tex_coord: u32) -> UvChannel {
match tex_coord {
0 => UvChannel::Uv0,
1 => UvChannel::Uv1,
_ => {
let material_name = material
.name()
.map(|n| format!("the material \"{n}\""))
.unwrap_or_else(|| "an unnamed material".to_string());
let material_index = material
.index()
.map(|i| format!("index {i}"))
.unwrap_or_else(|| "default".to_string());
warn!(
"Only 2 UV Channels are supported, but {material_name} ({material_index}) \
has the TEXCOORD attribute {} on texture kind {texture_kind}, which will fallback to 0.",
tex_coord,
);
UvChannel::Uv0
}
}
}
fn convert_texture_transform_to_affine2(texture_transform: TextureTransform) -> Affine2 {
Affine2::from_scale_angle_translation(
texture_transform.scale().into(),
@ -1700,11 +1775,17 @@ struct AnimationContext {
struct ClearcoatExtension {
clearcoat_factor: Option<f64>,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_channel: UvChannel,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_texture: Option<Handle<Image>>,
clearcoat_roughness_factor: Option<f64>,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_roughness_channel: UvChannel,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_roughness_texture: Option<Handle<Image>>,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_normal_channel: UvChannel,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_normal_texture: Option<Handle<Image>>,
}
@ -1713,32 +1794,66 @@ impl ClearcoatExtension {
fn parse(
load_context: &mut LoadContext,
document: &Document,
material_extensions: Option<&Map<String, Value>>,
material: &Material,
) -> Option<ClearcoatExtension> {
let extension = material_extensions?
let extension = material
.extensions()?
.get("KHR_materials_clearcoat")?
.as_object()?;
#[cfg(feature = "pbr_multi_layer_material_textures")]
let (clearcoat_channel, clearcoat_texture) = extension
.get("clearcoatTexture")
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
.map(|json_info| {
(
get_uv_channel(material, "clearcoat", json_info.tex_coord),
texture_handle_from_info(load_context, document, &json_info),
)
})
.unzip();
#[cfg(feature = "pbr_multi_layer_material_textures")]
let (clearcoat_roughness_channel, clearcoat_roughness_texture) = extension
.get("clearcoatRoughnessTexture")
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
.map(|json_info| {
(
get_uv_channel(material, "clearcoat roughness", json_info.tex_coord),
texture_handle_from_info(load_context, document, &json_info),
)
})
.unzip();
#[cfg(feature = "pbr_multi_layer_material_textures")]
let (clearcoat_normal_channel, clearcoat_normal_texture) = extension
.get("clearcoatNormalTexture")
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
.map(|json_info| {
(
get_uv_channel(material, "clearcoat normal", json_info.tex_coord),
texture_handle_from_info(load_context, document, &json_info),
)
})
.unzip();
Some(ClearcoatExtension {
clearcoat_factor: extension.get("clearcoatFactor").and_then(Value::as_f64),
clearcoat_roughness_factor: extension
.get("clearcoatRoughnessFactor")
.and_then(Value::as_f64),
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_texture: extension
.get("clearcoatTexture")
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
.map(|json_info| texture_handle_from_info(load_context, document, &json_info)),
clearcoat_channel: clearcoat_channel.unwrap_or_default(),
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_roughness_texture: extension
.get("clearcoatRoughnessTexture")
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
.map(|json_info| texture_handle_from_info(load_context, document, &json_info)),
clearcoat_texture,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_normal_texture: extension
.get("clearcoatNormalTexture")
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
.map(|json_info| texture_handle_from_info(load_context, document, &json_info)),
clearcoat_roughness_channel: clearcoat_roughness_channel.unwrap_or_default(),
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_roughness_texture,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_normal_channel: clearcoat_normal_channel.unwrap_or_default(),
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_normal_texture,
})
}
}

View file

@ -10,6 +10,18 @@ use bitflags::bitflags;
use crate::deferred::DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID;
use crate::*;
/// An enum to define which UV attribute to use for a texture.
/// It is used for every texture in the [`StandardMaterial`].
/// It only supports two UV attributes, [`Mesh::ATTRIBUTE_UV_0`] and [`Mesh::ATTRIBUTE_UV_1`].
/// The default is [`UvChannel::Uv0`].
#[derive(Reflect, Default, Debug, Clone, PartialEq, Eq)]
#[reflect(Default, Debug)]
pub enum UvChannel {
#[default]
Uv0,
Uv1,
}
/// A material with "standard" properties used in PBR lighting
/// Standard property values with pictures here
/// <https://google.github.io/filament/Material%20Properties.pdf>.
@ -29,6 +41,11 @@ pub struct StandardMaterial {
/// Defaults to [`Color::WHITE`].
pub base_color: Color,
/// The UV channel to use for the [`StandardMaterial::base_color_texture`].
///
/// Defaults to [`UvChannel::Uv0`].
pub base_color_channel: UvChannel,
/// The texture component of the material's color before lighting.
/// The actual pre-lighting color is `base_color * this_texture`.
///
@ -73,6 +90,11 @@ pub struct StandardMaterial {
/// it just adds a value to the color seen on screen.
pub emissive: Color,
/// The UV channel to use for the [`StandardMaterial::emissive_texture`].
///
/// Defaults to [`UvChannel::Uv0`].
pub emissive_channel: UvChannel,
/// The emissive map, multiplies pixels with [`emissive`]
/// to get the final "emitting" color of a surface.
///
@ -114,6 +136,11 @@ pub struct StandardMaterial {
/// color as `metallic * metallic_texture_value`.
pub metallic: f32,
/// The UV channel to use for the [`StandardMaterial::metallic_roughness_texture`].
///
/// Defaults to [`UvChannel::Uv0`].
pub metallic_roughness_channel: UvChannel,
/// Metallic and roughness maps, stored as a single texture.
///
/// The blue channel contains metallic values,
@ -170,6 +197,12 @@ pub struct StandardMaterial {
#[doc(alias = "translucency")]
pub diffuse_transmission: f32,
/// The UV channel to use for the [`StandardMaterial::diffuse_transmission_texture`].
///
/// Defaults to [`UvChannel::Uv0`].
#[cfg(feature = "pbr_transmission_textures")]
pub diffuse_transmission_channel: UvChannel,
/// A map that modulates diffuse transmission via its alpha channel. Multiplied by [`StandardMaterial::diffuse_transmission`]
/// to obtain the final result.
///
@ -205,6 +238,12 @@ pub struct StandardMaterial {
#[doc(alias = "refraction")]
pub specular_transmission: f32,
/// The UV channel to use for the [`StandardMaterial::specular_transmission_texture`].
///
/// Defaults to [`UvChannel::Uv0`].
#[cfg(feature = "pbr_transmission_textures")]
pub specular_transmission_channel: UvChannel,
/// A map that modulates specular transmission via its red channel. Multiplied by [`StandardMaterial::specular_transmission`]
/// to obtain the final result.
///
@ -228,6 +267,12 @@ pub struct StandardMaterial {
#[doc(alias = "thin_walled")]
pub thickness: f32,
/// The UV channel to use for the [`StandardMaterial::thickness_texture`].
///
/// Defaults to [`UvChannel::Uv0`].
#[cfg(feature = "pbr_transmission_textures")]
pub thickness_channel: UvChannel,
/// A map that modulates thickness via its green channel. Multiplied by [`StandardMaterial::thickness`]
/// to obtain the final result.
///
@ -294,6 +339,11 @@ pub struct StandardMaterial {
#[doc(alias = "extinction_color")]
pub attenuation_color: Color,
/// The UV channel to use for the [`StandardMaterial::normal_map_texture`].
///
/// Defaults to [`UvChannel::Uv0`].
pub normal_map_channel: UvChannel,
/// Used to fake the lighting of bumps and dents on a material.
///
/// A typical usage would be faking cobblestones on a flat plane mesh in 3D.
@ -323,6 +373,11 @@ pub struct StandardMaterial {
/// it to right-handed conventions.
pub flip_normal_map_y: bool,
/// The UV channel to use for the [`StandardMaterial::occlusion_texture`].
///
/// Defaults to [`UvChannel::Uv0`].
pub occlusion_channel: UvChannel,
/// Specifies the level of exposure to ambient light.
///
/// This is usually generated and stored automatically ("baked") by 3D-modelling software.
@ -347,6 +402,12 @@ pub struct StandardMaterial {
/// Defaults to zero, specifying no clearcoat layer.
pub clearcoat: f32,
/// The UV channel to use for the [`StandardMaterial::clearcoat_texture`].
///
/// Defaults to [`UvChannel::Uv0`].
#[cfg(feature = "pbr_multi_layer_material_textures")]
pub clearcoat_channel: UvChannel,
/// An image texture that specifies the strength of the clearcoat layer in
/// the red channel. Values sampled from this texture are multiplied by the
/// main [`StandardMaterial::clearcoat`] factor.
@ -366,6 +427,12 @@ pub struct StandardMaterial {
/// Defaults to 0.5.
pub clearcoat_perceptual_roughness: f32,
/// The UV channel to use for the [`StandardMaterial::clearcoat_roughness_texture`].
///
/// Defaults to [`UvChannel::Uv0`].
#[cfg(feature = "pbr_multi_layer_material_textures")]
pub clearcoat_roughness_channel: UvChannel,
/// An image texture that specifies the roughness of the clearcoat level in
/// the green channel. Values from this texture are multiplied by the main
/// [`StandardMaterial::clearcoat_perceptual_roughness`] factor.
@ -376,6 +443,12 @@ pub struct StandardMaterial {
#[cfg(feature = "pbr_multi_layer_material_textures")]
pub clearcoat_roughness_texture: Option<Handle<Image>>,
/// The UV channel to use for the [`StandardMaterial::clearcoat_normal_texture`].
///
/// Defaults to [`UvChannel::Uv0`].
#[cfg(feature = "pbr_multi_layer_material_textures")]
pub clearcoat_normal_channel: UvChannel,
/// An image texture that specifies a normal map that is to be applied to
/// the clearcoat layer. This can be used to simulate, for example,
/// scratches on an outer layer of varnish. Normal maps are in the same
@ -607,13 +680,16 @@ impl Default for StandardMaterial {
// White because it gets multiplied with texture values if someone uses
// a texture.
base_color: Color::WHITE,
base_color_channel: UvChannel::Uv0,
base_color_texture: None,
emissive: Color::BLACK,
emissive_channel: UvChannel::Uv0,
emissive_texture: None,
// Matches Blender's default roughness.
perceptual_roughness: 0.5,
// Metallic should generally be set to 0.0 or 1.0.
metallic: 0.0,
metallic_roughness_channel: UvChannel::Uv0,
metallic_roughness_texture: None,
// Minimum real-world reflectance is 2%, most materials between 2-5%
// Expressed in a linear scale and equivalent to 4% reflectance see
@ -621,25 +697,39 @@ impl Default for StandardMaterial {
reflectance: 0.5,
diffuse_transmission: 0.0,
#[cfg(feature = "pbr_transmission_textures")]
diffuse_transmission_channel: UvChannel::Uv0,
#[cfg(feature = "pbr_transmission_textures")]
diffuse_transmission_texture: None,
specular_transmission: 0.0,
#[cfg(feature = "pbr_transmission_textures")]
specular_transmission_channel: UvChannel::Uv0,
#[cfg(feature = "pbr_transmission_textures")]
specular_transmission_texture: None,
thickness: 0.0,
#[cfg(feature = "pbr_transmission_textures")]
thickness_channel: UvChannel::Uv0,
#[cfg(feature = "pbr_transmission_textures")]
thickness_texture: None,
ior: 1.5,
attenuation_color: Color::WHITE,
attenuation_distance: f32::INFINITY,
occlusion_channel: UvChannel::Uv0,
occlusion_texture: None,
normal_map_channel: UvChannel::Uv0,
normal_map_texture: None,
clearcoat: 0.0,
clearcoat_perceptual_roughness: 0.5,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_channel: UvChannel::Uv0,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_texture: None,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_roughness_channel: UvChannel::Uv0,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_roughness_texture: None,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_normal_channel: UvChannel::Uv0,
#[cfg(feature = "pbr_multi_layer_material_textures")]
clearcoat_normal_texture: None,
flip_normal_map_y: false,
double_sided: false,
@ -914,6 +1004,17 @@ bitflags! {
const SPECULAR_TRANSMISSION = 0x20;
const CLEARCOAT = 0x40;
const CLEARCOAT_NORMAL_MAP = 0x80;
const BASE_COLOR_UV = 0x00100;
const EMISSIVE_UV = 0x00200;
const METALLIC_ROUGHNESS_UV = 0x00400;
const OCCLUSION_UV = 0x00800;
const SPECULAR_TRANSMISSION_UV = 0x01000;
const THICKNESS_UV = 0x02000;
const DIFFUSE_TRANSMISSION_UV = 0x04000;
const NORMAL_MAP_UV = 0x08000;
const CLEARCOAT_UV = 0x10000;
const CLEARCOAT_ROUGHNESS_UV = 0x20000;
const CLEARCOAT_NORMAL_UV = 0x40000;
const DEPTH_BIAS = 0xffffffff_00000000;
}
}
@ -959,6 +1060,58 @@ impl From<&StandardMaterial> for StandardMaterialKey {
material.clearcoat > 0.0 && material.clearcoat_normal_texture.is_some(),
);
key.set(
StandardMaterialKey::BASE_COLOR_UV,
material.base_color_channel != UvChannel::Uv0,
);
key.set(
StandardMaterialKey::EMISSIVE_UV,
material.emissive_channel != UvChannel::Uv0,
);
key.set(
StandardMaterialKey::METALLIC_ROUGHNESS_UV,
material.metallic_roughness_channel != UvChannel::Uv0,
);
key.set(
StandardMaterialKey::OCCLUSION_UV,
material.occlusion_channel != UvChannel::Uv0,
);
#[cfg(feature = "pbr_transmission_textures")]
{
key.set(
StandardMaterialKey::SPECULAR_TRANSMISSION_UV,
material.specular_transmission_channel != UvChannel::Uv0,
);
key.set(
StandardMaterialKey::THICKNESS_UV,
material.thickness_channel != UvChannel::Uv0,
);
key.set(
StandardMaterialKey::DIFFUSE_TRANSMISSION_UV,
material.diffuse_transmission_channel != UvChannel::Uv0,
);
}
key.set(
StandardMaterialKey::NORMAL_MAP_UV,
material.normal_map_channel != UvChannel::Uv0,
);
#[cfg(feature = "pbr_multi_layer_material_textures")]
{
key.set(
StandardMaterialKey::CLEARCOAT_UV,
material.clearcoat_channel != UvChannel::Uv0,
);
key.set(
StandardMaterialKey::CLEARCOAT_ROUGHNESS_UV,
material.clearcoat_roughness_channel != UvChannel::Uv0,
);
key.set(
StandardMaterialKey::CLEARCOAT_NORMAL_UV,
material.clearcoat_normal_channel != UvChannel::Uv0,
);
}
key.insert(StandardMaterialKey::from_bits_retain(
(material.depth_bias as u64) << STANDARD_MATERIAL_KEY_DEPTH_BIAS_SHIFT,
));
@ -1062,6 +1215,50 @@ impl Material for StandardMaterial {
StandardMaterialKey::CLEARCOAT_NORMAL_MAP,
"STANDARD_MATERIAL_CLEARCOAT_NORMAL_MAP",
),
(
StandardMaterialKey::BASE_COLOR_UV,
"STANDARD_MATERIAL_BASE_COLOR_UV_B",
),
(
StandardMaterialKey::EMISSIVE_UV,
"STANDARD_MATERIAL_EMISSIVE_UV_B",
),
(
StandardMaterialKey::METALLIC_ROUGHNESS_UV,
"STANDARD_MATERIAL_METALLIC_ROUGHNESS_UV_B",
),
(
StandardMaterialKey::OCCLUSION_UV,
"STANDARD_MATERIAL_OCCLUSION_UV_B",
),
(
StandardMaterialKey::SPECULAR_TRANSMISSION_UV,
"STANDARD_MATERIAL_SPECULAR_TRANSMISSION_UV_B",
),
(
StandardMaterialKey::THICKNESS_UV,
"STANDARD_MATERIAL_THICKNESS_UV_B",
),
(
StandardMaterialKey::DIFFUSE_TRANSMISSION_UV,
"STANDARD_MATERIAL_DIFFUSE_TRANSMISSION_UV_B",
),
(
StandardMaterialKey::NORMAL_MAP_UV,
"STANDARD_MATERIAL_NORMAL_MAP_UV_B",
),
(
StandardMaterialKey::CLEARCOAT_UV,
"STANDARD_MATERIAL_CLEARCOAT_UV_B",
),
(
StandardMaterialKey::CLEARCOAT_ROUGHNESS_UV,
"STANDARD_MATERIAL_CLEARCOAT_ROUGHNESS_UV_B",
),
(
StandardMaterialKey::CLEARCOAT_NORMAL_UV,
"STANDARD_MATERIAL_CLEARCOAT_NORMAL_UV_B",
),
] {
if key.bind_group_data.intersects(flags) {
shader_defs.push(shader_def.into());

View file

@ -394,10 +394,12 @@ where
if layout.0.contains(Mesh::ATTRIBUTE_UV_0) {
shader_defs.push("VERTEX_UVS".into());
shader_defs.push("VERTEX_UVS_A".into());
vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(1));
}
if layout.0.contains(Mesh::ATTRIBUTE_UV_1) {
shader_defs.push("VERTEX_UVS".into());
shader_defs.push("VERTEX_UVS_B".into());
vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(2));
}

View file

@ -56,9 +56,9 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
out.position.z = min(out.position.z, 1.0);
#endif // DEPTH_CLAMP_ORTHO
#ifdef VERTEX_UVS
#ifdef VERTEX_UVS_A
out.uv = vertex.uv;
#endif // VERTEX_UVS
#endif // VERTEX_UVS_A
#ifdef VERTEX_UVS_B
out.uv_b = vertex.uv_b;

View file

@ -6,7 +6,7 @@ struct Vertex {
@builtin(instance_index) instance_index: u32,
@location(0) position: vec3<f32>,
#ifdef VERTEX_UVS
#ifdef VERTEX_UVS_A
@location(1) uv: vec2<f32>,
#endif
@ -40,7 +40,7 @@ struct VertexOutput {
// and `frag coord` when used as a fragment stage input
@builtin(position) position: vec4<f32>,
#ifdef VERTEX_UVS
#ifdef VERTEX_UVS_A
@location(0) uv: vec2<f32>,
#endif

View file

@ -8,7 +8,7 @@ struct Vertex {
#ifdef VERTEX_NORMALS
@location(1) normal: vec3<f32>,
#endif
#ifdef VERTEX_UVS
#ifdef VERTEX_UVS_A
@location(2) uv: vec2<f32>,
#endif
#ifdef VERTEX_UVS_B
@ -35,7 +35,7 @@ struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) world_position: vec4<f32>,
@location(1) world_normal: vec3<f32>,
#ifdef VERTEX_UVS
#ifdef VERTEX_UVS_A
@location(2) uv: vec2<f32>,
#endif
#ifdef VERTEX_UVS_B

View file

@ -1528,10 +1528,12 @@ impl SpecializedMeshPipeline for MeshPipeline {
if layout.0.contains(Mesh::ATTRIBUTE_UV_0) {
shader_defs.push("VERTEX_UVS".into());
shader_defs.push("VERTEX_UVS_A".into());
vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2));
}
if layout.0.contains(Mesh::ATTRIBUTE_UV_1) {
shader_defs.push("VERTEX_UVS".into());
shader_defs.push("VERTEX_UVS_B".into());
vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(3));
}

View file

@ -63,10 +63,9 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
out.position = position_world_to_clip(out.world_position.xyz);
#endif
#ifdef VERTEX_UVS
#ifdef VERTEX_UVS_A
out.uv = vertex.uv;
#endif
#ifdef VERTEX_UVS_B
out.uv_b = vertex.uv_b;
#endif

View file

@ -91,7 +91,15 @@ fn pbr_input_from_standard_material(
#ifdef VERTEX_UVS
let uv_transform = pbr_bindings::material.uv_transform;
#ifdef VERTEX_UVS_A
var uv = (uv_transform * vec3(in.uv, 1.0)).xy;
#endif
#ifdef VERTEX_UVS_B
var uv_b = (uv_transform * vec3(in.uv_b, 1.0)).xy;
#else
var uv_b = uv;
#endif
#ifdef VERTEX_TANGENTS
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_DEPTH_MAP_BIT) != 0u) {
@ -101,6 +109,7 @@ fn pbr_input_from_standard_material(
let B = in.world_tangent.w * cross(N, T);
// Transform V from fragment to camera in world space to tangent space.
let Vt = vec3(dot(V, T), dot(V, B), dot(V, N));
#ifdef VERTEX_UVS_A
uv = parallaxed_uv(
pbr_bindings::material.parallax_depth_scale,
pbr_bindings::material.max_parallax_layer_count,
@ -111,6 +120,22 @@ fn pbr_input_from_standard_material(
// about.
-Vt,
);
#endif
#ifdef VERTEX_UVS_B
uv_b = parallaxed_uv(
pbr_bindings::material.parallax_depth_scale,
pbr_bindings::material.max_parallax_layer_count,
pbr_bindings::material.max_relief_mapping_search_steps,
uv_b,
// Flip the direction of Vt to go toward the surface to make the
// parallax mapping algorithm easier to understand and reason
// about.
-Vt,
);
#else
uv_b = uv;
#endif
}
#endif // VERTEX_TANGENTS
@ -118,7 +143,11 @@ fn pbr_input_from_standard_material(
pbr_input.material.base_color *= pbr_functions::sample_texture(
pbr_bindings::base_color_texture,
pbr_bindings::base_color_sampler,
#ifdef STANDARD_MATERIAL_BASE_COLOR_UV_B
uv_b,
#else
uv,
#endif
bias,
);
@ -156,7 +185,11 @@ fn pbr_input_from_standard_material(
emissive = vec4<f32>(emissive.rgb * pbr_functions::sample_texture(
pbr_bindings::emissive_texture,
pbr_bindings::emissive_sampler,
#ifdef STANDARD_MATERIAL_EMISSIVE_UV_B
uv_b,
#else
uv,
#endif
bias,
).rgb, 1.0);
}
@ -172,7 +205,11 @@ fn pbr_input_from_standard_material(
let metallic_roughness = pbr_functions::sample_texture(
pbr_bindings::metallic_roughness_texture,
pbr_bindings::metallic_roughness_sampler,
#ifdef STANDARD_MATERIAL_METALLIC_ROUGHNESS_UV_B
uv_b,
#else
uv,
#endif
bias,
);
// Sampling from GLTF standard channels for now
@ -191,7 +228,11 @@ fn pbr_input_from_standard_material(
pbr_input.material.clearcoat *= pbr_functions::sample_texture(
pbr_bindings::clearcoat_texture,
pbr_bindings::clearcoat_sampler,
#ifdef STANDARD_MATERIAL_CLEARCOAT_UV_B
uv_b,
#else
uv,
#endif
bias,
).r;
}
@ -206,7 +247,11 @@ fn pbr_input_from_standard_material(
pbr_input.material.clearcoat_perceptual_roughness *= pbr_functions::sample_texture(
pbr_bindings::clearcoat_roughness_texture,
pbr_bindings::clearcoat_roughness_sampler,
#ifdef STANDARD_MATERIAL_CLEARCOAT_ROUGHNESS_UV_B
uv_b,
#else
uv,
#endif
bias,
).g;
}
@ -214,28 +259,40 @@ fn pbr_input_from_standard_material(
#endif // VERTEX_UVS
var specular_transmission: f32 = pbr_bindings::material.specular_transmission;
#ifdef VERTEX_UVS
#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_SPECULAR_TRANSMISSION_TEXTURE_BIT) != 0u) {
specular_transmission *= pbr_functions::sample_texture(
pbr_bindings::specular_transmission_texture,
pbr_bindings::specular_transmission_sampler,
#ifdef STANDARD_MATERIAL_SPECULAR_TRANSMISSION_UV_B
uv_b,
#else
uv,
#endif
bias,
).r;
}
#endif
#endif
pbr_input.material.specular_transmission = specular_transmission;
var thickness: f32 = pbr_bindings::material.thickness;
#ifdef VERTEX_UVS
#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_THICKNESS_TEXTURE_BIT) != 0u) {
thickness *= pbr_functions::sample_texture(
pbr_bindings::thickness_texture,
pbr_bindings::thickness_sampler,
#ifdef STANDARD_MATERIAL_THICKNESS_UV_B
uv_b,
#else
uv,
#endif
bias,
).g;
}
#endif
#endif
// scale thickness, accounting for non-uniform scaling (e.g. a squished mesh)
// TODO: Meshlet support
@ -247,15 +304,21 @@ fn pbr_input_from_standard_material(
pbr_input.material.thickness = thickness;
var diffuse_transmission = pbr_bindings::material.diffuse_transmission;
#ifdef VERTEX_UVS
#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_DIFFUSE_TRANSMISSION_TEXTURE_BIT) != 0u) {
diffuse_transmission *= pbr_functions::sample_texture(
pbr_bindings::diffuse_transmission_texture,
pbr_bindings::diffuse_transmission_sampler,
#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION_UV_B
uv_b,
#else
uv,
#endif
bias,
).a;
}
#endif
#endif
pbr_input.material.diffuse_transmission = diffuse_transmission;
@ -266,7 +329,11 @@ fn pbr_input_from_standard_material(
diffuse_occlusion *= pbr_functions::sample_texture(
pbr_bindings::occlusion_texture,
pbr_bindings::occlusion_sampler,
#ifdef STANDARD_MATERIAL_OCCLUSION_UV_B
uv_b,
#else
uv,
#endif
bias,
).r;
}
@ -296,7 +363,11 @@ fn pbr_input_from_standard_material(
let Nt = pbr_functions::sample_texture(
pbr_bindings::normal_map_texture,
pbr_bindings::normal_map_sampler,
uv,
#ifdef STANDARD_MATERIAL_NORMAL_MAP_UV_B
uv_b,
#else
uv,
#endif
bias,
).rgb;
@ -323,7 +394,11 @@ fn pbr_input_from_standard_material(
let clearcoat_Nt = pbr_functions::sample_texture(
pbr_bindings::clearcoat_normal_texture,
pbr_bindings::clearcoat_normal_sampler,
uv,
#ifdef STANDARD_MATERIAL_CLEARCOAT_NORMAL_UV_B
uv_b,
#else
uv,
#endif
bias,
).rgb;

View file

@ -53,6 +53,12 @@ fn fragment(
#ifdef VERTEX_TANGENTS
#ifdef STANDARD_MATERIAL_NORMAL_MAP
#ifdef STANDARD_MATERIAL_NORMAL_MAP_UV_B
let uv = in.uv_b;
#else
let uv = in.uv;
#endif
// Fill in the sample bias so we can sample from textures.
var bias: SampleBias;
#ifdef MESHLET_MESH_MATERIAL_PASS
@ -65,7 +71,7 @@ fn fragment(
let Nt = pbr_functions::sample_texture(
pbr_bindings::normal_map_texture,
pbr_bindings::normal_map_sampler,
in.uv,
uv,
bias,
).rgb;

View file

@ -18,8 +18,18 @@ fn prepass_alpha_discard(in: VertexOutput) {
var output_color: vec4<f32> = pbr_bindings::material.base_color;
#ifdef VERTEX_UVS
#ifdef VERTEX_UVS_A
var uv = in.uv;
#else
var uv = in.uv_b;
#endif
#ifdef VERTEX_UVS_B
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_UV_BIT) != 0u) {
uv = in.uv_b;
}
#endif
let uv_transform = pbr_bindings::material.uv_transform;
let uv = (uv_transform * vec3(in.uv, 1.0)).xy;
uv = (uv_transform * vec3(uv, 1.0)).xy;
if (pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u {
output_color = output_color * textureSampleBias(pbr_bindings::base_color_texture, pbr_bindings::base_color_sampler, uv, view.mip_bias);
}