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

Add a repeating texture example #11161

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,17 @@ description = "Demonstrates how to process and load custom assets"
category = "Assets"
wasm = false

[[example]]
name = "texture_sampler"
path = "examples/asset/texture_sampler.rs"
doc-scrape-examples = true

[package.metadata.example.texture_sampler]
name = "Texture sampler configuration"
description = "How to configure the texture to repeat instead of the default clamp to edges"
category = "Assets"
wasm = true

# Async Tasks
[[example]]
name = "async_compute"
Expand Down
Binary file added assets/textures/facade018a.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/textures/facade018a_copy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ Example | Description
[Custom Asset](../examples/asset/custom_asset.rs) | Implements a custom asset loader
[Custom Asset IO](../examples/asset/custom_asset_reader.rs) | Implements a custom AssetReader
[Hot Reloading of Assets](../examples/asset/hot_asset_reloading.rs) | Demonstrates automatic reloading of assets when modified on disk
[Texture sampler configuration](../examples/asset/texture_sampler.rs) | How to configure the texture to repeat instead of the default clamp to edges

## Async Tasks

Expand Down
120 changes: 120 additions & 0 deletions examples/asset/texture_sampler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//! By default Bevy loads images to textures with sampler settings that clamps the image to the edges
//! (UV coordinates outside of the range `0..=1` are clamped to `0..=1`).
//! This example shows how to change the sampler settings to repeat the image instead.

use bevy::app::App;
use bevy::app::Startup;
use bevy::asset::AssetServer;
use bevy::asset::Assets;
use bevy::math::Vec3;
use bevy::prelude::*;
use bevy::render::camera::ScalingMode;
use bevy::render::mesh::Indices;
use bevy::render::mesh::PrimitiveTopology;
use bevy::render::texture::ImageAddressMode;
use bevy::render::texture::ImageLoaderSettings;
use bevy::render::texture::ImageSampler;
use bevy::render::texture::ImageSamplerDescriptor;
use bevy::sprite::MaterialMesh2dBundle;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}

fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
asset_server: ResMut<AssetServer>,
) {
commands.spawn(Camera2dBundle {
projection: OrthographicProjection {
scaling_mode: ScalingMode::AutoMin {
min_width: 2.,
min_height: 1.,
},
far: 1000.,
near: -1000.,
..default()
},
..default()
});

// Texture from ambientCG.com, licensed under the Creative Commons CC0 1.0 Universal License.
// https://ambientCG.com/a/Facade018A

// By default Bevy loads images to textures with sampler settings that clamp the image to the edges.
let image = asset_server.load("textures/facade018a.png");

// Here we override the sampler settings to repeat the image instead.
let image_repeat = asset_server.load_with_settings(
// We are using another file name, because Bevy ignores different loader settings for the same file.
// https://github.com/bevyengine/bevy/issues/11111
"textures/facade018a_copy.png",
|s: &mut ImageLoaderSettings| match &mut s.sampler {
ImageSampler::Default => {
s.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::Repeat,
..default()
});
}
ImageSampler::Descriptor(sampler) => {
sampler.address_mode_u = ImageAddressMode::Repeat;
sampler.address_mode_v = ImageAddressMode::Repeat;
}
},
);

// Instead of using a standard quad with UV coordinates in the range `0..=1` (spanning the entire texture),
// we create a quad with UV coordinates in the range `-1..=2`, going beyond the texture by one entire unit in each direction.
// Texture behaviour then depends on the selected sampler mode.
// By default (`ImageAddressMode::ClampToEdge`) the out-of-bounds UV coordinates will map to the edge of the texture.
// When `ImageAddressMode::Repeat` is specified the out-of-bounds UV coordinates will repeat the texture.
Comment on lines +72 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My opinion, which has very little weight in this context would be this:

Less text is better, because those who read the example, would have to spend less time and energy reading it.

standard quad with UV coordinates

There's no such thing as "standard quad with UV coordinates". For example, if you have a mesh for a sphere, it usually contains hundreds quads, and none of them use coordinates 0..1. Quads with XY and UV coordinates 0..1 are used mostly in examples (and probably in Minecraft).

Also the comments below "usual UV range 0..=1", "custom UV coordinates" are not exactly correct for the same reason.

Texture behaviour then depends on the selected sampler mode

Texture behavior always depends on the sampler more, not "then". Perhaps you meant "Texture behaviour then depends on the selected address mode", which indeed then depends, because when UV is in range 0..=1, it does not depend on address mode.

By default (ImageAddressMode::ClampToEdge) the out-of-bounds UV coordinates will map to the edge of the texture.
When ImageAddressMode::Repeat is specified the out-of-bounds UV coordinates will repeat the texture

These would be the redundant comments because they

  • repeat the mostly obvious enum variant name
  • repeat enum documentation
  • basically repeat what is said several lines above where the image is loaded (where this comments belongs)

Again, feel free to ignore this comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You make a good point in regards to the sampler vs address mode, that should be reworded.

As per what a standard quad UV means, you're right that when you're talking about quad as a graphics primitive (part of a mesh), it could have any UVs for any reason, but when one talks about a quad as a mesh itself (a geometric quadrilateral) it does occur to me only as natural to use UV coordinates as usual.
I said usual UV range 0..=1, because in that range lie UVs of most meshes people come into contact with (3D designers don't typically make their UVs sample outside of a texture from my experience).

You're right the comment does seem to give a bunch of redundant information that could be found in the docstrings for ImageAddressMode, though the purpose of the comment is mainly to explain why it takes user-handled in UV coordinates + a change in address mode to achieve the desired repeating effect, because just changing the adress mode would not actually produce any noticeable effects.
I'll consider shortening this part to a refference to the appropriate docs.

Anyway if someone gives me another opinion on this, I'll rewrite it.

let mesh = meshes.add(quad_with_custom_uv(-1.0, 2.0));

commands.spawn(MaterialMesh2dBundle {
mesh: mesh.clone().into(),
material: materials.add(ColorMaterial {
texture: Some(image),
..default()
}),
transform: Transform::from_translation(Vec3::new(-0.95, -0.45, 0.))
.with_scale(Vec3::new(0.9, 0.9, 0.9)),
..default()
});

commands.spawn(MaterialMesh2dBundle {
mesh: mesh.into(),
material: materials.add(ColorMaterial {
texture: Some(image_repeat),
..default()
}),
transform: Transform::from_translation(Vec3::new(0.05, -0.45, 0.))
.with_scale(Vec3::new(0.9, 0.9, 0.9)),
..default()
});
}

/// Creates a quad with UV coordinates in range `uv_low..=uv_high`, instead of the usual UV range `0..=1`.
fn quad_with_custom_uv(uv_low: f32, uv_high: f32) -> Mesh {
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.insert_attribute(
Mesh::ATTRIBUTE_POSITION,
vec![[0., 0., 0.], [1., 0., 0.], [1., 1., 0.], [0., 1., 0.]],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the vertex positions should match those of Bevy's "default quad." Users putting together code from various Bevy examples might be surprised that this quad isn't "anchored" on its center like the default one.

I personally feel like we should just modify the default Quad here, and that the example would be a bit more self-documenting with a function more like repeating_quad(times: f32). I have an example using that method here: #11136 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modifying the default Quad might be better, I'll at least fix the mesh coordinates.

A better function name is welcome, although repeating_quad(times: f32) feels a little suggestive, because it suggest using this mesh would automatically cause repeating, whereas it only sets up a mesh that can be repeated over, but maybe this is not such a problem given the comments explaining the mechanisms.

);
mesh.insert_attribute(
Mesh::ATTRIBUTE_UV_0,
vec![
[uv_low, uv_high],
[uv_high, uv_high],
[uv_high, uv_low],
[uv_low, uv_low],
],
);
mesh.set_indices(Some(Indices::U16(vec![0, 1, 2, 2, 3, 0])));
mesh
}