Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tiled/repeating textures #399

Closed
tristanpemble opened this issue Aug 30, 2020 · 39 comments
Closed

Tiled/repeating textures #399

tristanpemble opened this issue Aug 30, 2020 · 39 comments
Labels
A-Assets Load files from disk to use for things like images, models, and sounds A-Rendering Drawing game state to the screen C-Docs An addition or correction to our documentation D-Trivial Nice and easy! A great choice to get started with Bevy
Milestone

Comments

@tristanpemble
Copy link
Contributor

It would be nice to set a texture to tile or repeat on a mesh instead of stretch.

@GabCampbell GabCampbell added C-Feature A new feature, making something new possible D-Trivial Nice and easy! A great choice to get started with Bevy labels Aug 30, 2020
@GabCampbell
Copy link
Contributor

Sounds like a great idea

@karroffel
Copy link
Contributor

@tristanpemble
Copy link
Contributor Author

I'm not sure how to specify a SamplerDescriptor for a texture. if I am reading this code correctly, it seems like the sampler is being generated automatically with a From impl. it does seem like a SamplerDescriptor would be the right API for this

@W4RH4WK
Copy link
Contributor

W4RH4WK commented Oct 28, 2020

How about adding SamplerDescriptor to Texture as a member and grabbing it from there? The code that constructs a SamplerDescriptor from a Texture doesn't even use that texture.
https://docs.rs/bevy_render/0.2.1/src/bevy_render/texture/sampler_descriptor.rs.html#37-52

@cart
Copy link
Member

cart commented Oct 29, 2020

Yup adding a SamplerDescriptor to Texture has been on my "mental to-do list" for awhile. I think that's the right approach.

W4RH4WK added a commit to W4RH4WK/bevy that referenced this issue Oct 29, 2020
GLTF loader now grabs (some) sampler information from the respective
GLTF sampler struct.
W4RH4WK added a commit to W4RH4WK/bevy that referenced this issue Oct 29, 2020
GLTF loader now grabs (some) sampler information from the respective
GLTF sampler struct.
@W4RH4WK
Copy link
Contributor

W4RH4WK commented Oct 29, 2020

In #747 I've moved the SamplerDescriptor and adjusted the GLTF loader.

Still, when loading a texture manually (not from a GLTF file), like it's done in the texture example, we'd still need to specify the sampler somehow. Of course, we could just keep it this way, initialized with default, and the user could still mutate it after loading.

@cart
Copy link
Member

cart commented Oct 29, 2020

@W4RH4WK sounds reasonable.

Atelier Assets (which we will eventually migrate to) allows you to specify per-asset configuration in a ".meta" file. I think we will eventually put the SamplerDescriptor config there for formats like png.

cart pushed a commit that referenced this issue Oct 29, 2020
GLTF loader now grabs (some) sampler information from the respective
GLTF sampler struct.
@cart
Copy link
Member

cart commented Oct 30, 2020

#747 largely resolves this problem, but I think we should probably hold off on closing this until we can do it for arbitrary texture files (aka using the ".meta" config in atelier assets)

@memoryruins memoryruins added A-Assets Load files from disk to use for things like images, models, and sounds A-Rendering Drawing game state to the screen labels Nov 14, 2020
@agausmann
Copy link

agausmann commented Jan 6, 2021

I've tried using this feature, and it doesn't seem to be working the way I expected as of 0.4.0.

It does get as far as recreating the Sampler: I can modify a Texture's SamplerDescriptor, and the generated AssetEvent::Modified event is being picked up by texture_resource_system, which re-creates the sampler from the descriptor.
However, that new sampler has a new unique SamplerId, which is not passed on to any already existing BindGroups or RenderResourceBindings, and no new bindings are created, so drawables created before this point will still use the old sampler.

As an example of this, my project (at commit 85229caa). It's supposed to render a 2x2 grid with a texture as the tiles, and the texture is just a plain color with a border. (If it is small, you can scroll up to zoom in and see it better.) I set the sampler's address mode in the configure_textures system, once it is created. However, sampling is still effectively clamped.

A workaround that works for now is to not spawn the sprite until the texture is loaded and the address modes are set (e.g. moving lines 39-52 to immediately after line 75, and also adding the resources it requires to the system). There may be a better way to do this, maybe using states or stages, but I haven't explored it much and I'm not familiar with bevy best practices in general.

@mtsr
Copy link
Contributor

mtsr commented Mar 25, 2021

#747 largely resolves this problem, but I think we should probably hold off on closing this until we can do it for arbitrary texture files (aka using the ".meta" config in atelier assets)

Seems to me like #747 only works for gltf::image::Source::View textures and not for externally loaded ones.

And we have another issue, with GltfLoader loading all its textures as SRGB. This can be seen here crates\bevy_render\src\texture\image_texture_conversion.rs. It could be fixed in the same way, but again, this only works for gltf::image::Source::View textures.

Regular textures get passed to the AssetServer as an AssetPath dependency, with zero configation, which will then happily load them using its defaults. I haven't dug any deeper.

@huhlig
Copy link
Contributor

huhlig commented Apr 9, 2021

It would be nice to be able to offset a repeating texture dynamically for parallax backgrounds as well

@GreatGodOfFire
Copy link

Hey, is there a way to tile textures now?

@huhlig
Copy link
Contributor

huhlig commented Oct 5, 2021

Hey, is there a way to tile textures now?

I think we're still waiting on the distill integration for asset parameters.

@johanhelsing
Copy link
Contributor

#[derive(Default)]
struct BackgroundImage(Handle<Image>);

fn configure_background_image(background: Res<BackgroundImage>, mut images: ResMut<Assets<Image>>) {
    // Doing this in response to AssetEvents seem to be broken, instead we try to set the sampler every frame
    let mut image = images.get_mut(background.0.clone());
    if let Some(image) = image {
        image.sampler_descriptor.address_mode_u = AddressMode::Repeat;
        image.sampler_descriptor.address_mode_v = AddressMode::Repeat;
    }
}

const MAP_SIZE: f32 = 100.;

