Skip to content

Introduce TextureMap #889

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

Merged
merged 30 commits into from
Dec 17, 2024
Merged

Introduce TextureMap #889

merged 30 commits into from
Dec 17, 2024

Conversation

panxinmiao
Copy link
Contributor

@panxinmiao panxinmiao commented Nov 21, 2024

Add more robust support for texture in MeshMaterial to align with glTF specification requirements.

The goal is to enable pygfx users to easily load and display standard glTF models "out of the box" without needing to delve into model details or write processing code for each individual model.

This PR is part of that effort.


API changes

  • ImageBasicMaterial.map_interpolation is removed in favor of TextureMap.mag_filter and .min_filter.
  • LineMaterial.map_interpolation is removed in favor of TextureMap.mag_filter and .min_filter.
  • PointsMaterial.map_interpolation is removed in favor of TextureMap.mag_filter and .min_filter.
  • VolumeBasicMaterial.map_interpolation is removed in favor of TextureMap.mag_filter and .min_filter.

@panxinmiao panxinmiao requested a review from Korijn as a code owner November 21, 2024 03:20
@panxinmiao
Copy link
Contributor Author

panxinmiao commented Nov 22, 2024

The CI build failed due to the new version of imgui_bundle.
See : pygfx/wgpu-py#645

@almarklein
Copy link
Member

This is really nice! I'm only not sure about the API. We need a way for the glTF loader to let the render know what sampler properties to use for a texture.

We don't expose something like a sampler object. This is partly because these things are sometimes determined by the material, so creating a sampler object would be an abstraction leak, I think.

Adding sampler properties to the texture also does not feel right, because a texture may never be used via a sampler.

I'm thinking that we should:

  • Put the sampler properties together, in a separate object, maybe it can just be a dict.
  • Attach this to the texture via a prop called something like .sampler_hints or .sampler_info or .sampler_properties.
  • Then the shader can use these to set the sampling. And in cases where it makes sense, they can be ignored/overloaded by e.g. a material property.

I think we don't need these "hints" to be responsive. The idea is to set them before the texture's first use. If a dynamic property is needed, that should be implemented via a material.

@Korijn, @panxinmiao do you think this makese sense?

To give a bit of idea of what this could look like in the glTF loader:

tex = gfx.Texture(...)

sampler_props = {
    "mag_filter": "nearest",
    ...
}
tex.sampler_hints.update(sampler_props) 

@Korijn
Copy link
Collaborator

Korijn commented Nov 25, 2024

But you can reuse one texture with different samplers right?

@almarklein
Copy link
Member

almarklein commented Nov 25, 2024

But you can reuse one texture with different samplers right?

Yes. And you also load pixel values without a sampler. I think we want to offer a way for the thing that creates a texture to provide "intended sampling", and then it's up to the shader to honor that or not. E.g. for the fancy textures for mesh (pbr) rendering these sampling values will be used, but someone can do something like this:

image = gfx.Image(
    gfx.Geometry(grid=mesh.material.normal_map),
    gfx.ImageBasicMaterial(interpolation="linear"),
)

and that will use the same texture, but ignore the sampling hints.

My suggestion using a texture.sampling_hints dict is one idea to do this. I'm open to other methods :) But the point is to associate these sampling values with the texture without them "dictating" the sampling. Does that make sense?

@panxinmiao
Copy link
Contributor Author

  • Put the sampler properties together, in a separate object, maybe it can just be a dict.
  • Attach this to the texture via a prop called something like .sampler_hints or .sampler_info or .sampler_properties.
  • Then the shader can use these to set the sampling. And in cases where it makes sense, they can be ignored/overloaded by e.g. a material property.

For the current abstraction of textures in Pygfx, I think this approach makes sense. Let's do it this way first. :)

However, I feel like maybe we're missing a "Mapping" abstraction object, or perhaps we've confused the concepts of texture and mapping.

  • A texture is the image or pattern itself. It is typically a 2D image containing pixel data, represented as a matrix, and serves as a static resource. The Texture object is an abstraction of this "resource".

  • On the other hand, mapping is the process of applying a texture to a 3D model. The most common mapping method is UV mapping. The Mapping object should abstract this "process", including how the texture is mapped onto the model's surface.

In our material system, the various "maps" we use should actually be Mapping objects, not Textures. These should contain both the "resource" itself and the "method" by which that resource is applied.

@almarklein
Copy link
Member

@panxinmiao good point! It makes a lot more sense for these sampling/mapping props to be associated with the material!

My first guess was:

gfx.Mesh(
    ...
    gfx.MeshMaterial(map=some_texture, map_sampling=map_sampling_object)
)

But I also really like your idea:

gfx.Mesh(
    ...
    gfx.MeshMaterial(map=some_texture)  # default sampling
)

gfx.Mesh(
    ...
    gfx.MeshMaterial(map=gfx.Map(some_texture, mag_filter="nearest", ...))
)

@Korijn what do you think?

@Korijn
Copy link
Collaborator

Korijn commented Nov 26, 2024

The mapping seems like a very elegant solution! It fits well with the Material abstraction.

@panxinmiao panxinmiao changed the title Introduce uv_channel, filter_mode, and wrap_mode properties for Texture. Introduce TextureMap Dec 3, 2024
@panxinmiao
Copy link
Contributor Author

panxinmiao commented Dec 3, 2024

The TextureMap has been introduced.

Various map properties in the Material class have been updated to use TextureMap instead of Texture. For backward compatibility, when a Texture is set in a Material's map, it will automatically be converted to a TextureMap.

The only uncertainty I have is regarding the colorspace property. It could either be placed in the Texture or the TextureMap. I tend to favor placing it in the Texture, as it describes the data of the Texture, and it's unlikely that different TextureMaps would share the same Texture but with different colorspace settings.

The map_interpolation property has been removed from all Material objects. It was originally used to define the sampling filter method for the colormap, but it should have been a property of TextureMap rather than Material. We can now directly set this in the corresponding TextureMap object for the colormap, and each TextureMap should be able to define its own texture sampling method.

@almarklein
Copy link
Member

The only uncertainty I have is regarding the colorspace property. It could either be placed in the Texture or the TextureMap. I tend to favor placing it in the Texture, as it describes the data of the Texture,

I think I agree with you; the colorspace is also a more intrinsic property of the data, rather than a choice on how to represent the data. Plus it's used when the texture is used on a geometry.grid, in which case we don't have a map.

Copy link
Member

@almarklein almarklein left a comment

Choose a reason for hiding this comment

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

Nice work, a few small suggestions.

@Korijn Korijn enabled auto-merge (squash) December 17, 2024 13:56
@Korijn Korijn merged commit 4844ea0 into pygfx:main Dec 17, 2024
14 checks passed
@@ -70,7 +70,7 @@ def __init__(self, wobject):
self["img_nchannels"] = 3
elif material.map is not None:
self["use_colormap"] = True
self["colorspace"] = material.map.colorspace
self["colorspace"] = material.map.texture.colorspace
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to add a property to map that is defined as:

@property
def colorspace(self):
    return self.texture.colorspace

so as to avoid this needed change in downstream users?

Perhaps even with a "warning" that tells users to use map.texture.colorspace instead of map.colorspace.

Mostly a thin transition layer.

I'm happy to make the PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

hmm the changes to the interface of define_img_colormap are more breaking for myself, so i've created a more complicated shims that work for me.

I don't want push the focus toward these backward compatibility concerns too much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants