-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Add image sampler configuration in GLTF loader #17875
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
Conversation
I can't underrate anisotropic filtering.
Enforce anisotropy requirements to avoid wgpu Validation Error
Welcome, new contributor! Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨ |
I can't believe I fell for that twice
Would it be better to instead provide an override for the default sampler? That would allow users to control the properties of the sampler that aren't specified in the glTF without overriding the properties that are specified in the glTF. I think you could then also remove the separate That feels potentially more useful to me than a complete override, but this isn't really my area. |
A complete override still feels necessary as some sort of option to me: for example, it's a common thing in games to allow choosing between bilinear and trilinear filtering (there was a performance impact back in the day, now trilinear is cheap enough I'm not testing it) and I think Bevy should still allow implementing that, which just couldn't be done as long as we can't override glTF's fields somehow. Aside of I could replace commands.spawn(SceneRoot(asset_server.load_with_settings(
GltfAssetLabel::Scene(0).from_asset("models/test-scene.gltf"),
|settings: &mut GltfLoaderSettings| settings.default_sampler = ImageSamplerDesciptor{
anisotropy_clamp: 16,
..default()
}
))); which I believe looks bloated. |
wgpu's |
My concern is coming from |
In realistic project, they'd be overridden per-asset like I do in example. Setting up global override would be very intentional. |
"globally" is the wrong word, but as-is, you would not be able to override one of those other fields without nuking the value of Sorry if I'm being obtuse here. |
Yes, the only field that can be modified without nuking glTF's sampler data is |
Most games allow setting anisotropic filtering (among other settings) in the graphics options anytime, and I think that some way to facilitate that is needed in Bevy (possibly starting out as a third party crate). This pr only allows changing the anisotropic filtering level when loading the asset, and only for Gltf assets. I suppose the question is whether or not this solution sufficient to meet the needs of users until a more sophisticated solution is made, and I don't have the expertise to make that judgement. |
We have https://docs.rs/bevy/latest/bevy/prelude/struct.ImagePlugin.html#structfield.default_sampler, but this is not utilized by So something along the lines of this PR seems useful. |
Huh, I didn't know about that. Would a more desirable behavior be to use the default sampler to set any sampler settings that are unspecified by the Gltf? I took a glance at the definition, and we could store the default sampler in the GltfLoader in the same way we store it in the render app, so it should be possible. It could only be set once at the beginning of the app, but that's true for the render app too. |
I looked more into it and yes, |
Or by requiring an app restart. That's definitely a better situation than what we have now. Right now, you can technically you can use a non-
Might be able to get away with only storing a descriptor. |
This is true as it's currently written, but there's nothing stopping us from having the ImagePlugin store a copy of the default sampler in the world for us to access during the GlftLoader initialization. I don't think that would be an issue. This, with slightly modified form of your current solution where we have an Option<ImageSamplerDescriptor> and an "override everything" bool in the GltfLoaderSettings, would allow us to:
commands.spawn(SceneRoot(asset_server.load_with_settings(
GltfAssetLabel::Scene(0).from_asset("models/test-scene.gltf"),
|settings: &mut GltfLoaderSettings| settings.default_sampler = Some(ImageSamplerDesciptor{
anisotropy_clamp: 16,
..stored_default_sampler.clone()
})
)));
commands.spawn(SceneRoot(asset_server.load_with_settings(
GltfAssetLabel::Scene(0).from_asset("models/test-scene.gltf"),
|settings: &mut GltfLoaderSettings| settings.default_sampler = Some({
ImageSamplerDesciptor{
anisotropy_clamp: 16,
..stored_default_sampler.clone()
});
settings.override_asset_settings = true;
)));
commands.spawn(SceneRoot(asset_server.load_with_settings(
GltfAssetLabel::Scene(0).from_asset("models/test-scene.gltf"),
|settings: &mut GltfLoaderSettings| settings.override_asset_settings = true
))); Would something like this be the best of both worlds, at least until there's a more robust system in place that supersedes it? |
Perfect! I'm going to write that after #15994 gets merged. |
Awesome, let me know if you'd like me to help with or write any portion of this PR, I'd be glad to help! I'll take a look at #15994 as well. |
@axlitEels Boop. |
@thepackett Yes, I noticed. Was working on a different thing in my project, didn't want to mix it up. I'll begin today or tomorrow. I'm thinking about how the default descriptor should be stored. Rn it feels like a resource in main world with a single |
CI hates me
@thepackett thanks again for all the help! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made a couple of documentation suggestions, but otherwise this looks good to me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clicking approve as I've partly tested the changes (overriding via GltfLoaderSettings
) and can't see any issues. Just added one suggestion to fix a clippy error.
There's one problem I ran into while testing. If I load the same asset multiple times with different settings then it doesn't work - the settings from the first load seem to override later loads. I'm assuming that's a limitation of the asset system rather than a problem with this PR. If this PR lands then I might investigate some more and file a bug or feature request.
In case it's useful, the example is on this branch: https://github.com/greeble-dev/bevy/tree/pr/17875 . It technically works right now, but only by duplicating the asset.
I helped test this, and I can confirm that the issue is in the asset server and I've made an issue for it here #18267. The TLDR is that the asset server doesn't really support reloads fully. Reloads are accomplished by forcibly spawning a load task for an asset, but this does not forcibly spawn load tasks for dependencies of an asset, such as an image in this particular case. |
Ah, gotcha, I didn't see that bug. I'm kinda tempted to make a PR with my example since it will serve as a repro for #18267? Let me know if that sounds worthwhile. |
@greeble-dev IMO, It would be nice to have an example to show off the behavior in this PR, as well as to act as a gltf reloading test. Incidentally, any assets that are loaded as labeled subassets should still reload properly (I didn't actually test), since they don't go through the asset server in the same way. The gltf loader will treat images that are embedded directly in a gltf view (ie, stored directly in the gltf/glb, or in a .bin file, ie not stored as a URI to another file) as labeled subassets. As a test case / reproduction, I suspect it would be useful to use a gltf file that stores images in both ways so that it run and test both code paths. |
I don't understand how I didn't get this warn either Co-authored-by: Greeble <[email protected]>
@axlitEels this looks fantastic; can you resolve merge conflicts and I'll get this in for you? |
|
||
impl DefaultGltfImageSampler { | ||
/// Creates a new [`DefaultGltfImageSampler`]. | ||
pub fn new(descriptor: &ImageSamplerDescriptor) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about making this constructor pub(crate)
? This is supposed to be a resource only GltfPlugin
would need to insert. Any attempt of inserting one's own DefaultGltfImageSampler
would be a conflict with GltfPlugin
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I have to @alice-i-cecile
Because otherwise it's ready for merge
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the simpler and more flexible fully public design here.
I can't underrate anisotropic filtering.
Objective
ImageFilterMode
for glTF assets at runtime.Solution
DefaultGltfImageSampler
: it storesArc<Mutex<ImageSamplerDescriptor>>
and the sameArc
is stored inGltfLoader
. The default is independent from provided toImagePlugin
and is set in the same way but withGltfPlugin
. It can then be modified at runtime withDefaultGltfImageSampler::set
.GltfLoaderSettings
:default_sampler: Option<ImageSamplerDescriptor>
to override aforementioned global default descriptor andoverride_sampler: bool
to ignore glTF sampler data.Showcase
Enabling anisotropic filtering as easy as:
Use code below to ignore both the global default sampler and glTF data, having
your_shiny_sampler
used directly for all textures instead:Remove either setting to get different result! They don't come in pair!
Scene rendered with trillinear texture filtering:


Scene rendered with 16x anisotropic texture filtering:
Migration Guide
GltfLoaderSettings
have their default values replicate previous behavior.