fn setup(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<ColorMaterial>>,
    mut meshes: ResMut<Assets<Mesh>>,
    mut background: ResMut<BackgroundImage>,
) {
    background.0 = asset_server.load("background.png");
    let mut mesh = Mesh::from(shape::Quad::default());
    if let Some(VertexAttributeValues::Float32x2(uvs)) = mesh.attribute_mut(Mesh::ATTRIBUTE_UV_0) {
        for uv in uvs {
            uv[0] *= MAP_SIZE;
            uv[1] *= MAP_SIZE;
        }
    }

    commands.spawn_bundle(ColorMesh2dBundle {
        transform: Transform::from_scale(Vec3::splat(MAP_SIZE)),
        material: materials.add(background.0.clone().into()),
        mesh: meshes.add(mesh.into()).into(),
        ..Default::default()
    });

I ended up doing this in a project. Relevant discord thread: https://discord.com/channels/691052431525675048/742884593551802431/932954492394164255

Would be nice to know if there is a less hacky solution that works with 0.6

@djeedai
Copy link
Contributor

djeedai commented Apr 1, 2022

This doesn't work. I've spent 1 hour trying this and other solutions on Discord, none of them work on 0.6.

@sanisoclem
Copy link

run into this one today, here's what worked for me:

pub fn set_texture_tiled(
  mut texture_events: EventReader<AssetEvent<Image>>,
  mut textures: ResMut<Assets<Image>>,
) {
  for event in texture_events.iter() {
      match event {
          AssetEvent::Created { handle } => {
              if let Some(mut texture) = textures.get_mut(handle) {
                texture.sampler_descriptor.address_mode_u = bevy::render::render_resource::AddressMode::Repeat;
                texture.sampler_descriptor.address_mode_v = bevy::render::render_resource::AddressMode::Repeat;
                texture.sampler_descriptor.address_mode_w = bevy::render::render_resource::AddressMode::Repeat;
              }
          }
          _ => (),
      }
  }
}

One caveat though is this system has to run before you use the Handle<Image> in a material (not sure why). For example, the system below doesn't work if I use terrain_mat.material but it works if I construct a new material (in a different

pub fn startup_system(
  asset_server: Res<AssetServer>,
  mut terrain_mat: ResMut<TempTerrainMaterial>,
  mut materials: ResMut<Assets<StandardMaterial>>,
) {
  terrain_mat.tex = asset_server.load("textures/test.png");
  terrain_mat.normal = asset_server.load("textures/test_n.png");

  terrain_mat.material =  materials.add(StandardMaterial {
    base_color_texture: Some(terrain_mat.tex.clone()),
    normal_map_texture: Some(terrain_mat.normal.clone()),
    perceptual_roughness: 0.89,
    ..default()
  });
}


pub fn spawn_mesh(
  mut commands: Commands,
  mut meshes: ResMut<Assets<Mesh>>,
  mut materials: ResMut<Assets<StandardMaterial>>,
  terrain_mat: Res<TempTerrainMaterial>,
) {
      commands.spawn_bundle(PbrBundle {
        mesh: meshes.add(some_mesh),
        //material: terrain_mat.material.clone(), // doesn't work
        material: materials.add(StandardMaterial { // works
          base_color_texture: Some(terrain_mat.tex.clone()),
          normal_map_texture: Some(terrain_mat.normal.clone()),
          perceptual_roughness: 0.89,
          ..default()
        }),
        ..default()
      });
}

mystal added a commit to mystal/flappy-bevy that referenced this issue Jun 12, 2022
Kind of a hack based on bevyengine/bevy#399,
but good enough for now.
@Elogain
Copy link

Elogain commented Jun 20, 2022

@sanisoclem I have also ran into the issue where "pre-loaded"materials doesnt pick up the address modes if its texture! So strange. I also had to resort to re-creating the material at entity spawning time.

@alice-i-cecile
Copy link
Member

@ManevilleF, do any of your PRs completely resolve this? If so, please link this issue there :)

@ManevilleF
Copy link
Contributor

@ManevilleF, do any of your PRs completely resolve this? If so, please link this issue there :)

My MR on slicing and tiling only works on 2d texture rects, so sprites or UI

@chaynabors
Copy link

chaynabors commented Oct 4, 2022

For anyone comfortable with all of their textures using the same wgpu::SamplerDescriptor. I found that adding the following ImageSettings as a resource works rather well at the moment :)

.insert_resource(ImageSettings {
    default_sampler: SamplerDescriptor {
        address_mode_u: AddressMode::Repeat,
        address_mode_v: AddressMode::Repeat,
        address_mode_w: AddressMode::Repeat,
        ..Default::default()
    },
})

@MatrixDev
Copy link

Sadly, this doesn't help when you need textures with different parameters. And current way of listening for events when image is loaded is way to cumbersome while also not working when image is attached to the material beforehand :(

@Mokuzzai
Copy link

For those looking for the v0.10 compatible workaround:

.add_plugins(
  DefaultPlugins
    .set(ImagePlugin {
      default_sampler: SamplerDescriptor {
        address_mode_u: AddressMode::Repeat,
        address_mode_v: AddressMode::Repeat,
        address_mode_w: AddressMode::Repeat,
        ..Default::default()
      },
    })
)

Specifying sampler works only if you have a single texture that you want to repeat.

My problem is that I have a large texture (texture atlas) and I want to repeat a region of it, which is not currently possible without writing custom shader.

So having support for it in StandardMaterial would be nice

@cleak
Copy link

cleak commented Jul 12, 2023

I ended up just bypassing AssetsServer and loading the images directly. It's not ideal, but it doesn't require listening for events and it allows for both tiled and clamped textures. The loading is done synchronously this way and you'll need to give it a path relative to your working directory (probably just adding assets/ to the start of the string for most people).

