mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
Add support for KHR_texture_transform (#11904)
Adopted #8266, so copy-pasting the description from there: # Objective Support the KHR_texture_transform extension for the glTF loader. - Fixes #6335 - Fixes #11869 - Implements part of #11350 - Implements the GLTF part of #399 ## Solution As is, this only supports a single transform. Looking at Godot's source, they support one transform with an optional second one for detail, AO, and emission. glTF specifies one per texture. The public domain materials I looked at seem to share the same transform. So maybe having just one is acceptable for now. I tried to include a warning if multiple different transforms exist for the same material. Note the gltf crate doesn't expose the texture transform for the normal and occlusion textures, which it should, so I just ignored those for now. (note by @janhohenheim: this is still the case) Via `cargo run --release --example scene_viewer ~/src/clone/glTF-Sample-Models/2.0/TextureTransformTest/glTF/TextureTransformTest.gltf`: ![texture_transform](https://user-images.githubusercontent.com/283864/228938298-aa2ef524-555b-411d-9637-fd0dac226fb0.png) ## Changelog Support for the [KHR_texture_transform](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform) extension added. Texture UVs that were scaled, rotated, or offset in a GLTF are now properly handled. --------- Co-authored-by: Al McElrath <hello@yrns.org> Co-authored-by: Kanabenki <lucien.menassol@gmail.com>
This commit is contained in:
parent
37e632145a
commit
8531033b31
12 changed files with 106 additions and 19 deletions
|
@ -43,6 +43,7 @@ gltf = { version = "1.4.0", default-features = false, features = [
|
|||
"KHR_materials_volume",
|
||||
"KHR_materials_unlit",
|
||||
"KHR_materials_emissive_strength",
|
||||
"KHR_texture_transform",
|
||||
"extras",
|
||||
"extensions",
|
||||
"names",
|
||||
|
|
|
@ -9,7 +9,7 @@ use bevy_ecs::entity::EntityHashMap;
|
|||
use bevy_ecs::{entity::Entity, world::World};
|
||||
use bevy_hierarchy::{BuildWorldChildren, WorldChildBuilder};
|
||||
use bevy_log::{error, info_span, warn};
|
||||
use bevy_math::{Mat4, Vec3};
|
||||
use bevy_math::{Affine2, Mat4, Vec3};
|
||||
use bevy_pbr::{
|
||||
AlphaMode, DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight, PointLightBundle,
|
||||
SpotLight, SpotLightBundle, StandardMaterial, MAX_JOINTS,
|
||||
|
@ -42,7 +42,7 @@ use bevy_utils::{
|
|||
use gltf::{
|
||||
accessor::Iter,
|
||||
mesh::{util::ReadIndices, Mode},
|
||||
texture::{MagFilter, MinFilter, WrappingMode},
|
||||
texture::{Info, MagFilter, MinFilter, TextureTransform, WrappingMode},
|
||||
Material, Node, Primitive, Semantic,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -826,6 +826,14 @@ fn load_material(
|
|||
texture_handle(load_context, &info.texture())
|
||||
});
|
||||
|
||||
let uv_transform = pbr
|
||||
.base_color_texture()
|
||||
.and_then(|info| {
|
||||
info.texture_transform()
|
||||
.map(convert_texture_transform_to_affine2)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let normal_map_texture: Option<Handle<Image>> =
|
||||
material.normal_texture().map(|normal_texture| {
|
||||
// TODO: handle normal_texture.scale
|
||||
|
@ -835,6 +843,12 @@ fn load_material(
|
|||
|
||||
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,
|
||||
uv_transform,
|
||||
"metallic/roughness",
|
||||
);
|
||||
texture_handle(load_context, &info.texture())
|
||||
});
|
||||
|
||||
|
@ -848,6 +862,7 @@ fn load_material(
|
|||
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())
|
||||
});
|
||||
|
||||
|
@ -935,11 +950,51 @@ fn load_material(
|
|||
),
|
||||
unlit: material.unlit(),
|
||||
alpha_mode: alpha_mode(material),
|
||||
uv_transform,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_texture_transform_to_affine2(texture_transform: TextureTransform) -> Affine2 {
|
||||
Affine2::from_scale_angle_translation(
|
||||
texture_transform.scale().into(),
|
||||
-texture_transform.rotation(),
|
||||
texture_transform.offset().into(),
|
||||
)
|
||||
}
|
||||
|
||||
fn warn_on_differing_texture_transforms(
|
||||
material: &Material,
|
||||
info: &Info,
|
||||
texture_transform: Affine2,
|
||||
texture_kind: &str,
|
||||
) {
|
||||
let has_differing_texture_transform = info
|
||||
.texture_transform()
|
||||
.map(convert_texture_transform_to_affine2)
|
||||
.is_some_and(|t| t != texture_transform);
|
||||
if has_differing_texture_transform {
|
||||
let material_name = material
|
||||
.name()
|
||||
.map(|n| format!("the material \"{n}\""))
|
||||
.unwrap_or_else(|| "an unnamed material".to_string());
|
||||
let texture_name = info
|
||||
.texture()
|
||||
.name()
|
||||
.map(|n| format!("its {texture_kind} texture \"{n}\""))
|
||||
.unwrap_or_else(|| format!("its unnamed {texture_kind} texture"));
|
||||
let material_index = material
|
||||
.index()
|
||||
.map(|i| format!("index {i}"))
|
||||
.unwrap_or_else(|| "default".to_string());
|
||||
warn!(
|
||||
"Only texture transforms on base color textures are supported, but {material_name} ({material_index}) \
|
||||
has a texture transform on {texture_name} (index {}), which will be ignored.", info.texture().index()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a glTF node.
|
||||
#[allow(clippy::too_many_arguments, clippy::result_large_err)]
|
||||
fn load_node(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use bevy_asset::{Asset, Handle};
|
||||
use bevy_math::Vec4;
|
||||
use bevy_math::{Affine2, Vec2, Vec4};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::{
|
||||
color::Color, mesh::MeshVertexBufferLayout, render_asset::RenderAssets, render_resource::*,
|
||||
|
@ -472,6 +472,9 @@ pub struct StandardMaterial {
|
|||
/// Default is [`DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID`] for default
|
||||
/// PBR deferred lighting pass. Ignored in the case of forward materials.
|
||||
pub deferred_lighting_pass_id: u8,
|
||||
|
||||
/// The transform applied to the UVs corresponding to ATTRIBUTE_UV_0 on the mesh before sampling. Default is identity.
|
||||
pub uv_transform: Affine2,
|
||||
}
|
||||
|
||||
impl Default for StandardMaterial {
|
||||
|
@ -520,6 +523,7 @@ impl Default for StandardMaterial {
|
|||
parallax_mapping_method: ParallaxMappingMethod::Occlusion,
|
||||
opaque_render_method: OpaqueRendererMethod::Auto,
|
||||
deferred_lighting_pass_id: DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID,
|
||||
uv_transform: Affine2::IDENTITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -590,9 +594,17 @@ pub struct StandardMaterialUniform {
|
|||
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
|
||||
/// in between.
|
||||
pub base_color: Vec4,
|
||||
// Use a color for user friendliness even though we technically don't use the alpha channel
|
||||
// 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: Vec4,
|
||||
/// Color white light takes after travelling through the attenuation distance underneath the material surface
|
||||
pub attenuation_color: Vec4,
|
||||
/// The x-axis of the mat2 of the transform applied to the UVs corresponding to ATTRIBUTE_UV_0 on the mesh before sampling. Default is [1, 0].
|
||||
pub uv_transform_x_axis: Vec2,
|
||||
/// The y-axis of the mat2 of the transform applied to the UVs corresponding to ATTRIBUTE_UV_0 on the mesh before sampling. Default is [0, 1].
|
||||
pub uv_transform_y_axis: Vec2,
|
||||
/// The translation of the transform applied to the UVs corresponding to ATTRIBUTE_UV_0 on the mesh before sampling. Default is [0, 0].
|
||||
pub uv_transform_translation: Vec2,
|
||||
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
|
||||
/// Defaults to minimum of 0.089
|
||||
pub roughness: f32,
|
||||
|
@ -611,8 +623,6 @@ pub struct StandardMaterialUniform {
|
|||
pub ior: f32,
|
||||
/// How far light travels through the volume underneath the material surface before being absorbed
|
||||
pub attenuation_distance: f32,
|
||||
/// Color white light takes after travelling through the attenuation distance underneath the material surface
|
||||
pub attenuation_color: Vec4,
|
||||
/// The [`StandardMaterialFlags`] accessible in the `wgsl` shader.
|
||||
pub flags: u32,
|
||||
/// When the alpha mode mask flag is set, any base color alpha above this cutoff means fully opaque,
|
||||
|
@ -729,6 +739,9 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
|
|||
lightmap_exposure: self.lightmap_exposure,
|
||||
max_relief_mapping_search_steps: self.parallax_mapping_method.max_steps(),
|
||||
deferred_lighting_pass_id: self.deferred_lighting_pass_id as u32,
|
||||
uv_transform_x_axis: self.uv_transform.matrix2.x_axis,
|
||||
uv_transform_y_axis: self.uv_transform.matrix2.y_axis,
|
||||
uv_transform_translation: self.uv_transform.translation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,15 +6,15 @@
|
|||
mesh_types::MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT,
|
||||
view_transformations::position_world_to_clip,
|
||||
}
|
||||
#import bevy_render::maths::{affine_to_square, mat2x4_f32_to_mat3x3_unpack}
|
||||
#import bevy_render::maths::{affine3_to_square, mat2x4_f32_to_mat3x3_unpack}
|
||||
|
||||
|
||||
fn get_model_matrix(instance_index: u32) -> mat4x4<f32> {
|
||||
return affine_to_square(mesh[instance_index].model);
|
||||
return affine3_to_square(mesh[instance_index].model);
|
||||
}
|
||||
|
||||
fn get_previous_model_matrix(instance_index: u32) -> mat4x4<f32> {
|
||||
return affine_to_square(mesh[instance_index].previous_model);
|
||||
return affine3_to_square(mesh[instance_index].previous_model);
|
||||
}
|
||||
|
||||
fn mesh_position_local_to_world(model: mat4x4<f32>, vertex_position: vec4<f32>) -> vec4<f32> {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
struct Mesh {
|
||||
// Affine 4x3 matrices transposed to 3x4
|
||||
// Use bevy_render::maths::affine_to_square to unpack
|
||||
// Use bevy_render::maths::affine3_to_square to unpack
|
||||
model: mat3x4<f32>,
|
||||
previous_model: mat3x4<f32>,
|
||||
// 3x3 matrix packed in mat2x4 and f32 as:
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
parallax_mapping::parallaxed_uv,
|
||||
lightmap::lightmap,
|
||||
}
|
||||
#import bevy_render::maths::affine2_to_square
|
||||
|
||||
#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION
|
||||
#import bevy_pbr::mesh_view_bindings::screen_space_ambient_occlusion_texture
|
||||
|
@ -73,7 +74,8 @@ fn pbr_input_from_standard_material(
|
|||
let NdotV = max(dot(pbr_input.N, pbr_input.V), 0.0001);
|
||||
|
||||
#ifdef VERTEX_UVS
|
||||
var uv = in.uv;
|
||||
let uv_transform = affine2_to_square(pbr_bindings::material.uv_transform);
|
||||
var uv = (uv_transform * vec3(in.uv, 1.0)).xy;
|
||||
|
||||
#ifdef VERTEX_TANGENTS
|
||||
if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_DEPTH_MAP_BIT) != 0u) {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
pbr_bindings,
|
||||
pbr_types,
|
||||
}
|
||||
#import bevy_render::maths::affine2_to_square
|
||||
|
||||
// Cutoff used for the premultiplied alpha modes BLEND and ADD.
|
||||
const PREMULTIPLIED_ALPHA_CUTOFF = 0.05;
|
||||
|
@ -18,8 +19,10 @@ fn prepass_alpha_discard(in: VertexOutput) {
|
|||
var output_color: vec4<f32> = pbr_bindings::material.base_color;
|
||||
|
||||
#ifdef VERTEX_UVS
|
||||
let uv_transform = affine2_to_square(pbr_bindings::material.uv_transform);
|
||||
let uv = (uv_transform * vec3(in.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, in.uv, view.mip_bias);
|
||||
output_color = output_color * textureSampleBias(pbr_bindings::base_color_texture, pbr_bindings::base_color_sampler, uv, view.mip_bias);
|
||||
}
|
||||
#endif // VERTEX_UVS
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
#define_import_path bevy_pbr::pbr_types
|
||||
|
||||
// Since this is a hot path, try to keep the alignment and size of the struct members in mind.
|
||||
// You can find the alignment and sizes at <https://www.w3.org/TR/WGSL/#alignment-and-size>.
|
||||
struct StandardMaterial {
|
||||
base_color: vec4<f32>,
|
||||
emissive: vec4<f32>,
|
||||
attenuation_color: vec4<f32>,
|
||||
uv_transform: mat3x2<f32>,
|
||||
perceptual_roughness: f32,
|
||||
metallic: f32,
|
||||
reflectance: f32,
|
||||
|
@ -11,7 +15,6 @@ struct StandardMaterial {
|
|||
thickness: f32,
|
||||
ior: f32,
|
||||
attenuation_distance: f32,
|
||||
attenuation_color: vec4<f32>,
|
||||
// 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
|
||||
flags: u32,
|
||||
alpha_cutoff: f32,
|
||||
|
@ -74,6 +77,8 @@ fn standard_material_new() -> StandardMaterial {
|
|||
material.max_parallax_layer_count = 16.0;
|
||||
material.max_relief_mapping_search_steps = 5u;
|
||||
material.deferred_lighting_pass_id = 1u;
|
||||
// scale 1, translation 0, rotation 0
|
||||
material.uv_transform = mat3x2<f32>(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
|
||||
|
||||
return material;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
#define_import_path bevy_render::maths
|
||||
|
||||
fn affine_to_square(affine: mat3x4<f32>) -> mat4x4<f32> {
|
||||
fn affine2_to_square(affine: mat3x2<f32>) -> mat3x3<f32> {
|
||||
return mat3x3<f32>(
|
||||
vec3<f32>(affine[0].xy, 0.0),
|
||||
vec3<f32>(affine[1].xy, 0.0),
|
||||
vec3<f32>(affine[2].xy, 1.0),
|
||||
);
|
||||
}
|
||||
|
||||
fn affine3_to_square(affine: mat3x4<f32>) -> mat4x4<f32> {
|
||||
return transpose(mat4x4<f32>(
|
||||
affine[0],
|
||||
affine[1],
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
mesh2d_view_bindings::view,
|
||||
mesh2d_bindings::mesh,
|
||||
}
|
||||
#import bevy_render::maths::{affine_to_square, mat2x4_f32_to_mat3x3_unpack}
|
||||
#import bevy_render::maths::{affine3_to_square, mat2x4_f32_to_mat3x3_unpack}
|
||||
|
||||
fn get_model_matrix(instance_index: u32) -> mat4x4<f32> {
|
||||
return affine_to_square(mesh[instance_index].model);
|
||||
return affine3_to_square(mesh[instance_index].model);
|
||||
}
|
||||
|
||||
fn mesh2d_position_local_to_world(model: mat4x4<f32>, vertex_position: vec4<f32>) -> vec4<f32> {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
struct Mesh2d {
|
||||
// Affine 4x3 matrix transposed to 3x4
|
||||
// Use bevy_render::maths::affine_to_square to unpack
|
||||
// Use bevy_render::maths::affine3_to_square to unpack
|
||||
model: mat3x4<f32>,
|
||||
// 3x3 matrix packed in mat2x4 and f32 as:
|
||||
// [0].xyz, [1].x,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#endif
|
||||
|
||||
#import bevy_render::{
|
||||
maths::affine_to_square,
|
||||
maths::affine3_to_square,
|
||||
view::View,
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ fn vertex(in: VertexInput) -> VertexOutput {
|
|||
0.0
|
||||
);
|
||||
|
||||
out.clip_position = view.view_proj * affine_to_square(mat3x4<f32>(
|
||||
out.clip_position = view.view_proj * affine3_to_square(mat3x4<f32>(
|
||||
in.i_model_transpose_col0,
|
||||
in.i_model_transpose_col1,
|
||||
in.i_model_transpose_col2,
|
||||
|
|
Loading…
Reference in a new issue