pub fn load_tiled_texture(
    images: &mut Assets<Image>,
    texture_path: &str
) -> Handle<Image> {
    let ext = std::path::Path::new(texture_path).extension().unwrap().to_str().unwrap();
    let img_bytes = std::fs::read(texture_path).unwrap();
    let mut image = Image::from_buffer(&img_bytes, ImageType::Extension(ext), CompressedImageFormats::all(), true).unwrap();
    image.sampler_descriptor = ImageSampler::Descriptor(SamplerDescriptor {
        address_mode_u: AddressMode::Repeat,
        address_mode_v: AddressMode::Repeat,
        ..Default::default()
    });
    images.add(image)
}

@blunted2night
Copy link
Contributor

With the new asset server in 0.12, this worked for me

    let sampler_desc = ImageSamplerDescriptor {
        address_mode_u: ImageAddressMode::Repeat,
        address_mode_v: ImageAddressMode::Repeat,
        ..Default::default()
    };

    let settings = move |s: &mut ImageLoaderSettings| {
        s.sampler = ImageSampler::Descriptor(sampler_desc.clone());
    };

    let texture_handle = assets.load_with_settings("texture.png", settings);

@alice-i-cecile
Copy link
Member

For tiling backgrounds specifically, see https://lib.rs/crates/bevy_tiling_background for prior art.

@alice-i-cecile alice-i-cecile added C-Docs An addition or correction to our documentation and removed C-Feature A new feature, making something new possible labels Jan 11, 2024
@alice-i-cecile
Copy link
Member

This is now just a documentation issue: we should demonstrate the code showed by @blunted2night in a simple 2D example.

@nervenes
Copy link

With the new asset server in 0.12, this worked for me

    let sampler_desc = ImageSamplerDescriptor {
        address_mode_u: ImageAddressMode::Repeat,
        address_mode_v: ImageAddressMode::Repeat,
        ..Default::default()
    };

    let settings = move |s: &mut ImageLoaderSettings| {
        s.sampler = ImageSampler::Descriptor(sampler_desc.clone());
    };

    let texture_handle = assets.load_with_settings("texture.png", settings);

@blunted2night how did you spawn the sprite? this is my attempt and it doesn't seem to work properly:

fn load_tiled_texture(assets: Res<AssetServer>, mut commands: Commands) {
    let sampler_desc = ImageSamplerDescriptor {
        address_mode_u: ImageAddressMode::Repeat,
        address_mode_v: ImageAddressMode::Repeat,
        ..default()
    };

    let settings = move |s: &mut ImageLoaderSettings| {
        s.sampler = ImageSampler::Descriptor(sampler_desc.clone());
    };

    commands.spawn(SpriteBundle {
        texture: assets.load_with_settings("tile.png", settings),
        ..default()
    });
}

I'm a beginner to both rust and bevy, sorry if it's a rookie mistake.

@njeffords
Copy link

@evrsen in my case, I was using the texture for a terrain component I was playing with. I'm not sure but I think you would need to override the set of texture coordinates used for the sprite. I assume by default it would select 0 -> 1 for both axis.

commands.spawn(SpriteBundle {
    sprite: Sprite {
      rect: Some(Rect::new(0,0,10,1)),
      ..default()
    },
    texture: assets.load_with_settings("tile.png", settings),
    ..default()
});

@janhohenheim
Copy link
Member

Is it possible that changing the sampler after the image has already been loaded still has no effect? If so, can I somehow force the change? The reason I ask is because I want to apply the repeating effect to a texture that I'm loading from a .glb scene, so I don't really have the opportunity to call load_with_settings for just that one texture (AFAIK).

github-merge-queue bot pushed a commit that referenced this issue Feb 21, 2024
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 <[email protected]>
Co-authored-by: Kanabenki <[email protected]>
msvbg pushed a commit to msvbg/bevy that referenced this issue Feb 26, 2024
Adopted bevyengine#8266, so copy-pasting the description from there:

# Objective

Support the KHR_texture_transform extension for the glTF loader.

- Fixes bevyengine#6335
- Fixes bevyengine#11869 
- Implements part of bevyengine#11350
- Implements the GLTF part of bevyengine#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 <[email protected]>
Co-authored-by: Kanabenki <[email protected]>
msvbg pushed a commit to msvbg/bevy that referenced this issue Feb 26, 2024
Adopted bevyengine#8266, so copy-pasting the description from there:

# Objective

Support the KHR_texture_transform extension for the glTF loader.

- Fixes bevyengine#6335
- Fixes bevyengine#11869 
- Implements part of bevyengine#11350
- Implements the GLTF part of bevyengine#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 <[email protected]>
Co-authored-by: Kanabenki <[email protected]>
@bugsweeper
Copy link
Contributor

bugsweeper commented Apr 8, 2024

Since version 0.12 there is Bevy Asset v2. So you can add ImageSamplerDesctription settings via asset meta files near your png, instead of changing it from a code (additional profit is abbility to hotload, so you can test settings pretty fast). For example there is my config:

(
    meta_format_version: "1.0",
    asset: Process(
        processor: "bevy_asset::processor::process::LoadAndSave<bevy_render::texture::image_loader::ImageLoader, bevy_render::texture::compressed_image_saver::CompressedImageSaver>",
        settings: (
            loader_settings: (
                format: FromExtension,
                is_srgb: true,
                sampler: Descriptor(
                    ImageSamplerDescriptor(
                        address_mode_u: Repeat,
                        address_mode_v: Repeat,
                        address_mode_w: Repeat,
                        mag_filter: Nearest,
                        min_filter: Linear,
                        mipmap_filter: Linear,
                        lod_min_clamp: 0.0,
                        lod_max_clamp: 32.0,
                        anisotropy_clamp: 1,
                    ),
                ),
                asset_usage: ("RENDER_WORLD"),
            ),
            saver_settings: (),
        ),
    ),
)

But it adds just abbility to Repeat texture but not abbility to set proportions. For proportions you can use StandardMaterial::uv_transform, which is pretty fresh, because it had been added for KHR_texture_transform support to current main and it will be available from next 0.14 release

@janhohenheim
Copy link
Member

@bugsweeper would that meta file entry also work for setting only a specific texture of a gltf to repeat?

@bugsweeper
Copy link
Contributor

bugsweeper commented Apr 9, 2024

would that meta file entry also work for setting only a specific texture of a gltf to repeat?

No, bevy_gltf has code for sampler definition and it defines ImageAddressMode according to info loaded from gltf, but you can define sampler inside your gltf file, which is more appropriate way

@janhohenheim
Copy link
Member

janhohenheim commented Apr 9, 2024

@bugsweeper thanks for the info, I didn't know that! Do you know whether Bevy respects these sampler settings in the gltf? If so, I'd say we can close this issue.

@bugsweeper
Copy link
Contributor

bugsweeper commented Apr 9, 2024

Do you know whether Bevy respects these sampler settings in the gltf? If so, I'd say we can close this issue.

Yes, it uses code I mentioned above to generate sampler for images loaded from gltf

@janhohenheim
Copy link
Member

Nice! @alice-i-cecile, since I last blocked the closing of this issue, I think all of my concerns have been addressed, or at least they will be once 0.14 lands with my KHR_texture_transform changes. Voting to add this to the 0.14 milestone and closing it :)

@bugsweeper
Copy link
Contributor

But I think you should try to modify your gltf, to ensure it works as you expected, and then close this issue.

@janhohenheim
Copy link
Member

janhohenheim commented Apr 9, 2024

But I think you should try to modify your gltf, to ensure it works as you expected, and then close this issue.

Good idea! Unfortunately I won't have time for at least a couple of weeks, but I'd be glad if someone else confirmed this :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Assets Load files from disk to use for things like images, models, and sounds A-Rendering Drawing game state to the screen C-Docs An addition or correction to our documentation D-Trivial Nice and easy! A great choice to get started with Bevy
Projects
None yet
Development

No branches or pull requests